@@ -3,6 +3,7 @@ package graphql
3
3
import (
4
4
"bytes"
5
5
"encoding/json"
6
+ "fmt"
6
7
"io"
7
8
"reflect"
8
9
"sort"
@@ -13,7 +14,7 @@ import (
13
14
func constructQuery (v interface {}, variables map [string ]interface {}, name string ) string {
14
15
query := query (v )
15
16
if len (variables ) > 0 {
16
- return "query " + name + "(" + queryArguments (variables ) + ")" + query
17
+ return "query " + name + "(" + queryArguments (variables , false ) + ")" + query
17
18
}
18
19
19
20
if name != "" {
@@ -25,7 +26,7 @@ func constructQuery(v interface{}, variables map[string]interface{}, name string
25
26
func constructMutation (v interface {}, variables map [string ]interface {}, name string ) string {
26
27
query := query (v )
27
28
if len (variables ) > 0 {
28
- return "mutation " + name + "(" + queryArguments (variables ) + ")" + query
29
+ return "mutation " + name + "(" + queryArguments (variables , true ) + ")" + query
29
30
}
30
31
if name != "" {
31
32
return "mutation " + name + query
@@ -36,7 +37,7 @@ func constructMutation(v interface{}, variables map[string]interface{}, name str
36
37
func constructSubscription (v interface {}, variables map [string ]interface {}, name string ) string {
37
38
query := query (v )
38
39
if len (variables ) > 0 {
39
- return "subscription " + name + "(" + queryArguments (variables ) + ")" + query
40
+ return "subscription " + name + "(" + queryArguments (variables , false ) + ")" + query
40
41
}
41
42
if name != "" {
42
43
return "subscription " + name + query
@@ -47,7 +48,7 @@ func constructSubscription(v interface{}, variables map[string]interface{}, name
47
48
// queryArguments constructs a minified arguments string for variables.
48
49
//
49
50
// E.g., map[string]interface{}{"a": Int(123), "b": NewBoolean(true)} -> "$a:Int!$b:Boolean".
50
- func queryArguments (variables map [string ]interface {}) string {
51
+ func queryArguments (variables map [string ]interface {}, isMutation bool ) string {
51
52
// Sort keys in order to produce deterministic output for testing purposes.
52
53
// TODO: If tests can be made to work with non-deterministic output, then no need to sort.
53
54
keys := make ([]string , 0 , len (variables ))
@@ -61,7 +62,7 @@ func queryArguments(variables map[string]interface{}) string {
61
62
io .WriteString (& buf , "$" )
62
63
io .WriteString (& buf , k )
63
64
io .WriteString (& buf , ":" )
64
- writeArgumentType (& buf , reflect .TypeOf (variables [k ]), true )
65
+ writeArgumentType (& buf , reflect .TypeOf (variables [k ]), true , isMutation )
65
66
// Don't insert a comma here.
66
67
// Commas in GraphQL are insignificant, and we want minified output.
67
68
// See https://facebook.github.io/graphql/October2016/#sec-Insignificant-Commas.
@@ -72,18 +73,21 @@ func queryArguments(variables map[string]interface{}) string {
72
73
// writeArgumentType writes a minified GraphQL type for t to w.
73
74
// value indicates whether t is a value (required) type or pointer (optional) type.
74
75
// If value is true, then "!" is written at the end of t.
75
- func writeArgumentType (w io.Writer , t reflect.Type , value bool ) {
76
- if t .Kind () == reflect .Ptr {
76
+ func writeArgumentType (w io.Writer , t reflect.Type , value , isMutation bool ) {
77
+ if t .Kind () == reflect .Ptr && ! isMutation {
77
78
// Pointer is an optional type, so no "!" at the end of the pointer's underlying type.
78
- writeArgumentType (w , t .Elem (), false )
79
+ writeArgumentType (w , t .Elem (), false , isMutation )
80
+ return
81
+ } else if t .Kind () == reflect .Ptr {
82
+ writeArgumentType (w , t .Elem (), true , isMutation )
79
83
return
80
84
}
81
85
82
86
switch t .Kind () {
83
87
case reflect .Slice , reflect .Array :
84
88
// List. E.g., "[Int]".
85
89
io .WriteString (w , "[" )
86
- writeArgumentType (w , t .Elem (), true )
90
+ writeArgumentType (w , t .Elem (), true , isMutation )
87
91
io .WriteString (w , "]" )
88
92
default :
89
93
// Named type. E.g., "Int".
@@ -117,16 +121,19 @@ func writeArgumentType(w io.Writer, t reflect.Type, value bool) {
117
121
// E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}".
118
122
func query (v interface {}) string {
119
123
var buf bytes.Buffer
120
- writeQuery (& buf , reflect .TypeOf (v ), false )
124
+ seen := make (map [string ]struct {})
125
+ writeQuery (& buf , reflect .TypeOf (v ), false , "" )
126
+ fmt .Println (seen )
121
127
return buf .String ()
122
128
}
123
129
124
130
// writeQuery writes a minified query for t to w.
125
131
// If inline is true, the struct fields of t are inlined into parent struct.
126
- func writeQuery (w io.Writer , t reflect.Type , inline bool ) {
132
+ // Seen is used to stop infinite loops
133
+ func writeQuery (w io.Writer , t reflect.Type , inline bool , inverseName string ) {
127
134
switch t .Kind () {
128
135
case reflect .Ptr , reflect .Slice :
129
- writeQuery (w , t .Elem (), false )
136
+ writeQuery (w , t .Elem (), false , inverseName )
130
137
case reflect .Struct :
131
138
// If the type implements json.Unmarshaler, it's a scalar. Don't expand it.
132
139
if reflect .PtrTo (t ).Implements (jsonUnmarshaler ) {
@@ -136,20 +143,29 @@ func writeQuery(w io.Writer, t reflect.Type, inline bool) {
136
143
io .WriteString (w , "{" )
137
144
}
138
145
for i := 0 ; i < t .NumField (); i ++ {
146
+ f := t .Field (i )
147
+ value , ok := f .Tag .Lookup ("graphql" )
148
+ if value == "-" {
149
+ //skip (this is 'omit')
150
+ continue
151
+ } else if value == "" {
152
+ value = ident .ParseMixedCaps (f .Name ).ToLowerCamelCase ()
153
+ }
154
+ if inverseName == value {
155
+ continue //Don't allow recursion
156
+ }
157
+ thisInverseName , _ := f .Tag .Lookup ("hasInverse" )
158
+ // if thisInverseName != "" {
159
+ // fmt.Println()
160
+ // }
139
161
if i != 0 {
140
162
io .WriteString (w , "," )
141
163
}
142
- f := t .Field (i )
143
- value , ok := f .Tag .Lookup ("graphql" )
144
164
inlineField := f .Anonymous && ! ok
145
165
if ! inlineField {
146
- if ok {
147
- io .WriteString (w , value )
148
- } else {
149
- io .WriteString (w , ident .ParseMixedCaps (f .Name ).ToLowerCamelCase ())
150
- }
166
+ io .WriteString (w , value )
151
167
}
152
- writeQuery (w , f .Type , inlineField )
168
+ writeQuery (w , f .Type , inlineField , thisInverseName )
153
169
}
154
170
if ! inline {
155
171
io .WriteString (w , "}" )
0 commit comments