├── go.mod
├── go.sum
├── .gitignore
├── .circleci
└── config.yml
├── LICENSE
├── README.md
├── graphiql_test.go
├── graphcoolPlayground_test.go
├── graphcoolPlayground.go
├── graphiql.go
├── handler.go
├── handler_test.go
└── request_options_test.go
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/graphql-go/handler
2 |
3 | go 1.14
4 |
5 | require github.com/graphql-go/graphql v0.8.1 // indirect
6 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
2 | github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 | *.swp
26 | .idea
27 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | defaults: &defaults
2 | working_directory: /go/src/github.com/graphql-go/handler
3 | steps:
4 | - checkout
5 | - run: go get -v -t -d ./...
6 | - run: go test ./...
7 |
8 | version: 2
9 | jobs:
10 | golang:1.8.7:
11 | <<: *defaults
12 | docker:
13 | - image: circleci/golang:1.8.7
14 | golang:1.9.7:
15 | <<: *defaults
16 | docker:
17 | - image: circleci/golang:1.9.7
18 | golang:latest:
19 | <<: *defaults
20 | docker:
21 | - image: circleci/golang:latest
22 | coveralls:
23 | working_directory: /go/src/github.com/graphql-go/handler
24 | docker:
25 | - image: circleci/golang:latest
26 | steps:
27 | - checkout
28 | - run: go get -v -t -d ./...
29 | - run: go get github.com/mattn/goveralls
30 | - run: go test -v -cover -race -coverprofile=coverage.out
31 | - run: /go/bin/goveralls -coverprofile=coverage.out -service=circle-ci -repotoken $COVERALLS_TOKEN
32 |
33 | workflows:
34 | version: 2
35 | build:
36 | jobs:
37 | - golang:1.8.7
38 | - golang:1.9.7
39 | - golang:latest
40 | - coveralls
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Hafiz Ismail
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # graphql-go-handler [](https://circleci.com/gh/graphql-go/handler) [](https://godoc.org/github.com/graphql-go/handler) [](https://coveralls.io/github/graphql-go/handler?branch=master) [](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2 |
3 |
4 | Golang HTTP.Handler for [graphl-go](https://github.com/graphql-go/graphql)
5 |
6 | ### Usage
7 |
8 | ```go
9 | package main
10 |
11 | import (
12 | "net/http"
13 | "github.com/graphql-go/handler"
14 | )
15 |
16 | func main() {
17 | schema, _ := graphql.NewSchema(...)
18 |
19 | h := handler.New(&handler.Config{
20 | Schema: &schema,
21 | Pretty: true,
22 | GraphiQL: true,
23 | })
24 |
25 | http.Handle("/graphql", h)
26 | http.ListenAndServe(":8080", nil)
27 | }
28 | ```
29 |
30 | ### Using Playground
31 | ```go
32 | h := handler.New(&handler.Config{
33 | Schema: &schema,
34 | Pretty: true,
35 | GraphiQL: false,
36 | Playground: true,
37 | })
38 | ```
39 |
40 | ### Details
41 |
42 | The handler will accept requests with
43 | the parameters:
44 |
45 | * **`query`**: A string GraphQL document to be executed.
46 |
47 | * **`variables`**: The runtime values to use for any GraphQL query variables
48 | as a JSON object.
49 |
50 | * **`operationName`**: If the provided `query` contains multiple named
51 | operations, this specifies which operation should be executed. If not
52 | provided, an 400 error will be returned if the `query` contains multiple
53 | named operations.
54 |
55 | GraphQL will first look for each parameter in the URL's query-string:
56 |
57 | ```
58 | /graphql?query=query+getUser($id:ID){user(id:$id){name}}&variables={"id":"4"}
59 | ```
60 |
61 | If not found in the query-string, it will look in the POST request body.
62 | The `handler` will interpret it
63 | depending on the provided `Content-Type` header.
64 |
65 | * **`application/json`**: the POST body will be parsed as a JSON
66 | object of parameters.
67 |
68 | * **`application/x-www-form-urlencoded`**: this POST body will be
69 | parsed as a url-encoded string of key-value pairs.
70 |
71 | * **`application/graphql`**: The POST body will be parsed as GraphQL
72 | query string, which provides the `query` parameter.
73 |
74 |
75 | ### Examples
76 | - [golang-graphql-playground](https://github.com/graphql-go/playground)
77 | - [golang-relay-starter-kit](https://github.com/sogko/golang-relay-starter-kit)
78 | - [todomvc-relay-go](https://github.com/sogko/todomvc-relay-go)
79 |
80 | ### Test
81 | ```bash
82 | $ go get github.com/graphql-go/handler
83 | $ go build && go test ./...
84 |
--------------------------------------------------------------------------------
/graphiql_test.go:
--------------------------------------------------------------------------------
1 | package handler_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/graphql-go/graphql/testutil"
10 | "github.com/graphql-go/handler"
11 | )
12 |
13 | func TestRenderGraphiQL(t *testing.T) {
14 | cases := map[string]struct {
15 | graphiqlEnabled bool
16 | accept string
17 | url string
18 | expectedStatusCode int
19 | expectedContentType string
20 | expectedBodyContains string
21 | }{
22 | "renders GraphiQL": {
23 | graphiqlEnabled: true,
24 | accept: "text/html",
25 | expectedStatusCode: http.StatusOK,
26 | expectedContentType: "text/html; charset=utf-8",
27 | expectedBodyContains: "",
28 | },
29 | "doesn't render graphiQL if turned off": {
30 | graphiqlEnabled: false,
31 | accept: "text/html",
32 | expectedStatusCode: http.StatusOK,
33 | expectedContentType: "application/json; charset=utf-8",
34 | },
35 | "doesn't render GraphiQL if Content-Type application/json is present": {
36 | graphiqlEnabled: true,
37 | accept: "application/json,text/html",
38 | expectedStatusCode: http.StatusOK,
39 | expectedContentType: "application/json; charset=utf-8",
40 | },
41 | "doesn't render GraphiQL if Content-Type text/html is not present": {
42 | graphiqlEnabled: true,
43 | expectedStatusCode: http.StatusOK,
44 | expectedContentType: "application/json; charset=utf-8",
45 | },
46 | "doesn't render GraphiQL if 'raw' query is present": {
47 | graphiqlEnabled: true,
48 | accept: "text/html",
49 | url: "?raw",
50 | expectedStatusCode: http.StatusOK,
51 | expectedContentType: "application/json; charset=utf-8",
52 | },
53 | }
54 |
55 | for tcID, tc := range cases {
56 | t.Run(tcID, func(t *testing.T) {
57 | req, err := http.NewRequest(http.MethodGet, tc.url, nil)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | req.Header.Set("Accept", tc.accept)
63 |
64 | h := handler.New(&handler.Config{
65 | Schema: &testutil.StarWarsSchema,
66 | GraphiQL: tc.graphiqlEnabled,
67 | })
68 |
69 | rr := httptest.NewRecorder()
70 |
71 | h.ServeHTTP(rr, req)
72 | resp := rr.Result()
73 |
74 | statusCode := resp.StatusCode
75 | if statusCode != tc.expectedStatusCode {
76 | t.Fatalf("%s: wrong status code, expected %v, got %v", tcID, tc.expectedStatusCode, statusCode)
77 | }
78 |
79 | contentType := resp.Header.Get("Content-Type")
80 | if contentType != tc.expectedContentType {
81 | t.Fatalf("%s: wrong content type, expected %s, got %s", tcID, tc.expectedContentType, contentType)
82 | }
83 |
84 | body := rr.Body.String()
85 | if !strings.Contains(body, tc.expectedBodyContains) {
86 | t.Fatalf("%s: wrong body, expected %s to contain %s", tcID, body, tc.expectedBodyContains)
87 | }
88 | })
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/graphcoolPlayground_test.go:
--------------------------------------------------------------------------------
1 | package handler_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/graphql-go/graphql/testutil"
10 | "github.com/graphql-go/handler"
11 | )
12 |
13 | func TestRenderPlayground(t *testing.T) {
14 | cases := map[string]struct {
15 | playgroundEnabled bool
16 | accept string
17 | url string
18 | expectedStatusCode int
19 | expectedContentType string
20 | expectedBodyContains string
21 | }{
22 | "renders Playground": {
23 | playgroundEnabled: true,
24 | accept: "text/html",
25 | expectedStatusCode: http.StatusOK,
26 | expectedContentType: "text/html; charset=utf-8",
27 | expectedBodyContains: "",
28 | },
29 | "doesn't render Playground if turned off": {
30 | playgroundEnabled: false,
31 | accept: "text/html",
32 | expectedStatusCode: http.StatusOK,
33 | expectedContentType: "application/json; charset=utf-8",
34 | },
35 | "doesn't render Playground if Content-Type application/json is present": {
36 | playgroundEnabled: true,
37 | accept: "application/json,text/html",
38 | expectedStatusCode: http.StatusOK,
39 | expectedContentType: "application/json; charset=utf-8",
40 | },
41 | "doesn't render Playground if Content-Type text/html is not present": {
42 | playgroundEnabled: true,
43 | expectedStatusCode: http.StatusOK,
44 | expectedContentType: "application/json; charset=utf-8",
45 | },
46 | "doesn't render Playground if 'raw' query is present": {
47 | playgroundEnabled: true,
48 | accept: "text/html",
49 | url: "?raw",
50 | expectedStatusCode: http.StatusOK,
51 | expectedContentType: "application/json; charset=utf-8",
52 | },
53 | }
54 |
55 | for tcID, tc := range cases {
56 | t.Run(tcID, func(t *testing.T) {
57 | req, err := http.NewRequest(http.MethodGet, tc.url, nil)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | req.Header.Set("Accept", tc.accept)
63 |
64 | h := handler.New(&handler.Config{
65 | Schema: &testutil.StarWarsSchema,
66 | GraphiQL: false,
67 | Playground: tc.playgroundEnabled,
68 | })
69 |
70 | rr := httptest.NewRecorder()
71 |
72 | h.ServeHTTP(rr, req)
73 | resp := rr.Result()
74 |
75 | statusCode := resp.StatusCode
76 | if statusCode != tc.expectedStatusCode {
77 | t.Fatalf("%s: wrong status code, expected %v, got %v", tcID, tc.expectedStatusCode, statusCode)
78 | }
79 |
80 | contentType := resp.Header.Get("Content-Type")
81 | if contentType != tc.expectedContentType {
82 | t.Fatalf("%s: wrong content type, expected %s, got %s", tcID, tc.expectedContentType, contentType)
83 | }
84 |
85 | body := rr.Body.String()
86 | if !strings.Contains(body, tc.expectedBodyContains) {
87 | t.Fatalf("%s: wrong body, expected %s to contain %s", tcID, body, tc.expectedBodyContains)
88 | }
89 | })
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/graphcoolPlayground.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "html/template"
5 | "net/http"
6 | )
7 |
8 | type playgroundData struct {
9 | PlaygroundVersion string
10 | Endpoint string
11 | SubscriptionEndpoint string
12 | SetTitle bool
13 | }
14 |
15 | // renderPlayground renders the Playground GUI
16 | func renderPlayground(w http.ResponseWriter, r *http.Request, endpoint string, subscriptionEndpoint string) {
17 | t := template.New("Playground")
18 | t, err := t.Parse(graphcoolPlaygroundTemplate)
19 | if err != nil {
20 | http.Error(w, err.Error(), http.StatusInternalServerError)
21 | return
22 | }
23 |
24 | d := playgroundData{
25 | PlaygroundVersion: graphcoolPlaygroundVersion,
26 | Endpoint: endpoint,
27 | SubscriptionEndpoint: subscriptionEndpoint,
28 | SetTitle: true,
29 | }
30 | err = t.ExecuteTemplate(w, "index", d)
31 | if err != nil {
32 | http.Error(w, err.Error(), http.StatusInternalServerError)
33 | }
34 | }
35 |
36 | const graphcoolPlaygroundVersion = "1.5.2"
37 |
38 | const graphcoolPlaygroundTemplate = `
39 | {{ define "index" }}
40 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | GraphQL Playground
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
89 |

90 |
Loading
91 | GraphQL Playground
92 |
93 |
94 |
102 |
103 |
104 |
105 | {{ end }}
106 | `
107 |
--------------------------------------------------------------------------------
/graphiql.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "html/template"
6 | "net/http"
7 |
8 | "github.com/graphql-go/graphql"
9 | )
10 |
11 | // graphiqlData is the page data structure of the rendered GraphiQL page
12 | type graphiqlData struct {
13 | GraphiqlVersion string
14 | QueryString string
15 | VariablesString string
16 | OperationName string
17 | ResultString string
18 | }
19 |
20 | // renderGraphiQL renders the GraphiQL GUI
21 | func renderGraphiQL(w http.ResponseWriter, params graphql.Params) {
22 | t := template.New("GraphiQL")
23 | t, err := t.Parse(graphiqlTemplate)
24 | if err != nil {
25 | http.Error(w, err.Error(), http.StatusInternalServerError)
26 | return
27 | }
28 |
29 | // Create variables string
30 | vars, err := json.MarshalIndent(params.VariableValues, "", " ")
31 | if err != nil {
32 | http.Error(w, err.Error(), http.StatusInternalServerError)
33 | return
34 | }
35 | varsString := string(vars)
36 | if varsString == "null" {
37 | varsString = ""
38 | }
39 |
40 | // Create result string
41 | var resString string
42 | if params.RequestString == "" {
43 | resString = ""
44 | } else {
45 | result, err := json.MarshalIndent(graphql.Do(params), "", " ")
46 | if err != nil {
47 | http.Error(w, err.Error(), http.StatusInternalServerError)
48 | return
49 | }
50 | resString = string(result)
51 | }
52 |
53 | d := graphiqlData{
54 | GraphiqlVersion: graphiqlVersion,
55 | QueryString: params.RequestString,
56 | ResultString: resString,
57 | VariablesString: varsString,
58 | OperationName: params.OperationName,
59 | }
60 | err = t.ExecuteTemplate(w, "index", d)
61 | if err != nil {
62 | http.Error(w, err.Error(), http.StatusInternalServerError)
63 | }
64 |
65 | return
66 | }
67 |
68 | // graphiqlVersion is the current version of GraphiQL
69 | const graphiqlVersion = "0.11.11"
70 |
71 | // tmpl is the page template to render GraphiQL
72 | const graphiqlTemplate = `
73 | {{ define "index" }}
74 |
82 |
83 |
84 |
85 |
86 | GraphiQL
87 |
88 |
89 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Loading...
109 |
202 |
203 |
204 | {{ end }}
205 | `
206 |
--------------------------------------------------------------------------------
/handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "net/url"
9 | "strings"
10 |
11 | "github.com/graphql-go/graphql"
12 |
13 | "context"
14 |
15 | "github.com/graphql-go/graphql/gqlerrors"
16 | )
17 |
18 | const (
19 | ContentTypeJSON = "application/json"
20 | ContentTypeGraphQL = "application/graphql"
21 | ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"
22 | )
23 |
24 | type ResultCallbackFn func(ctx context.Context, params *graphql.Params, result *graphql.Result, responseBody []byte)
25 |
26 | type Handler struct {
27 | Schema *graphql.Schema
28 | pretty bool
29 | graphiql bool
30 | playground bool
31 | playgroundConfig *PlaygroundConfig
32 | rootObjectFn RootObjectFn
33 | resultCallbackFn ResultCallbackFn
34 | formatErrorFn func(err error) gqlerrors.FormattedError
35 | }
36 |
37 | type RequestOptions struct {
38 | Query string `json:"query" url:"query" schema:"query"`
39 | Variables map[string]interface{} `json:"variables" url:"variables" schema:"variables"`
40 | OperationName string `json:"operationName" url:"operationName" schema:"operationName"`
41 | }
42 |
43 | // a workaround for getting`variables` as a JSON string
44 | type requestOptionsCompatibility struct {
45 | Query string `json:"query" url:"query" schema:"query"`
46 | Variables string `json:"variables" url:"variables" schema:"variables"`
47 | OperationName string `json:"operationName" url:"operationName" schema:"operationName"`
48 | }
49 |
50 | func getFromForm(values url.Values) *RequestOptions {
51 | query := values.Get("query")
52 | if query != "" {
53 | // get variables map
54 | variables := make(map[string]interface{}, len(values))
55 | variablesStr := values.Get("variables")
56 | json.Unmarshal([]byte(variablesStr), &variables)
57 |
58 | return &RequestOptions{
59 | Query: query,
60 | Variables: variables,
61 | OperationName: values.Get("operationName"),
62 | }
63 | }
64 |
65 | return nil
66 | }
67 |
68 | // RequestOptions Parses a http.Request into GraphQL request options struct
69 | func NewRequestOptions(r *http.Request) *RequestOptions {
70 | if reqOpt := getFromForm(r.URL.Query()); reqOpt != nil {
71 | return reqOpt
72 | }
73 |
74 | if r.Method != http.MethodPost {
75 | return &RequestOptions{}
76 | }
77 |
78 | if r.Body == nil {
79 | return &RequestOptions{}
80 | }
81 |
82 | // TODO: improve Content-Type handling
83 | contentTypeStr := r.Header.Get("Content-Type")
84 | contentTypeTokens := strings.Split(contentTypeStr, ";")
85 | contentType := contentTypeTokens[0]
86 |
87 | switch contentType {
88 | case ContentTypeGraphQL:
89 | body, err := ioutil.ReadAll(r.Body)
90 | if err != nil {
91 | return &RequestOptions{}
92 | }
93 | return &RequestOptions{
94 | Query: string(body),
95 | }
96 | case ContentTypeFormURLEncoded:
97 | if err := r.ParseForm(); err != nil {
98 | return &RequestOptions{}
99 | }
100 |
101 | if reqOpt := getFromForm(r.PostForm); reqOpt != nil {
102 | return reqOpt
103 | }
104 |
105 | return &RequestOptions{}
106 |
107 | case ContentTypeJSON:
108 | fallthrough
109 | default:
110 | var opts RequestOptions
111 | body, err := ioutil.ReadAll(r.Body)
112 | if err != nil {
113 | return &opts
114 | }
115 | err = json.Unmarshal(body, &opts)
116 | if err != nil {
117 | // Probably `variables` was sent as a string instead of an object.
118 | // So, we try to be polite and try to parse that as a JSON string
119 | var optsCompatible requestOptionsCompatibility
120 | json.Unmarshal(body, &optsCompatible)
121 | json.Unmarshal([]byte(optsCompatible.Variables), &opts.Variables)
122 | }
123 | return &opts
124 | }
125 | }
126 |
127 | // ContextHandler provides an entrypoint into executing graphQL queries with a
128 | // user-provided context.
129 | func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
130 | // get query
131 | opts := NewRequestOptions(r)
132 |
133 | // execute graphql query
134 | params := graphql.Params{
135 | Schema: *h.Schema,
136 | RequestString: opts.Query,
137 | VariableValues: opts.Variables,
138 | OperationName: opts.OperationName,
139 | Context: ctx,
140 | }
141 | if h.rootObjectFn != nil {
142 | params.RootObject = h.rootObjectFn(ctx, r)
143 | }
144 | result := graphql.Do(params)
145 |
146 | if formatErrorFn := h.formatErrorFn; formatErrorFn != nil && len(result.Errors) > 0 {
147 | formatted := make([]gqlerrors.FormattedError, len(result.Errors))
148 | for i, formattedError := range result.Errors {
149 | formatted[i] = formatErrorFn(formattedError.OriginalError())
150 | }
151 | result.Errors = formatted
152 | }
153 |
154 | if h.graphiql {
155 | acceptHeader := r.Header.Get("Accept")
156 | _, raw := r.URL.Query()["raw"]
157 | if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
158 | renderGraphiQL(w, params)
159 | return
160 | }
161 | }
162 |
163 | if h.playground {
164 | acceptHeader := r.Header.Get("Accept")
165 | _, raw := r.URL.Query()["raw"]
166 | if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
167 |
168 | endpoint := r.URL.Path
169 | subscriptionEndpoint := fmt.Sprintf("ws://%v/subscriptions", r.Host)
170 | if h.playgroundConfig != nil {
171 | endpoint = h.playgroundConfig.Endpoint
172 | subscriptionEndpoint = h.playgroundConfig.SubscriptionEndpoint
173 | }
174 |
175 | renderPlayground(w, r, endpoint, subscriptionEndpoint)
176 | return
177 | }
178 | }
179 |
180 | // use proper JSON Header
181 | w.Header().Add("Content-Type", "application/json; charset=utf-8")
182 |
183 | var buff []byte
184 | if h.pretty {
185 | w.WriteHeader(http.StatusOK)
186 | buff, _ = json.MarshalIndent(result, "", "\t")
187 |
188 | w.Write(buff)
189 | } else {
190 | w.WriteHeader(http.StatusOK)
191 | buff, _ = json.Marshal(result)
192 |
193 | w.Write(buff)
194 | }
195 |
196 | if h.resultCallbackFn != nil {
197 | h.resultCallbackFn(ctx, ¶ms, result, buff)
198 | }
199 | }
200 |
201 | // ServeHTTP provides an entrypoint into executing graphQL queries.
202 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
203 | h.ContextHandler(r.Context(), w, r)
204 | }
205 |
206 | // RootObjectFn allows a user to generate a RootObject per request
207 | type RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}
208 |
209 | type PlaygroundConfig struct {
210 | Endpoint string
211 | SubscriptionEndpoint string
212 | }
213 |
214 | type Config struct {
215 | Schema *graphql.Schema
216 | Pretty bool
217 | GraphiQL bool
218 | Playground bool
219 | PlaygroundConfig *PlaygroundConfig
220 | RootObjectFn RootObjectFn
221 | ResultCallbackFn ResultCallbackFn
222 | FormatErrorFn func(err error) gqlerrors.FormattedError
223 | }
224 |
225 | func NewConfig() *Config {
226 | return &Config{
227 | Schema: nil,
228 | Pretty: true,
229 | GraphiQL: true,
230 | Playground: false,
231 | PlaygroundConfig: nil,
232 | }
233 | }
234 |
235 | func New(p *Config) *Handler {
236 | if p == nil {
237 | p = NewConfig()
238 | }
239 |
240 | if p.Schema == nil {
241 | panic("undefined GraphQL schema")
242 | }
243 |
244 | return &Handler{
245 | Schema: p.Schema,
246 | pretty: p.Pretty,
247 | graphiql: p.GraphiQL,
248 | playground: p.Playground,
249 | playgroundConfig: p.PlaygroundConfig,
250 | rootObjectFn: p.RootObjectFn,
251 | resultCallbackFn: p.ResultCallbackFn,
252 | formatErrorFn: p.FormatErrorFn,
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/handler_test.go:
--------------------------------------------------------------------------------
1 | package handler_test
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "net/http/httptest"
9 | "reflect"
10 | "strings"
11 | "testing"
12 |
13 | "context"
14 |
15 | "github.com/graphql-go/graphql"
16 | "github.com/graphql-go/graphql/gqlerrors"
17 | "github.com/graphql-go/graphql/language/location"
18 | "github.com/graphql-go/graphql/testutil"
19 | "github.com/graphql-go/handler"
20 | )
21 |
22 | func decodeResponse(t *testing.T, recorder *httptest.ResponseRecorder) *graphql.Result {
23 | // clone request body reader so that we can have a nicer error message
24 | bodyString := ""
25 | var target graphql.Result
26 | if b, err := ioutil.ReadAll(recorder.Body); err == nil {
27 | bodyString = string(b)
28 | }
29 | readerClone := strings.NewReader(bodyString)
30 |
31 | decoder := json.NewDecoder(readerClone)
32 | err := decoder.Decode(&target)
33 | if err != nil {
34 | t.Fatalf("DecodeResponseToType(): %v \n%v", err.Error(), bodyString)
35 | }
36 | return &target
37 | }
38 | func executeTest(t *testing.T, h *handler.Handler, req *http.Request) (*graphql.Result, *httptest.ResponseRecorder) {
39 | resp := httptest.NewRecorder()
40 | h.ServeHTTP(resp, req)
41 | result := decodeResponse(t, resp)
42 | return result, resp
43 | }
44 |
45 | func TestContextPropagated(t *testing.T) {
46 | myNameQuery := graphql.NewObject(graphql.ObjectConfig{
47 | Name: "Query",
48 | Fields: graphql.Fields{
49 | "name": &graphql.Field{
50 | Name: "name",
51 | Type: graphql.String,
52 | Resolve: func(p graphql.ResolveParams) (interface{}, error) {
53 | return p.Context.Value("name"), nil
54 | },
55 | },
56 | },
57 | })
58 | myNameSchema, err := graphql.NewSchema(graphql.SchemaConfig{
59 | Query: myNameQuery,
60 | })
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 |
65 | expected := &graphql.Result{
66 | Data: map[string]interface{}{
67 | "name": "context-data",
68 | },
69 | }
70 | queryString := `query={name}`
71 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
72 |
73 | h := handler.New(&handler.Config{
74 | Schema: &myNameSchema,
75 | Pretty: true,
76 | })
77 |
78 | ctx := context.WithValue(context.Background(), "name", "context-data")
79 | resp := httptest.NewRecorder()
80 | h.ContextHandler(ctx, resp, req)
81 | result := decodeResponse(t, resp)
82 | if resp.Code != http.StatusOK {
83 | t.Fatalf("unexpected server response %v", resp.Code)
84 | }
85 | if !reflect.DeepEqual(result, expected) {
86 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
87 | }
88 | }
89 |
90 | func TestHandler_BasicQuery_Pretty(t *testing.T) {
91 | expected := &graphql.Result{
92 | Data: map[string]interface{}{
93 | "hero": map[string]interface{}{
94 | "name": "R2-D2",
95 | },
96 | },
97 | }
98 | queryString := `query=query HeroNameQuery { hero { name } }&operationName=HeroNameQuery`
99 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
100 |
101 | callbackCalled := false
102 | h := handler.New(&handler.Config{
103 | Schema: &testutil.StarWarsSchema,
104 | Pretty: true,
105 | ResultCallbackFn: func(ctx context.Context, params *graphql.Params, result *graphql.Result, responseBody []byte) {
106 | callbackCalled = true
107 | if params.OperationName != "HeroNameQuery" {
108 | t.Fatalf("OperationName passed to callback was not HeroNameQuery: %v", params.OperationName)
109 | }
110 |
111 | if result.HasErrors() {
112 | t.Fatalf("unexpected graphql result errors")
113 | }
114 | },
115 | })
116 | result, resp := executeTest(t, h, req)
117 | if resp.Code != http.StatusOK {
118 | t.Fatalf("unexpected server response %v", resp.Code)
119 | }
120 | if !reflect.DeepEqual(result, expected) {
121 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
122 | }
123 | if !callbackCalled {
124 | t.Fatalf("ResultCallbackFn was not called when it should have been")
125 | }
126 | }
127 |
128 | func TestHandler_BasicQuery_Ugly(t *testing.T) {
129 | expected := &graphql.Result{
130 | Data: map[string]interface{}{
131 | "hero": map[string]interface{}{
132 | "name": "R2-D2",
133 | },
134 | },
135 | }
136 | queryString := `query=query HeroNameQuery { hero { name } }`
137 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
138 |
139 | h := handler.New(&handler.Config{
140 | Schema: &testutil.StarWarsSchema,
141 | Pretty: false,
142 | })
143 | result, resp := executeTest(t, h, req)
144 | if resp.Code != http.StatusOK {
145 | t.Fatalf("unexpected server response %v", resp.Code)
146 | }
147 | if !reflect.DeepEqual(result, expected) {
148 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
149 | }
150 | }
151 |
152 | func TestHandler_Params_NilParams(t *testing.T) {
153 | defer func() {
154 | if r := recover(); r != nil {
155 | if str, ok := r.(string); ok {
156 | if str != "undefined GraphQL schema" {
157 | t.Fatalf("unexpected error, got %v", r)
158 | }
159 | // test passed
160 | return
161 | }
162 | t.Fatalf("unexpected error, got %v", r)
163 |
164 | }
165 | t.Fatalf("expected to panic, did not panic")
166 | }()
167 | _ = handler.New(nil)
168 |
169 | }
170 |
171 | func TestHandler_BasicQuery_WithRootObjFn(t *testing.T) {
172 | myNameQuery := graphql.NewObject(graphql.ObjectConfig{
173 | Name: "Query",
174 | Fields: graphql.Fields{
175 | "name": &graphql.Field{
176 | Name: "name",
177 | Type: graphql.String,
178 | Resolve: func(p graphql.ResolveParams) (interface{}, error) {
179 | rv := p.Info.RootValue.(map[string]interface{})
180 | return rv["rootValue"], nil
181 | },
182 | },
183 | },
184 | })
185 | myNameSchema, err := graphql.NewSchema(graphql.SchemaConfig{
186 | Query: myNameQuery,
187 | })
188 | if err != nil {
189 | t.Fatal(err)
190 | }
191 |
192 | expected := &graphql.Result{
193 | Data: map[string]interface{}{
194 | "name": "foo",
195 | },
196 | }
197 | queryString := `query={name}`
198 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
199 |
200 | h := handler.New(&handler.Config{
201 | Schema: &myNameSchema,
202 | Pretty: true,
203 | RootObjectFn: func(ctx context.Context, r *http.Request) map[string]interface{} {
204 | return map[string]interface{}{"rootValue": "foo"}
205 | },
206 | })
207 | result, resp := executeTest(t, h, req)
208 | if resp.Code != http.StatusOK {
209 | t.Fatalf("unexpected server response %v", resp.Code)
210 | }
211 | if !reflect.DeepEqual(result, expected) {
212 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
213 | }
214 | }
215 |
216 | type customError struct {
217 | message string
218 | }
219 |
220 | func (e customError) Error() string {
221 | return fmt.Sprintf("%s", e.message)
222 | }
223 |
224 | func TestHandler_BasicQuery_WithFormatErrorFn(t *testing.T) {
225 | resolverError := customError{message: "resolver error"}
226 | myNameQuery := graphql.NewObject(graphql.ObjectConfig{
227 | Name: "Query",
228 | Fields: graphql.Fields{
229 | "name": &graphql.Field{
230 | Name: "name",
231 | Type: graphql.String,
232 | Resolve: func(p graphql.ResolveParams) (interface{}, error) {
233 | return nil, resolverError
234 | },
235 | },
236 | },
237 | })
238 | myNameSchema, err := graphql.NewSchema(graphql.SchemaConfig{
239 | Query: myNameQuery,
240 | })
241 | if err != nil {
242 | t.Fatal(err)
243 | }
244 |
245 | customFormattedError := gqlerrors.FormattedError{
246 | Message: resolverError.Error(),
247 | Locations: []location.SourceLocation{
248 | location.SourceLocation{
249 | Line: 1,
250 | Column: 2,
251 | },
252 | },
253 | Path: []interface{}{"name"},
254 | }
255 |
256 | expected := &graphql.Result{
257 | Data: map[string]interface{}{
258 | "name": nil,
259 | },
260 | Errors: []gqlerrors.FormattedError{customFormattedError},
261 | }
262 |
263 | queryString := `query={name}`
264 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
265 |
266 | formatErrorFnCalled := false
267 | h := handler.New(&handler.Config{
268 | Schema: &myNameSchema,
269 | Pretty: true,
270 | FormatErrorFn: func(err error) gqlerrors.FormattedError {
271 | formatErrorFnCalled = true
272 | var formatted gqlerrors.FormattedError
273 | switch err := err.(type) {
274 | case *gqlerrors.Error:
275 | formatted = gqlerrors.FormatError(err)
276 | default:
277 | t.Fatalf("unexpected error type: %v", reflect.TypeOf(err))
278 | }
279 | return formatted
280 | },
281 | })
282 | result, resp := executeTest(t, h, req)
283 | if resp.Code != http.StatusOK {
284 | t.Fatalf("unexpected server response %v", resp.Code)
285 | }
286 | if !formatErrorFnCalled {
287 | t.Fatalf("FormatErrorFn was not called when it should have been")
288 | }
289 | if !reflect.DeepEqual(result, expected) {
290 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
291 | }
292 | }
293 |
294 | func TestPlaygroundWithDefaultConfig(t *testing.T) {
295 | query := graphql.NewObject(graphql.ObjectConfig{
296 | Name: "Query",
297 | Fields: graphql.Fields{
298 | "ping": &graphql.Field{
299 | Name: "ping",
300 | Type: graphql.String,
301 | Resolve: func(p graphql.ResolveParams) (interface{}, error) {
302 | return "OK", nil
303 | },
304 | },
305 | },
306 | })
307 |
308 | schema, err := graphql.NewSchema(graphql.SchemaConfig{
309 | Query: query,
310 | })
311 | if err != nil {
312 | t.Fatal(err)
313 | }
314 |
315 | req, err := http.NewRequest("GET", "/graphql", nil)
316 | req.Header.Set("Accept", "text/html")
317 | if err != nil {
318 | t.Fatal(err)
319 | }
320 |
321 | h := handler.New(&handler.Config{
322 | Schema: &schema,
323 | Playground: true,
324 | })
325 |
326 | resp := httptest.NewRecorder()
327 | h.ContextHandler(context.Background(), resp, req)
328 |
329 | if resp.Code != http.StatusOK {
330 | t.Fatalf("unexpected server response %v", resp.Code)
331 | }
332 |
333 | expectedBodyContains := []string{
334 | "GraphQL Playground",
335 | `endpoint: "/graphql"`,
336 | `subscriptionEndpoint: "ws:///subscriptions"`,
337 | }
338 | respBody := resp.Body.String()
339 |
340 | for _, e := range expectedBodyContains {
341 | if !strings.Contains(respBody, e) {
342 | t.Fatalf("wrong body, expected %s to contain %s", respBody, e)
343 | }
344 | }
345 | }
346 |
347 | func TestPlaygroundWithCustomConfig(t *testing.T) {
348 | query := graphql.NewObject(graphql.ObjectConfig{
349 | Name: "Query",
350 | Fields: graphql.Fields{
351 | "ping": &graphql.Field{
352 | Name: "ping",
353 | Type: graphql.String,
354 | Resolve: func(p graphql.ResolveParams) (interface{}, error) {
355 | return "OK", nil
356 | },
357 | },
358 | },
359 | })
360 |
361 | schema, err := graphql.NewSchema(graphql.SchemaConfig{
362 | Query: query,
363 | })
364 | if err != nil {
365 | t.Fatal(err)
366 | }
367 |
368 | req, err := http.NewRequest("GET", "/custom-path/graphql", nil)
369 | req.Header.Set("Accept", "text/html")
370 | if err != nil {
371 | t.Fatal(err)
372 | }
373 |
374 | h := handler.New(&handler.Config{
375 | Schema: &schema,
376 | Playground: true,
377 | PlaygroundConfig: &handler.PlaygroundConfig{
378 | Endpoint: "/custom-path/graphql",
379 | SubscriptionEndpoint: "/custom-path/ws",
380 | },
381 | })
382 |
383 | resp := httptest.NewRecorder()
384 | h.ContextHandler(context.Background(), resp, req)
385 |
386 | if resp.Code != http.StatusOK {
387 | t.Fatalf("unexpected server response %v", resp.Code)
388 | }
389 |
390 | expectedBodyContains := []string{
391 | "GraphQL Playground",
392 | `endpoint: "/custom-path/graphql"`,
393 | `subscriptionEndpoint: "/custom-path/ws"`,
394 | }
395 | respBody := resp.Body.String()
396 |
397 | for _, e := range expectedBodyContains {
398 | if !strings.Contains(respBody, e) {
399 | t.Fatalf("wrong body, expected %s to contain %s", respBody, e)
400 | }
401 | }
402 | }
403 |
--------------------------------------------------------------------------------
/request_options_test.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/http"
7 | "net/url"
8 | "reflect"
9 | "testing"
10 |
11 | "github.com/graphql-go/graphql/testutil"
12 | )
13 |
14 | func TestRequestOptions_GET_BasicQueryString(t *testing.T) {
15 | queryString := "query=query RebelsShipsQuery { rebels { name } }"
16 | expected := &RequestOptions{
17 | Query: "query RebelsShipsQuery { rebels { name } }",
18 | Variables: make(map[string]interface{}),
19 | }
20 |
21 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
22 | result := NewRequestOptions(req)
23 |
24 | if !reflect.DeepEqual(result, expected) {
25 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
26 | }
27 | }
28 | func TestRequestOptions_GET_ContentTypeApplicationGraphQL(t *testing.T) {
29 | body := []byte(`query RebelsShipsQuery { rebels { name } }`)
30 | expected := &RequestOptions{}
31 |
32 | req, _ := http.NewRequest("GET", "/graphql", bytes.NewBuffer(body))
33 | req.Header.Add("Content-Type", "application/graphql")
34 | result := NewRequestOptions(req)
35 |
36 | if !reflect.DeepEqual(result, expected) {
37 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
38 | }
39 | }
40 | func TestRequestOptions_GET_ContentTypeApplicationJSON(t *testing.T) {
41 | body := `
42 | {
43 | "query": "query RebelsShipsQuery { rebels { name } }"
44 | }`
45 | expected := &RequestOptions{}
46 |
47 | req, _ := http.NewRequest("GET", "/graphql", bytes.NewBufferString(body))
48 | req.Header.Add("Content-Type", "application/json")
49 | result := NewRequestOptions(req)
50 |
51 | if !reflect.DeepEqual(result, expected) {
52 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
53 | }
54 | }
55 | func TestRequestOptions_GET_ContentTypeApplicationUrlEncoded(t *testing.T) {
56 | data := url.Values{}
57 | data.Add("query", "query RebelsShipsQuery { rebels { name } }")
58 |
59 | expected := &RequestOptions{}
60 |
61 | req, _ := http.NewRequest("GET", "/graphql", bytes.NewBufferString(data.Encode()))
62 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
63 | result := NewRequestOptions(req)
64 |
65 | if !reflect.DeepEqual(result, expected) {
66 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
67 | }
68 | }
69 |
70 | func TestRequestOptions_POST_BasicQueryString_WithNoBody(t *testing.T) {
71 | queryString := "query=query RebelsShipsQuery { rebels { name } }"
72 | expected := &RequestOptions{
73 | Query: "query RebelsShipsQuery { rebels { name } }",
74 | Variables: make(map[string]interface{}),
75 | }
76 |
77 | req, _ := http.NewRequest("POST", fmt.Sprintf("/graphql?%v", queryString), nil)
78 | result := NewRequestOptions(req)
79 |
80 | if !reflect.DeepEqual(result, expected) {
81 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
82 | }
83 | }
84 | func TestRequestOptions_POST_ContentTypeApplicationGraphQL(t *testing.T) {
85 | body := []byte(`query RebelsShipsQuery { rebels { name } }`)
86 | expected := &RequestOptions{
87 | Query: "query RebelsShipsQuery { rebels { name } }",
88 | }
89 |
90 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBuffer(body))
91 | req.Header.Add("Content-Type", "application/graphql")
92 | result := NewRequestOptions(req)
93 |
94 | if !reflect.DeepEqual(result, expected) {
95 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
96 | }
97 | }
98 | func TestRequestOptions_POST_ContentTypeApplicationGraphQL_WithNonGraphQLQueryContent(t *testing.T) {
99 | body := []byte(`not a graphql query`)
100 | expected := &RequestOptions{
101 | Query: "not a graphql query",
102 | }
103 |
104 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBuffer(body))
105 | req.Header.Add("Content-Type", "application/graphql")
106 | result := NewRequestOptions(req)
107 |
108 | if !reflect.DeepEqual(result, expected) {
109 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
110 | }
111 | }
112 | func TestRequestOptions_POST_ContentTypeApplicationGraphQL_EmptyBody(t *testing.T) {
113 | body := []byte(``)
114 | expected := &RequestOptions{
115 | Query: "",
116 | }
117 |
118 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBuffer(body))
119 | req.Header.Add("Content-Type", "application/graphql")
120 | result := NewRequestOptions(req)
121 |
122 | if !reflect.DeepEqual(result, expected) {
123 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
124 | }
125 | }
126 | func TestRequestOptions_POST_ContentTypeApplicationGraphQL_NilBody(t *testing.T) {
127 | expected := &RequestOptions{}
128 |
129 | req, _ := http.NewRequest("POST", "/graphql", nil)
130 | req.Header.Add("Content-Type", "application/graphql")
131 | result := NewRequestOptions(req)
132 |
133 | if !reflect.DeepEqual(result, expected) {
134 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
135 | }
136 | }
137 |
138 | func TestRequestOptions_POST_ContentTypeApplicationJSON(t *testing.T) {
139 | body := `
140 | {
141 | "query": "query RebelsShipsQuery { rebels { name } }"
142 | }`
143 | expected := &RequestOptions{
144 | Query: "query RebelsShipsQuery { rebels { name } }",
145 | }
146 |
147 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(body))
148 | req.Header.Add("Content-Type", "application/json")
149 | result := NewRequestOptions(req)
150 |
151 | if !reflect.DeepEqual(result, expected) {
152 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
153 | }
154 | }
155 |
156 | func TestRequestOptions_GET_WithVariablesAsObject(t *testing.T) {
157 | variables := url.QueryEscape(`{ "a": 1, "b": "2" }`)
158 | query := url.QueryEscape("query RebelsShipsQuery { rebels { name } }")
159 | queryString := fmt.Sprintf("query=%s&variables=%s", query, variables)
160 | expected := &RequestOptions{
161 | Query: "query RebelsShipsQuery { rebels { name } }",
162 | Variables: map[string]interface{}{
163 | "a": float64(1),
164 | "b": "2",
165 | },
166 | }
167 |
168 | req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
169 | result := NewRequestOptions(req)
170 |
171 | if !reflect.DeepEqual(result, expected) {
172 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
173 | }
174 | }
175 |
176 | func TestRequestOptions_POST_ContentTypeApplicationJSON_WithVariablesAsObject(t *testing.T) {
177 | body := `
178 | {
179 | "query": "query RebelsShipsQuery { rebels { name } }",
180 | "variables": { "a": 1, "b": "2" }
181 | }`
182 | expected := &RequestOptions{
183 | Query: "query RebelsShipsQuery { rebels { name } }",
184 | Variables: map[string]interface{}{
185 | "a": float64(1),
186 | "b": "2",
187 | },
188 | }
189 |
190 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(body))
191 | req.Header.Add("Content-Type", "application/json")
192 | result := NewRequestOptions(req)
193 |
194 | if !reflect.DeepEqual(result, expected) {
195 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
196 | }
197 | }
198 | func TestRequestOptions_POST_ContentTypeApplicationJSON_WithVariablesAsString(t *testing.T) {
199 | body := `
200 | {
201 | "query": "query RebelsShipsQuery { rebels { name } }",
202 | "variables": "{ \"a\": 1, \"b\": \"2\" }"
203 | }`
204 | expected := &RequestOptions{
205 | Query: "query RebelsShipsQuery { rebels { name } }",
206 | Variables: map[string]interface{}{
207 | "a": float64(1),
208 | "b": "2",
209 | },
210 | }
211 |
212 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(body))
213 | req.Header.Add("Content-Type", "application/json")
214 | result := NewRequestOptions(req)
215 |
216 | if !reflect.DeepEqual(result, expected) {
217 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
218 | }
219 | }
220 | func TestRequestOptions_POST_ContentTypeApplicationJSON_WithInvalidJSON(t *testing.T) {
221 | body := `INVALIDJSON{}`
222 | expected := &RequestOptions{}
223 |
224 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(body))
225 | req.Header.Add("Content-Type", "application/json")
226 | result := NewRequestOptions(req)
227 |
228 | if !reflect.DeepEqual(result, expected) {
229 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
230 | }
231 | }
232 | func TestRequestOptions_POST_ContentTypeApplicationJSON_WithNilBody(t *testing.T) {
233 | expected := &RequestOptions{}
234 |
235 | req, _ := http.NewRequest("POST", "/graphql", nil)
236 | req.Header.Add("Content-Type", "application/json")
237 | result := NewRequestOptions(req)
238 |
239 | if !reflect.DeepEqual(result, expected) {
240 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
241 | }
242 | }
243 |
244 | func TestRequestOptions_POST_ContentTypeApplicationUrlEncoded(t *testing.T) {
245 | data := url.Values{}
246 | data.Add("query", "query RebelsShipsQuery { rebels { name } }")
247 |
248 | expected := &RequestOptions{
249 | Query: "query RebelsShipsQuery { rebels { name } }",
250 | Variables: make(map[string]interface{}),
251 | }
252 |
253 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(data.Encode()))
254 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
255 | result := NewRequestOptions(req)
256 |
257 | if !reflect.DeepEqual(result, expected) {
258 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
259 | }
260 | }
261 | func TestRequestOptions_POST_ContentTypeApplicationUrlEncoded_WithInvalidData(t *testing.T) {
262 | data := "Invalid Data"
263 |
264 | expected := &RequestOptions{}
265 |
266 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(data))
267 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
268 | result := NewRequestOptions(req)
269 |
270 | if !reflect.DeepEqual(result, expected) {
271 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
272 | }
273 | }
274 | func TestRequestOptions_POST_ContentTypeApplicationUrlEncoded_WithNilBody(t *testing.T) {
275 |
276 | expected := &RequestOptions{}
277 |
278 | req, _ := http.NewRequest("POST", "/graphql", nil)
279 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
280 | result := NewRequestOptions(req)
281 |
282 | if !reflect.DeepEqual(result, expected) {
283 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
284 | }
285 | }
286 |
287 | func TestRequestOptions_PUT_BasicQueryString(t *testing.T) {
288 | queryString := "query=query RebelsShipsQuery { rebels { name } }"
289 | expected := &RequestOptions{
290 | Query: "query RebelsShipsQuery { rebels { name } }",
291 | Variables: make(map[string]interface{}),
292 | }
293 |
294 | req, _ := http.NewRequest("PUT", fmt.Sprintf("/graphql?%v", queryString), nil)
295 | result := NewRequestOptions(req)
296 |
297 | if !reflect.DeepEqual(result, expected) {
298 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
299 | }
300 | }
301 | func TestRequestOptions_PUT_ContentTypeApplicationGraphQL(t *testing.T) {
302 | body := []byte(`query RebelsShipsQuery { rebels { name } }`)
303 | expected := &RequestOptions{}
304 |
305 | req, _ := http.NewRequest("PUT", "/graphql", bytes.NewBuffer(body))
306 | req.Header.Add("Content-Type", "application/graphql")
307 | result := NewRequestOptions(req)
308 |
309 | if !reflect.DeepEqual(result, expected) {
310 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
311 | }
312 | }
313 | func TestRequestOptions_PUT_ContentTypeApplicationJSON(t *testing.T) {
314 | body := `
315 | {
316 | "query": "query RebelsShipsQuery { rebels { name } }"
317 | }`
318 | expected := &RequestOptions{}
319 |
320 | req, _ := http.NewRequest("PUT", "/graphql", bytes.NewBufferString(body))
321 | req.Header.Add("Content-Type", "application/json")
322 | result := NewRequestOptions(req)
323 |
324 | if !reflect.DeepEqual(result, expected) {
325 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
326 | }
327 | }
328 | func TestRequestOptions_PUT_ContentTypeApplicationUrlEncoded(t *testing.T) {
329 | data := url.Values{}
330 | data.Add("query", "query RebelsShipsQuery { rebels { name } }")
331 |
332 | expected := &RequestOptions{}
333 |
334 | req, _ := http.NewRequest("PUT", "/graphql", bytes.NewBufferString(data.Encode()))
335 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
336 | result := NewRequestOptions(req)
337 |
338 | if !reflect.DeepEqual(result, expected) {
339 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
340 | }
341 | }
342 |
343 | func TestRequestOptions_DELETE_BasicQueryString(t *testing.T) {
344 | queryString := "query=query RebelsShipsQuery { rebels { name } }"
345 | expected := &RequestOptions{
346 | Query: "query RebelsShipsQuery { rebels { name } }",
347 | Variables: make(map[string]interface{}),
348 | }
349 |
350 | req, _ := http.NewRequest("DELETE", fmt.Sprintf("/graphql?%v", queryString), nil)
351 | result := NewRequestOptions(req)
352 |
353 | if !reflect.DeepEqual(result, expected) {
354 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
355 | }
356 | }
357 | func TestRequestOptions_DELETE_ContentTypeApplicationGraphQL(t *testing.T) {
358 | body := []byte(`query RebelsShipsQuery { rebels { name } }`)
359 | expected := &RequestOptions{}
360 |
361 | req, _ := http.NewRequest("DELETE", "/graphql", bytes.NewBuffer(body))
362 | req.Header.Add("Content-Type", "application/graphql")
363 | result := NewRequestOptions(req)
364 |
365 | if !reflect.DeepEqual(result, expected) {
366 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
367 | }
368 | }
369 | func TestRequestOptions_DELETE_ContentTypeApplicationJSON(t *testing.T) {
370 | body := `
371 | {
372 | "query": "query RebelsShipsQuery { rebels { name } }"
373 | }`
374 | expected := &RequestOptions{}
375 |
376 | req, _ := http.NewRequest("DELETE", "/graphql", bytes.NewBufferString(body))
377 | req.Header.Add("Content-Type", "application/json")
378 | result := NewRequestOptions(req)
379 |
380 | if !reflect.DeepEqual(result, expected) {
381 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
382 | }
383 | }
384 | func TestRequestOptions_DELETE_ContentTypeApplicationUrlEncoded(t *testing.T) {
385 | data := url.Values{}
386 | data.Add("query", "query RebelsShipsQuery { rebels { name } }")
387 |
388 | expected := &RequestOptions{}
389 |
390 | req, _ := http.NewRequest("DELETE", "/graphql", bytes.NewBufferString(data.Encode()))
391 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
392 | result := NewRequestOptions(req)
393 |
394 | if !reflect.DeepEqual(result, expected) {
395 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
396 | }
397 | }
398 |
399 | func TestRequestOptions_POST_UnsupportedContentType(t *testing.T) {
400 | body := `query{}`
401 | expected := &RequestOptions{}
402 |
403 | req, _ := http.NewRequest("POST", "/graphql", bytes.NewBufferString(body))
404 | req.Header.Add("Content-Type", "application/xml")
405 | result := NewRequestOptions(req)
406 |
407 | if !reflect.DeepEqual(result, expected) {
408 | t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
409 | }
410 | }
411 |
--------------------------------------------------------------------------------