Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: formatted GraphQL queries for POST requests #127

Closed
codepunkt opened this issue Oct 24, 2017 · 29 comments
Closed

Feature request: formatted GraphQL queries for POST requests #127

codepunkt opened this issue Oct 24, 2017 · 29 comments

Comments

@codepunkt
Copy link

Amazing extension, thanks a lot!

What's missing for my usecase is a way of adding a formatted GraphQL query as a POST body.

I can do this to send GET with GraphQL queries, but this gets unreadable a lot really quick once the queries get more complicated:

GET http://localhost:2018/graphql/?query={projectList(count:50){id,name}}
Accept: application/json

What i would love to do instead is something like this:

POST http://localhost:2018/graphql
Accept: application/json
Type: GraphQL

query {
    projectList(count: 50) {
        id
        name
    }
}

I know that Type is not a header you'd want to specify and adding meta information that is not sent as a header is probably not part of RFC 2616, but it would be a way to format a query accordingly and could even have syntax highlighting for the query included.

Maybe there's others who would be interested in this ?

@Huachao
Copy link
Owner

Huachao commented Oct 24, 2017

@codepunkt nice suggestion, I will investigate it first and will update this thread later

@codepunkt
Copy link
Author

@Huachao I'm investigating myself. I can do this:

POST http://localhost:2018/graphql
Accept: application/json
Content-Type: application/json

{
    "query": "{
        projectList(count: 50) {
            id
            name
        }
    }"
}

But when i send it to a simple JSON parser, e.g. body-parser in a nodejs express webserver, it cannot be parsed and i'm unsure why. What happens to linebreaks in JSON strings?

Another idea that i had was having each graphQL query in it's own .gql file, which has syntax highlighting when using the appropriate VSCode extension. This could then be used a little bit like this < ./query.gql.

The GraphQL query itself would only be the string value of the query property in my former example snippet, which is why simply doing < ./query.gql will not work.

@codepunkt
Copy link
Author

codepunkt commented Oct 24, 2017

Okay. So it seems it would be easier to pass in GraphQL from a file.

# example.http

POST http://localhost:2018/graphql
Accept: application/json
Content-Type: application/json

> ./example.gql
query ProjectLists($defaultCount: Int!) {
  projectList(count: $defaultCount) {
    id
    name
  }
}

variables {
  "defaultCount": 50
}

If this would work, it would be great!
The actual JSON payload that would be sent in the body is this:

{
  "query":"query ProjectLists($defaultCount: Int!) {\n  projectList(count: $defaultCount) {\n    id\n    name\n  }\n}",
  "variables":{
    "defaultCount":50
  },
  "operationName":"ProjectLists"
}

There is a web frontend to explore and send GraphQL queries called GraphiQL, which basically does this:

graphql

They get the variables and query and transform them to corresponding JSON that is sent in a request body. I'm currently looking through their codebase to see if the code doing this is in it's own module - which would be a godsend because we might be able to reuse (parts of) it here.

Detect .gql piped into request body, use module to transform to JSON - done! 😁

@Huachao
Copy link
Owner

Huachao commented Oct 24, 2017

@codepunkt if use the post request for querying, does the content type should be 'application/graphql'. And I think for a graphql server side normally will support both get and post cases. But the request body is not in JSON.

@codepunkt
Copy link
Author

The POST Request section of the GraphQL docs states this explicitly. Content-Type is application/json.

@Huachao
Copy link
Owner

Huachao commented Oct 24, 2017

@codepunkt you are right, however I also found following sentences in the page you provided

In addition to the above, we recommend supporting two additional cases:

If the "query" query string parameter is present (as in the GET example above), it should be parsed and handled in the same way as the HTTP GET case.
If the "application/graphql" Content-Type header is present, treat the HTTP POST body contents as the GraphQL query string.
If you're using express-graphql, you already get these behaviors for free.

So the content-type is "application/graphql" works the same as query string, and no need to transform to JSON

@codepunkt
Copy link
Author

codepunkt commented Oct 25, 2017

Yes, that is correct. However, this is a way to query a server that almost no one uses and that i've not seen in production ever. Main reason is that it does not support variables, which is an important part of GraphQL.

A lot of GraphQL server libraries don't even implement it and imho there was a discussion somewhere to even remove it from the docs which i couldn't find offhand.

@Huachao
Copy link
Owner

Huachao commented Oct 26, 2017

@codepunkt thanks, previously I am not quite familiar with GraphQL. I will consider your suggestions carefully.

@codepunkt
Copy link
Author

@Huachao that you are even considering this, not being familiar with the technology, is amazing. Even if nothing comes from this, thank you!

@Huachao
Copy link
Owner

Huachao commented Oct 26, 2017

@codepunkt Thanks for your suggestion. I think it's a must to support GraphQL. And I will work in two aspects:

  • Fix the json error bug(root cause is it's a multi-line string in json field)
  • Support auto convert for graphql body to json (that means user only need to provide graphql format body)

@codepunkt
Copy link
Author

codepunkt commented Oct 26, 2017

How can i help? I don't have any experience with vscode exensions, but if you have questions on GraphQL or need a second opinion on anything, let me know!

@Huachao
Copy link
Owner

Huachao commented Oct 26, 2017

@codepunkt thanks, I will sure turn to you when I have questions about GraphQL

@dacz
Copy link

dacz commented Mar 31, 2018

@Huachao @codepunkt I'm up to help, too. This plugin is amazing and support for graphQl will be real time saver (not only for me, I suppose). I can help with function to serialize the payload - in another words it prepare object that should be sent then (like extract operation name and prepare query value).

Would that help?

@Huachao
Copy link
Owner

Huachao commented Mar 31, 2018

@dacz if you can help, that will be fantastic!

@marija17
Copy link

Any status on this feature? Love the plugin - GraphQL would be a huge value add for us.

@Huachao
Copy link
Owner

Huachao commented Aug 15, 2018

@marija17 sorry for no any update on this feature, since I am busy in my daily work

@marija17
Copy link

@Huachao - Thanks for the prompt response!

If I were to explore this myself, are you open to contributions to your plugin? I'm not sure how far you explored this addition - but did you run into any particular challenges or roadblocks you could note?

Alternatively, would you be open to someone using your plugin as a base (with credits given) to create a new plugin particularly for graphql?

@Huachao
Copy link
Owner

Huachao commented Aug 16, 2018

@marija17 I really welcome that you can contribute to my extension for the graphql feature. The challenges for me(since I am not familiar with graphql), what kind of request body should we support and how to support. Like following two examples:

POST http://localhost:2018/graphql
Accept: application/json

query {
    projectList(count: 50) {
        id
        name
    }
}

and

{
    "query": "{
        projectList(count: 50) {
            id
            name
        }
    }"
}

In both examples, they are not standard json body, that means how should we identify the request body targets for graphql and we should do some additional operations on the request body. BTW, do you think what other features we should support for this feature? Thanks in advance.

@alembiq
Copy link

alembiq commented Sep 13, 2018

thank for the great work so far
just FYI
GraphQL have other requests, not just "query", there is also "mutation" - which cannot be send through GET, only by POST
i've tried the @codepunkt solution for query, which works very nicely, but it doesn't work for mutation :(

POST  {{graphqlEndpoint}} 
content-type: application/json

{
    "mutation": "{ purchaseMovie(input: {id: \"ssss\", pin: \"4444\", deviceType: STB}){ clientMutationId }}"
}

@mfulton26
Copy link

@alembiq

i've tried the @codepunkt solution for query, which works very nicely, but it doesn't work for mutation :(

GraphQL mutations over HTTP still use "query" but you'll need mutation in the actual GQL:

POST  {{graphqlEndpoint}} 
content-type: application/json

{
    "query": "mutation { purchaseMovie(input: {id: \"ssss\", pin: \"4444\", deviceType: STB}){ clientMutationId }}"
}

@ferronrsmith
Copy link
Contributor

diff --git a/src/utils/httpRequestParser.ts b/src/utils/httpRequestParser.ts
index d9f8520..3c1a960 100644
--- a/src/utils/httpRequestParser.ts
+++ b/src/utils/httpRequestParser.ts
@@ -45,7 +45,10 @@ export class HttpRequestParser implements IRequestParser {
         // get headers range
         let headers: Headers;
         let body: string | Stream;
+        let variables: string | Stream;
         let bodyLines: string[] = [];
+        let variableLines: string[] = [];
+        let isGraphQlRequest: boolean = false
         let headerStartLine = ArrayUtility.firstIndexOf(lines, value => value.trim() !== '', 1);
         if (headerStartLine !== -1) {
             if (headerStartLine === 1) {
@@ -55,7 +58,7 @@ export class HttpRequestParser implements IRequestParser {
                 let headerLines = lines.slice(headerStartLine, headerEndLine);
                 let index = 0;
                 let queryString = '';
-                for (; index < headerLines.length; ) {
+                for (; index < headerLines.length;) {
                     let headerLine = (headerLines[index]).trim();
                     if (['?', '&'].includes(headerLine[0])) {
                         queryString += headerLine;
@@ -73,6 +76,7 @@ export class HttpRequestParser implements IRequestParser {
                 // get body range
                 const bodyStartLine = ArrayUtility.firstIndexOf(lines, value => value.trim() !== '', headerEndLine);
                 if (bodyStartLine !== -1) {
+                    const requestTypeHeader = getHeader(headers, 'x-request-type');
                     const contentTypeHeader = getHeader(headers, 'content-type') || getHeader(this._restClientSettings.defaultHeaders, 'content-type');
                     firstEmptyLine = ArrayUtility.firstIndexOf(lines, value => value.trim() === '', bodyStartLine);
                     const bodyEndLine =
@@ -80,6 +84,16 @@ export class HttpRequestParser implements IRequestParser {
                             ? lines.length
                             : firstEmptyLine;
                     bodyLines = lines.slice(bodyStartLine, bodyEndLine);
+
+                    if (requestTypeHeader && requestTypeHeader === 'GraphQL') {
+                        const variableStartLine = ArrayUtility.firstIndexOf(lines, value => value.trim() !== '', bodyEndLine);
+                        if (variableStartLine !== -1) {
+                            firstEmptyLine = ArrayUtility.firstIndexOf(lines, value => value.trim() === '', variableStartLine)
+                            variableLines = lines.slice(variableStartLine, firstEmptyLine === -1 ? lines.length : firstEmptyLine);
+                            isGraphQlRequest = true;
+                        }
+                    }
+
                 }
             } else {
                 // parse body, since no headers provided
@@ -99,19 +113,31 @@ export class HttpRequestParser implements IRequestParser {
 
         // parse body
         const contentTypeHeader = getHeader(headers, 'content-type') || getHeader(this._restClientSettings.defaultHeaders, 'content-type');
-        body = HttpRequestParser.parseRequestBody(bodyLines, requestAbsoluteFilePath, contentTypeHeader);
-        if (this._restClientSettings.formParamEncodingStrategy !== FormParamEncodingStrategy.Never && body && typeof body === 'string' && MimeUtility.isFormUrlEncoded(contentTypeHeader)) {
-            if (this._restClientSettings.formParamEncodingStrategy === FormParamEncodingStrategy.Always) {
-                const stringPairs = body.split('&');
-                const encodedStringParis = [];
-                for (const stringPair of stringPairs) {
-                    const [name, ...values] = stringPair.split('=');
-                    let value = values.join('=');
-                    encodedStringParis.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
+        if (isGraphQlRequest) {
+            body = HttpRequestParser.parseRequestBody(bodyLines, requestAbsoluteFilePath, contentTypeHeader);
+            variables = HttpRequestParser.parseRequestBody(variableLines, requestAbsoluteFilePath, contentTypeHeader);
+
+            let graphQlPayload = {
+                query: body,
+                variables: JSON.parse(variables.toString())
+            };
+            body = JSON.stringify(graphQlPayload, null, 2);
+            bodyLines = body.split(EOL)
+        } else {
+            body = HttpRequestParser.parseRequestBody(bodyLines, requestAbsoluteFilePath, contentTypeHeader);
+            if (this._restClientSettings.formParamEncodingStrategy !== FormParamEncodingStrategy.Never && body && typeof body === 'string' && MimeUtility.isFormUrlEncoded(contentTypeHeader)) {
+                if (this._restClientSettings.formParamEncodingStrategy === FormParamEncodingStrategy.Always) {
+                    const stringPairs = body.split('&');
+                    const encodedStringParis = [];
+                    for (const stringPair of stringPairs) {
+                        const [name, ...values] = stringPair.split('=');
+                        let value = values.join('=');
+                        encodedStringParis.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
+                    }
+                    body = encodedStringParis.join('&');
+                } else {
+                    body = encodeurl(body);
                 }
-                body = encodedStringParis.join('&');
-            } else {
-                body = encodeurl(body);
             }
         }

Here is a patch of httpRequestParser.ts that adds support GraphQL

Request looks something like this :

POST http://localhost:60000/graphql HTTP/1.1
content-type: application/json
x-request-type: GraphQL
Authorization: Bearer xxxx

query ($context: ListDataFilesInput) {
  listDataFiles(input: $context) {
    name,
    lastModified
  }
}

{
	"context": {
		"customerCode": "test",
		"meshKey": "test",
		"dataBucketCode": "test-bucket",
		"filter": ""
	}
}

####

Pay special attention to x-request-type: GraphQL. This customer-header tells the parser that it's a GraphQL request and it should look for a query/mutation + variables.

@dremekie
Copy link

dremekie commented May 9, 2019

@codepunkt @Huachao We have used the update above (from @ferronrsmith) in order to use GraphQL (which we use a great deal). It works very well. Would you be able to apply the patch above?

This plugin is extremely useful and having GraphQL support like this opens up the audience even more.

@imCorfitz
Copy link

imCorfitz commented Jul 7, 2019

@Huachao I second that.. GraphQL support like this would be beautiful.. Nice job @ferronrsmith

@Huachao
Copy link
Owner

Huachao commented Jul 8, 2019

@ferronrsmith @CorfitzMe @dremekie PR is warmly welcomed, pls feel free to create a PR and I will review it. Thanks in advance

@ferronrsmith
Copy link
Contributor

ferronrsmith commented Jul 8, 2019

#384

My organization fork uses relaxed-json instead of JSON.parse since it allows developers to better document their JSON before version control.

(Note in cases anyone wants to do this). Example :

POST http://localhost:60000/graphql HTTP/1.1
content-type: application/json
x-request-type: GraphQL
Authorization: Bearer xxxx

query ($context: ListDataFilesInput) {
  listDataFiles(input: $context) {
    name,
    lastModified
  }
}

{
	context: {
		customerCode: 'test', // customer unique identifier
		meshKey: 'test',     // customer environment (test/prod/demo)
		dataBucketCode: 'test-bucket',
		filter: '' // mongo filter can be passed here
	}
}

####

@Huachao
Copy link
Owner

Huachao commented Jul 9, 2019

@codepunkt @marija17 @jon301 @bisyonary @offero @mfulton26 @wprater @CorfitzMe @dacz @alembiq @dremekie with the fantastic patch from @ferronrsmith, the extension has the capability to send the GraphQL request directly. And this feature will be published in next release! You can provide a required query body and optional variables section.

You can easily achieve this by adding a custom http header in your request headers X-Request-Type: GraphQL, or simply type graphql, a built-in snippet will be available for you. Here I use the example provided by @ferronrsmith himself

POST http://localhost:60000/graphql HTTP/1.1
content-type: application/json
x-request-type: GraphQL
Authorization: Bearer xxxx

query ($context: ListDataFilesInput) {
  listDataFiles(input: $context) {
    name,
    lastModified
  }
}

{
    "context": {
        "customerCode": "test",
        "meshKey": "test",
            "dataBucketCode": "test-bucket",
            "filter": ""
       }
}

Thanks @ferronrsmith for your contribution 😄

@Huachao Huachao closed this as completed Jul 9, 2019
@Huachao
Copy link
Owner

Huachao commented Jul 31, 2019

@codepunkt @marija17 @jon301 @bisyonary @offero @mfulton26 @wprater @CorfitzMe @dacz @alembiq @dremekie GraphQL support is coming in the latest version 0.22.0, you can download and try it. Thanks for your patience, and @ferronrsmith 's great contribution to this feature.

@GoMino
Copy link

GoMino commented Sep 6, 2019

What about graphql fragment support ?

@hashinclude72
Copy link

hi @ferronrsmith, could you please add graphql fragment support also

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests