@@ -17,7 +17,7 @@ type QueryPOSTBody struct {
17
17
OperationName string `json:"operationName"`
18
18
}
19
19
20
- func writeErrors ( err error , w http. ResponseWriter ) {
20
+ func formatErrors ( data map [ string ] interface {}, err error ) map [ string ] interface {} {
21
21
// the final list of formatted errors
22
22
var errList graphql.ErrorList
23
23
@@ -32,34 +32,36 @@ func writeErrors(err error, w http.ResponseWriter) {
32
32
}
33
33
}
34
34
35
- response , err := json .Marshal (map [string ]interface {}{
35
+ return map [string ]interface {}{
36
+ "data" : data ,
36
37
"errors" : errList ,
37
- })
38
- if err != nil {
39
- w .WriteHeader (http .StatusInternalServerError )
40
- writeErrors (err , w )
41
- return
42
38
}
43
-
44
- w .Write (response )
45
39
}
46
40
47
41
// GraphQLHandler returns a http.HandlerFunc that should be used as the
48
42
// primary endpoint for the gateway API. The endpoint will respond
49
- // to queries on both GET and POST requests.
43
+ // to queries on both GET and POST requests. POST requests can either be
44
+ // a single object with { query, variables, operationName } or a list
45
+ // of that object.
50
46
func (g * Gateway ) GraphQLHandler (w http.ResponseWriter , r * http.Request ) {
51
- // a place to store query params
52
- payload := QueryPOSTBody {}
47
+ // this handler can handle multiple operations sent in the same query. Internally,
48
+ // it modules a single operation as a list of one.
49
+ operations := []* QueryPOSTBody {}
53
50
54
51
// the error we have encountered when extracting query input
55
52
var payloadErr error
53
+ // make our lives easier. track if we're in batch mode
54
+ batchMode := false
56
55
57
56
// if we got a GET request
58
57
if r .Method == http .MethodGet {
59
58
parameters := r .URL .Query ()
60
59
// get the query parameter
61
60
if query , ok := parameters ["query" ]; ok {
62
- payload .Query = query [0 ]
61
+ // build a query obj
62
+ query := & QueryPOSTBody {
63
+ Query : query [0 ],
64
+ }
63
65
64
66
// include operationName
65
67
if variableInput , ok := parameters ["variables" ]; ok {
@@ -71,13 +73,17 @@ func (g *Gateway) GraphQLHandler(w http.ResponseWriter, r *http.Request) {
71
73
}
72
74
73
75
// assign the variables to the payload
74
- payload .Variables = variables
76
+ query .Variables = variables
75
77
}
76
78
77
79
// include operationName
78
80
if operationName , ok := parameters ["operationName" ]; ok {
79
- payload .OperationName = operationName [0 ]
81
+ query .OperationName = operationName [0 ]
80
82
}
83
+
84
+ // add the query to the list of operations
85
+ operations = append (operations , query )
86
+
81
87
} else {
82
88
// there was no query parameter
83
89
payloadErr = errors .New ("must include query as parameter" )
@@ -90,43 +96,91 @@ func (g *Gateway) GraphQLHandler(w http.ResponseWriter, r *http.Request) {
90
96
payloadErr = fmt .Errorf ("encountered error reading body: %s" , err .Error ())
91
97
}
92
98
93
- err = json .Unmarshal (body , & payload )
94
- if err != nil {
95
- payloadErr = fmt .Errorf ("encountered error parsing body: %s" , err .Error ())
99
+ // there are two possible options for receiving information from a post request
100
+ // the first is that the user provides an object in the form of { query, variables, operationName }
101
+ // the second option is a list of that object
102
+
103
+ singleQuery := & QueryPOSTBody {}
104
+ // if we were given a single object
105
+ if err = json .Unmarshal (body , & singleQuery ); err == nil {
106
+ // add it to the list of operations
107
+ operations = append (operations , singleQuery )
108
+ // we weren't given an object
109
+ } else {
110
+ // but we could have been given a list
111
+ batch := []* QueryPOSTBody {}
112
+
113
+ if err = json .Unmarshal (body , & batch ); err != nil {
114
+ payloadErr = fmt .Errorf ("encountered error parsing body: %s" , err .Error ())
115
+ } else {
116
+ operations = batch
117
+ }
118
+
119
+ // we're in batch mode
120
+ batchMode = true
96
121
}
97
122
}
98
123
99
124
// if there was an error retrieving the payload
100
125
if payloadErr != nil {
126
+ // stringify the response
127
+ response , _ := json .Marshal (formatErrors (map [string ]interface {}{}, payloadErr ))
128
+
129
+ // send the error to the user
101
130
w .WriteHeader (http .StatusUnprocessableEntity )
102
- writeErrors ( payloadErr , w )
131
+ w . Write ( response )
103
132
return
104
133
}
105
134
106
- // if we dont have a query
107
- if payload .Query == "" {
108
- w .WriteHeader (http .StatusUnprocessableEntity )
109
- writeErrors (errors .New ("could not find a query in request payload" ), w )
110
- return
135
+ // we have to respond to each operation in the right order
136
+ results := []map [string ]interface {}{}
137
+
138
+ // the status code to report
139
+ statusCode := http .StatusOK
140
+
141
+ for _ , operation := range operations {
142
+ // the result of the operation
143
+ result := map [string ]interface {}{}
144
+
145
+ // the result of the operation
146
+ if operation .Query == "" {
147
+ statusCode = http .StatusUnprocessableEntity
148
+ results = append (results , formatErrors (map [string ]interface {}{}, errors .New ("could not find query body" )))
149
+ continue
150
+ }
151
+
152
+ // fire the query with the request context passed through to execution
153
+ result , err := g .Execute (r .Context (), operation .Query , operation .Variables )
154
+ if err != nil {
155
+ results = append (results , formatErrors (map [string ]interface {}{}, err ))
156
+ continue
157
+ }
158
+
159
+ // add this result to the list
160
+ results = append (results , map [string ]interface {}{"data" : result })
111
161
}
112
162
113
- // fire the query with the request context passed through to execution
114
- result , err := g .Execute (r .Context (), payload .Query , payload .Variables )
115
- if err != nil {
116
- writeErrors (err , w )
117
- return
163
+ // the final result depends on whether we are executing in batch mode or not
164
+ var finalResponse interface {}
165
+ if batchMode {
166
+ finalResponse = results
167
+ } else {
168
+ finalResponse = results [0 ]
118
169
}
119
170
120
- response , err := json .Marshal (map [string ]interface {}{
121
- "data" : result ,
122
- })
171
+ // serialized the response
172
+ response , err := json .Marshal (finalResponse )
123
173
if err != nil {
124
- w .WriteHeader (http .StatusInternalServerError )
125
- writeErrors (err , w )
126
- return
174
+ // if we couldn't serialize the response then we're in internal error territory
175
+ statusCode = http .StatusInternalServerError
176
+ response , err = json .Marshal (formatErrors (map [string ]interface {}{}, err ))
177
+ if err != nil {
178
+ response , _ = json .Marshal (formatErrors (map [string ]interface {}{}, err ))
179
+ }
127
180
}
128
181
129
182
// send the result to the user
183
+ w .WriteHeader (statusCode )
130
184
fmt .Fprint (w , string (response ))
131
185
}
132
186
0 commit comments