1package query
2
3import (
4 "fmt"
5 "strings"
6 "text/scanner"
7
8 "github.com/vektah/gqlgen/neelance/common"
9 "github.com/vektah/gqlgen/neelance/errors"
10)
11
12type Document struct {
13 Operations OperationList
14 Fragments FragmentList
15}
16
17type OperationList []*Operation
18
19func (l OperationList) Get(name string) *Operation {
20 for _, f := range l {
21 if f.Name.Name == name {
22 return f
23 }
24 }
25 return nil
26}
27
28type FragmentList []*FragmentDecl
29
30func (l FragmentList) Get(name string) *FragmentDecl {
31 for _, f := range l {
32 if f.Name.Name == name {
33 return f
34 }
35 }
36 return nil
37}
38
39type Operation struct {
40 Type OperationType
41 Name common.Ident
42 Vars common.InputValueList
43 Selections []Selection
44 Directives common.DirectiveList
45 Loc errors.Location
46}
47
48type OperationType string
49
50const (
51 Query OperationType = "QUERY"
52 Mutation = "MUTATION"
53 Subscription = "SUBSCRIPTION"
54)
55
56type Fragment struct {
57 On common.TypeName
58 Selections []Selection
59}
60
61type FragmentDecl struct {
62 Fragment
63 Name common.Ident
64 Directives common.DirectiveList
65 Loc errors.Location
66}
67
68type Selection interface {
69 isSelection()
70}
71
72type Field struct {
73 Alias common.Ident
74 Name common.Ident
75 Arguments common.ArgumentList
76 Directives common.DirectiveList
77 Selections []Selection
78 SelectionSetLoc errors.Location
79}
80
81type InlineFragment struct {
82 Fragment
83 Directives common.DirectiveList
84 Loc errors.Location
85}
86
87type FragmentSpread struct {
88 Name common.Ident
89 Directives common.DirectiveList
90 Loc errors.Location
91}
92
93func (Field) isSelection() {}
94func (InlineFragment) isSelection() {}
95func (FragmentSpread) isSelection() {}
96
97func Parse(queryString string) (*Document, *errors.QueryError) {
98 sc := &scanner.Scanner{
99 Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
100 }
101 sc.Init(strings.NewReader(queryString))
102
103 l := common.New(sc)
104 var doc *Document
105 err := l.CatchSyntaxError(func() {
106 doc = parseDocument(l)
107 })
108 if err != nil {
109 return nil, err
110 }
111
112 return doc, nil
113}
114
115func parseDocument(l *common.Lexer) *Document {
116 d := &Document{}
117 for l.Peek() != scanner.EOF {
118 if l.Peek() == '{' {
119 op := &Operation{Type: Query, Loc: l.Location()}
120 op.Selections = parseSelectionSet(l)
121 d.Operations = append(d.Operations, op)
122 continue
123 }
124
125 loc := l.Location()
126 switch x := l.ConsumeIdent(); x {
127 case "query":
128 op := parseOperation(l, Query)
129 op.Loc = loc
130 d.Operations = append(d.Operations, op)
131
132 case "mutation":
133 d.Operations = append(d.Operations, parseOperation(l, Mutation))
134
135 case "subscription":
136 d.Operations = append(d.Operations, parseOperation(l, Subscription))
137
138 case "fragment":
139 frag := parseFragment(l)
140 frag.Loc = loc
141 d.Fragments = append(d.Fragments, frag)
142
143 default:
144 l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x))
145 }
146 }
147 return d
148}
149
150func parseOperation(l *common.Lexer, opType OperationType) *Operation {
151 op := &Operation{Type: opType}
152 op.Name.Loc = l.Location()
153 if l.Peek() == scanner.Ident {
154 op.Name = l.ConsumeIdentWithLoc()
155 }
156 op.Directives = common.ParseDirectives(l)
157 if l.Peek() == '(' {
158 l.ConsumeToken('(')
159 for l.Peek() != ')' {
160 loc := l.Location()
161 l.ConsumeToken('$')
162 iv := common.ParseInputValue(l)
163 iv.Loc = loc
164 op.Vars = append(op.Vars, iv)
165 }
166 l.ConsumeToken(')')
167 }
168 op.Selections = parseSelectionSet(l)
169 return op
170}
171
172func parseFragment(l *common.Lexer) *FragmentDecl {
173 f := &FragmentDecl{}
174 f.Name = l.ConsumeIdentWithLoc()
175 l.ConsumeKeyword("on")
176 f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
177 f.Directives = common.ParseDirectives(l)
178 f.Selections = parseSelectionSet(l)
179 return f
180}
181
182func parseSelectionSet(l *common.Lexer) []Selection {
183 var sels []Selection
184 l.ConsumeToken('{')
185 for l.Peek() != '}' {
186 sels = append(sels, parseSelection(l))
187 }
188 l.ConsumeToken('}')
189 return sels
190}
191
192func parseSelection(l *common.Lexer) Selection {
193 if l.Peek() == '.' {
194 return parseSpread(l)
195 }
196 return parseField(l)
197}
198
199func parseField(l *common.Lexer) *Field {
200 f := &Field{}
201 f.Alias = l.ConsumeIdentWithLoc()
202 f.Name = f.Alias
203 if l.Peek() == ':' {
204 l.ConsumeToken(':')
205 f.Name = l.ConsumeIdentWithLoc()
206 }
207 if l.Peek() == '(' {
208 f.Arguments = common.ParseArguments(l)
209 }
210 f.Directives = common.ParseDirectives(l)
211 if l.Peek() == '{' {
212 f.SelectionSetLoc = l.Location()
213 f.Selections = parseSelectionSet(l)
214 }
215 return f
216}
217
218func parseSpread(l *common.Lexer) Selection {
219 loc := l.Location()
220 l.ConsumeToken('.')
221 l.ConsumeToken('.')
222 l.ConsumeToken('.')
223
224 f := &InlineFragment{Loc: loc}
225 if l.Peek() == scanner.Ident {
226 ident := l.ConsumeIdentWithLoc()
227 if ident.Name != "on" {
228 fs := &FragmentSpread{
229 Name: ident,
230 Loc: loc,
231 }
232 fs.Directives = common.ParseDirectives(l)
233 return fs
234 }
235 f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
236 }
237 f.Directives = common.ParseDirectives(l)
238 f.Selections = parseSelectionSet(l)
239 return f
240}
241
242func (d *Document) GetOperation(operationName string) (*Operation, error) {
243 if len(d.Operations) == 0 {
244 return nil, fmt.Errorf("no operations in query document")
245 }
246
247 if operationName == "" {
248 if len(d.Operations) > 1 {
249 return nil, fmt.Errorf("more than one operation in query document and no operation name given")
250 }
251 for _, op := range d.Operations {
252 return op, nil // return the one and only operation
253 }
254 }
255
256 op := d.Operations.Get(operationName)
257 if op == nil {
258 return nil, fmt.Errorf("no operation with name %q", operationName)
259 }
260 return op, nil
261}