Skip to content

Commit 88a1da6

Browse files
committed
feat: Merging in 1.4.0 changes
1 parent cdf4d97 commit 88a1da6

4 files changed

Lines changed: 275 additions & 20 deletions

File tree

api/v1/graphql.go

Lines changed: 132 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"fmt"
1616
"net/http"
1717

18+
"github.com/krotik/common/lang/graphql/parser"
1819
"github.com/krotik/common/stringutil"
1920
"github.com/krotik/eliasdb/api"
2021
"github.com/krotik/eliasdb/graphql"
@@ -43,6 +44,8 @@ type graphQLEndpoint struct {
4344
HandlePOST handles GraphQL queries.
4445
*/
4546
func (e *graphQLEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
47+
var err error
48+
var res map[string]interface{}
4649

4750
dec := json.NewDecoder(r.Body)
4851
data := make(map[string]interface{})
@@ -52,28 +55,81 @@ func (e *graphQLEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, res
5255
return
5356
}
5457

55-
partData, ok := data["partition"]
56-
if !ok && len(resources) > 0 {
57-
partData = resources[0]
58-
ok = true
59-
}
60-
if !ok || partData == "" {
61-
http.Error(w, "Need a partition", http.StatusBadRequest)
58+
toAST, ok1 := data["query-to-ast"]
59+
toQuery, ok2 := data["ast-to-query"]
60+
if ok1 || ok2 {
61+
62+
res := make(map[string]interface{})
63+
64+
if ok1 {
65+
resast, err := parser.Parse("request", fmt.Sprint(toAST))
66+
67+
if err != nil {
68+
http.Error(w, err.Error(), http.StatusBadRequest)
69+
return
70+
}
71+
72+
res["result-ast"] = resast.Plain()
73+
}
74+
75+
if ok2 {
76+
astmap, ok := toQuery.(map[string]interface{})
77+
78+
if !ok {
79+
http.Error(w, "Plain AST object expected as 'ast-to-query' value", http.StatusBadRequest)
80+
return
81+
}
82+
83+
// Try to create a proper AST from plain AST
84+
85+
astnode, err := parser.ASTFromPlain(astmap)
86+
87+
if err != nil {
88+
http.Error(w, err.Error(), http.StatusBadRequest)
89+
return
90+
}
91+
92+
// Now pretty print the AST
93+
94+
ppres, err := parser.PrettyPrint(astnode)
95+
96+
if err != nil {
97+
http.Error(w, err.Error(), http.StatusBadRequest)
98+
return
99+
}
100+
101+
res["result-query"] = ppres
102+
}
103+
104+
w.Header().Set("content-type", "application/json; charset=utf-8")
105+
json.NewEncoder(w).Encode(res)
106+
62107
return
63-
}
64108

65-
part := fmt.Sprint(partData)
109+
} else {
110+
partData, ok := data["partition"]
111+
if !ok && len(resources) > 0 {
112+
partData = resources[0]
113+
ok = true
114+
}
115+
if !ok || partData == "" {
116+
http.Error(w, "Need a partition", http.StatusBadRequest)
117+
return
118+
}
66119

67-
if _, ok := data["variables"]; !ok {
68-
data["variables"] = nil
69-
}
120+
part := fmt.Sprint(partData)
70121

71-
if _, ok := data["operationName"]; !ok {
72-
data["operationName"] = nil
73-
}
122+
if _, ok := data["variables"]; !ok {
123+
data["variables"] = nil
124+
}
74125

75-
res, err := graphql.RunQuery(stringutil.CreateDisplayString(part)+" query",
76-
part, data, api.GM, nil, false)
126+
if _, ok := data["operationName"]; !ok {
127+
data["operationName"] = nil
128+
}
129+
130+
res, err = graphql.RunQuery(stringutil.CreateDisplayString(part)+" query",
131+
part, data, api.GM, nil, false)
132+
}
77133

78134
if err != nil {
79135
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -151,4 +207,63 @@ func (e *graphQLEndpoint) SwaggerDefs(s map[string]interface{}) {
151207
},
152208
},
153209
}
210+
211+
s["paths"].(map[string]interface{})["/v1/graphql"] = map[string]interface{}{
212+
"post": map[string]interface{}{
213+
"summary": "GraphQL parser and pretty printer endpoint.",
214+
"description": "The GraphQL endpoint without specifying a partition should be used to parse a given GraphQL query into an Abstract Syntax Tree or pretty print a given Abstract Syntax Tree into a GraphQL query.",
215+
"consumes": []string{
216+
"application/json",
217+
},
218+
"produces": []string{
219+
"text/plain",
220+
"application/json",
221+
},
222+
"parameters": []map[string]interface{}{
223+
{
224+
"name": "data",
225+
"in": "body",
226+
"description": "Query or AST which should be converted.",
227+
"required": true,
228+
"schema": map[string]interface{}{
229+
"type": "object",
230+
"properties": map[string]interface{}{
231+
"query-to-ast": map[string]interface{}{
232+
"description": "Query which should be parsed.",
233+
"type": "string",
234+
},
235+
"ast-to-query": map[string]interface{}{
236+
"description": "AST which should be pretty printed.",
237+
"type": "object",
238+
},
239+
},
240+
},
241+
},
242+
},
243+
"responses": map[string]interface{}{
244+
"200": map[string]interface{}{
245+
"description": "The operation was successful.",
246+
"schema": map[string]interface{}{
247+
"type": "object",
248+
"properties": map[string]interface{}{
249+
"result-ast": map[string]interface{}{
250+
"description": "The resulting AST if a query was parsed.",
251+
"type": "object",
252+
},
253+
"result-query": map[string]interface{}{
254+
"description": "The pretty printed query if an AST was given.",
255+
"type": "string",
256+
},
257+
},
258+
},
259+
},
260+
"default": map[string]interface{}{
261+
"description": "Error response",
262+
"schema": map[string]interface{}{
263+
"$ref": "#/definitions/Error",
264+
},
265+
},
266+
},
267+
},
268+
}
154269
}

api/v1/graphql_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,143 @@ func TestGraphQLErrors(t *testing.T) {
175175
return
176176
}
177177
}
178+
179+
func TestGraphQLParsing(t *testing.T) {
180+
queryURL := "http://localhost" + TESTPORT + EndpointGraphQL
181+
182+
q, err := json.Marshal(map[string]interface{}{
183+
"query-to-ast": `{
184+
Song
185+
}`,
186+
})
187+
errorutil.AssertOk(err)
188+
_, _, res := sendTestRequest(queryURL+"main", "POST", q)
189+
190+
if res != `
191+
{
192+
"result-ast": {
193+
"children": [
194+
{
195+
"children": [
196+
{
197+
"children": [
198+
{
199+
"children": [
200+
{
201+
"children": [
202+
{
203+
"name": "Name",
204+
"value": "Song"
205+
}
206+
],
207+
"name": "Field"
208+
}
209+
],
210+
"name": "SelectionSet"
211+
}
212+
],
213+
"name": "OperationDefinition"
214+
}
215+
],
216+
"name": "ExecutableDefinition"
217+
}
218+
],
219+
"name": "Document"
220+
}
221+
}`[1:] {
222+
t.Error("Unexpected response:", res)
223+
return
224+
}
225+
226+
_, _, res = sendTestRequest(queryURL+"main", "POST", []byte(`{"ast-to-query": {
227+
"children": [
228+
{
229+
"children": [
230+
{
231+
"children": [
232+
{
233+
"children": [
234+
{
235+
"children": [
236+
{
237+
"name": "Name",
238+
"value": "Song"
239+
}
240+
],
241+
"name": "Field"
242+
}
243+
],
244+
"name": "SelectionSet"
245+
}
246+
],
247+
"name": "OperationDefinition"
248+
}
249+
],
250+
"name": "ExecutableDefinition"
251+
}
252+
],
253+
"name": "Document"
254+
}}`))
255+
256+
if res != `
257+
{
258+
"result-query": "{\n Song\n}"
259+
}`[1:] {
260+
t.Error("Unexpected response:", res)
261+
return
262+
}
263+
}
264+
265+
func TestGraphQLParsingErrors(t *testing.T) {
266+
queryURL := "http://localhost" + TESTPORT + EndpointGraphQL
267+
268+
q, err := json.Marshal(map[string]interface{}{
269+
"query-to-ast": `{{
270+
Song
271+
}`,
272+
})
273+
errorutil.AssertOk(err)
274+
_, _, res := sendTestRequest(queryURL+"main", "POST", q)
275+
276+
if res != "Parse error in request: Name expected ({) (Line:1 Pos:2)" {
277+
t.Error("Unexpected response:", res)
278+
return
279+
}
280+
281+
q, err = json.Marshal(map[string]interface{}{
282+
"ast-to-query": `aaa`,
283+
})
284+
errorutil.AssertOk(err)
285+
_, _, res = sendTestRequest(queryURL+"main", "POST", q)
286+
287+
if res != "Plain AST object expected as 'ast-to-query' value" {
288+
t.Error("Unexpected response:", res)
289+
return
290+
}
291+
292+
q, err = json.Marshal(map[string]interface{}{
293+
"ast-to-query": map[string]interface{}{
294+
"foo": `Document`,
295+
},
296+
})
297+
errorutil.AssertOk(err)
298+
_, _, res = sendTestRequest(queryURL+"main", "POST", q)
299+
300+
if res != "Found plain ast node without a name: map[foo:Document]" {
301+
t.Error("Unexpected response:", res)
302+
return
303+
}
304+
305+
q, err = json.Marshal(map[string]interface{}{
306+
"ast-to-query": map[string]interface{}{
307+
"name": `foo`,
308+
},
309+
})
310+
errorutil.AssertOk(err)
311+
_, _, res = sendTestRequest(queryURL+"main", "POST", q)
312+
313+
if res != "Could not find template for foo (tempkey: foo)" {
314+
t.Error("Unexpected response:", res)
315+
return
316+
}
317+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ go 1.12
44

55
require (
66
github.com/gorilla/websocket v1.4.1
7-
github.com/krotik/common v1.4.6
7+
github.com/krotik/common v1.5.1
88
github.com/krotik/ecal v1.6.3
99
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
22
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
33
github.com/krotik/common v1.4.4/go.mod h1:Ti5yTPm8lyOwgllpNNc0bFutiZ3nRu49QbSQCbjEaB0=
4-
github.com/krotik/common v1.4.6 h1:0ZEofm4oS0370JeXCm+XteqTpS8BIFetUkYr1eTQDG8=
5-
github.com/krotik/common v1.4.6/go.mod h1:Ti5yTPm8lyOwgllpNNc0bFutiZ3nRu49QbSQCbjEaB0=
4+
github.com/krotik/common v1.5.1 h1:WkX2ewJjm4gma1wg34tDvaR4PM9FhIgfD1mvoHGoK4M=
5+
github.com/krotik/common v1.5.1/go.mod h1:Ti5yTPm8lyOwgllpNNc0bFutiZ3nRu49QbSQCbjEaB0=
66
github.com/krotik/ecal v1.6.3 h1:HKJPB6Y3uCEcVNpSZsm2Jfo8RRgfBBS8HR69yMBZJ20=
77
github.com/krotik/ecal v1.6.3/go.mod h1:ULSgiGqiCxGtJKicRQKW8Unt6CeeCSYQ4i7fDdRFf3Q=

0 commit comments

Comments
 (0)