1
2
3 package validator
4
5 import (
6 "strconv"
7 "strings"
8
9 . "github.com/vektah/gqlparser/ast"
10 "github.com/vektah/gqlparser/gqlerror"
11 "github.com/vektah/gqlparser/parser"
12 )
13
14 func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) {
15 ast, err := parser.ParseSchemas(inputs...)
16 if err != nil {
17 return nil, err
18 }
19 return ValidateSchemaDocument(ast)
20 }
21
22 func ValidateSchemaDocument(ast *SchemaDocument) (*Schema, *gqlerror.Error) {
23 schema := Schema{
24 Types: map[string]*Definition{},
25 Directives: map[string]*DirectiveDefinition{},
26 PossibleTypes: map[string][]*Definition{},
27 Implements: map[string][]*Definition{},
28 }
29
30 for i, def := range ast.Definitions {
31 if schema.Types[def.Name] != nil {
32 return nil, gqlerror.ErrorPosf(def.Position, "Cannot redeclare type %s.", def.Name)
33 }
34 schema.Types[def.Name] = ast.Definitions[i]
35 }
36
37 defs := append(DefinitionList{}, ast.Definitions...)
38
39 for _, ext := range ast.Extensions {
40 def := schema.Types[ext.Name]
41 if def == nil {
42 schema.Types[ext.Name] = &Definition{
43 Kind: ext.Kind,
44 Name: ext.Name,
45 Position: ext.Position,
46 }
47 def = schema.Types[ext.Name]
48 defs = append(defs, def)
49 }
50
51 if def.Kind != ext.Kind {
52 return nil, gqlerror.ErrorPosf(ext.Position, "Cannot extend type %s because the base type is a %s, not %s.", ext.Name, def.Kind, ext.Kind)
53 }
54
55 def.Directives = append(def.Directives, ext.Directives...)
56 def.Interfaces = append(def.Interfaces, ext.Interfaces...)
57 def.Fields = append(def.Fields, ext.Fields...)
58 def.Types = append(def.Types, ext.Types...)
59 def.EnumValues = append(def.EnumValues, ext.EnumValues...)
60 }
61
62 for _, def := range defs {
63 switch def.Kind {
64 case Union:
65 for _, t := range def.Types {
66 schema.AddPossibleType(def.Name, schema.Types[t])
67 schema.AddImplements(t, def)
68 }
69 case InputObject, Object:
70 for _, intf := range def.Interfaces {
71 schema.AddPossibleType(intf, def)
72 schema.AddImplements(def.Name, schema.Types[intf])
73 }
74 schema.AddPossibleType(def.Name, def)
75 }
76 }
77
78 for i, dir := range ast.Directives {
79 if schema.Directives[dir.Name] != nil {
80 return nil, gqlerror.ErrorPosf(dir.Position, "Cannot redeclare directive %s.", dir.Name)
81 }
82 schema.Directives[dir.Name] = ast.Directives[i]
83 }
84
85 if len(ast.Schema) > 1 {
86 return nil, gqlerror.ErrorPosf(ast.Schema[1].Position, "Cannot have multiple schema entry points, consider schema extensions instead.")
87 }
88
89 if len(ast.Schema) == 1 {
90 for _, entrypoint := range ast.Schema[0].OperationTypes {
91 def := schema.Types[entrypoint.Type]
92 if def == nil {
93 return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
94 }
95 switch entrypoint.Operation {
96 case Query:
97 schema.Query = def
98 case Mutation:
99 schema.Mutation = def
100 case Subscription:
101 schema.Subscription = def
102 }
103 }
104 }
105
106 for _, ext := range ast.SchemaExtension {
107 for _, entrypoint := range ext.OperationTypes {
108 def := schema.Types[entrypoint.Type]
109 if def == nil {
110 return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
111 }
112 switch entrypoint.Operation {
113 case Query:
114 schema.Query = def
115 case Mutation:
116 schema.Mutation = def
117 case Subscription:
118 schema.Subscription = def
119 }
120 }
121 }
122
123 for _, typ := range schema.Types {
124 err := validateDefinition(&schema, typ)
125 if err != nil {
126 return nil, err
127 }
128 }
129
130 for _, dir := range schema.Directives {
131 err := validateDirective(&schema, dir)
132 if err != nil {
133 return nil, err
134 }
135 }
136
137 if schema.Query == nil && schema.Types["Query"] != nil {
138 schema.Query = schema.Types["Query"]
139 }
140
141 if schema.Mutation == nil && schema.Types["Mutation"] != nil {
142 schema.Mutation = schema.Types["Mutation"]
143 }
144
145 if schema.Subscription == nil && schema.Types["Subscription"] != nil {
146 schema.Subscription = schema.Types["Subscription"]
147 }
148
149 if schema.Query != nil {
150 schema.Query.Fields = append(
151 schema.Query.Fields,
152 &FieldDefinition{
153 Name: "__schema",
154 Type: NonNullNamedType("__Schema", nil),
155 },
156 &FieldDefinition{
157 Name: "__type",
158 Type: NonNullNamedType("__Type", nil),
159 Arguments: ArgumentDefinitionList{
160 {Name: "name", Type: NamedType("String", nil)},
161 },
162 },
163 )
164 }
165
166 return &schema, nil
167 }
168
169 func validateDirective(schema *Schema, def *DirectiveDefinition) *gqlerror.Error {
170 if err := validateName(def.Position, def.Name); err != nil {
171
172 return err
173 }
174
175 return validateArgs(schema, def.Arguments, def)
176 }
177
178 func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
179 for _, field := range def.Fields {
180 if err := validateName(field.Position, field.Name); err != nil {
181
182 return err
183 }
184 if err := validateTypeRef(schema, field.Type); err != nil {
185 return err
186 }
187 if err := validateArgs(schema, field.Arguments, nil); err != nil {
188 return err
189 }
190 if err := validateDirectives(schema, field.Directives, nil); err != nil {
191 return err
192 }
193 }
194
195 for _, typ := range def.Types {
196 typDef := schema.Types[typ]
197 if typDef == nil {
198 return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(typ))
199 }
200 if !isValidKind(typDef.Kind, Object) {
201 return gqlerror.ErrorPosf(def.Position, "%s type %s must be %s.", def.Kind, strconv.Quote(typ), kindList(Object))
202 }
203 }
204
205 for _, intf := range def.Interfaces {
206 if err := validateImplements(schema, def, intf); err != nil {
207 return err
208 }
209 }
210
211 switch def.Kind {
212 case Object, Interface:
213 if len(def.Fields) == 0 {
214 return gqlerror.ErrorPosf(def.Position, "%s must define one or more fields.", def.Kind)
215 }
216 for _, field := range def.Fields {
217 if typ, ok := schema.Types[field.Type.Name()]; ok {
218 if !isValidKind(typ.Kind, Scalar, Object, Interface, Union, Enum) {
219 return gqlerror.ErrorPosf(field.Position, "%s field must be one of %s.", def.Kind, kindList(Scalar, Object, Interface, Union, Enum))
220 }
221 }
222 }
223 case Enum:
224 if len(def.EnumValues) == 0 {
225 return gqlerror.ErrorPosf(def.Position, "%s must define one or more unique enum values.", def.Kind)
226 }
227 case InputObject:
228 if len(def.Fields) == 0 {
229 return gqlerror.ErrorPosf(def.Position, "%s must define one or more input fields.", def.Kind)
230 }
231 for _, field := range def.Fields {
232 if typ, ok := schema.Types[field.Type.Name()]; ok {
233 if !isValidKind(typ.Kind, Scalar, Enum, InputObject) {
234 return gqlerror.ErrorPosf(field.Position, "%s field must be one of %s.", def.Kind, kindList(Scalar, Enum, InputObject))
235 }
236 }
237 }
238 }
239
240 for idx, field1 := range def.Fields {
241 for _, field2 := range def.Fields[idx+1:] {
242 if field1.Name == field2.Name {
243 return gqlerror.ErrorPosf(field2.Position, "Field %s.%s can only be defined once.", def.Name, field2.Name)
244 }
245 }
246 }
247
248 if !def.BuiltIn {
249
250 err := validateName(def.Position, def.Name)
251 if err != nil {
252 return err
253 }
254 }
255
256 return validateDirectives(schema, def.Directives, nil)
257 }
258
259 func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error {
260 if schema.Types[typ.Name()] == nil {
261 return gqlerror.ErrorPosf(typ.Position, "Undefined type %s.", typ.Name())
262 }
263 return nil
264 }
265
266 func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective *DirectiveDefinition) *gqlerror.Error {
267 for _, arg := range args {
268 if err := validateName(arg.Position, arg.Name); err != nil {
269
270 return err
271 }
272 if err := validateTypeRef(schema, arg.Type); err != nil {
273 return err
274 }
275 def := schema.Types[arg.Type.Name()]
276 if !def.IsInputType() {
277 return gqlerror.ErrorPosf(
278 arg.Position,
279 "cannot use %s as argument %s because %s is not a valid input type",
280 arg.Type.String(),
281 arg.Name,
282 def.Kind,
283 )
284 }
285 if err := validateDirectives(schema, arg.Directives, currentDirective); err != nil {
286 return err
287 }
288 }
289 return nil
290 }
291
292 func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *DirectiveDefinition) *gqlerror.Error {
293 for _, dir := range dirs {
294 if err := validateName(dir.Position, dir.Name); err != nil {
295
296 return err
297 }
298 if currentDirective != nil && dir.Name == currentDirective.Name {
299 return gqlerror.ErrorPosf(dir.Position, "Directive %s cannot refer to itself.", currentDirective.Name)
300 }
301 if schema.Directives[dir.Name] == nil {
302 return gqlerror.ErrorPosf(dir.Position, "Undefined directive %s.", dir.Name)
303 }
304 dir.Definition = schema.Directives[dir.Name]
305 }
306 return nil
307 }
308
309 func validateImplements(schema *Schema, def *Definition, intfName string) *gqlerror.Error {
310
311
312 intf := schema.Types[intfName]
313 if intf == nil {
314 return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intfName))
315 }
316 if intf.Kind != Interface {
317 return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intfName), intf.Kind)
318 }
319 for _, requiredField := range intf.Fields {
320 foundField := def.Fields.ForName(requiredField.Name)
321 if foundField == nil {
322 return gqlerror.ErrorPosf(def.Position,
323 `For %s to implement %s it must have a field called %s.`,
324 def.Name, intf.Name, requiredField.Name,
325 )
326 }
327
328 if !isCovariant(schema, requiredField.Type, foundField.Type) {
329 return gqlerror.ErrorPosf(foundField.Position,
330 `For %s to implement %s the field %s must have type %s.`,
331 def.Name, intf.Name, requiredField.Name, requiredField.Type.String(),
332 )
333 }
334
335 for _, requiredArg := range requiredField.Arguments {
336 foundArg := foundField.Arguments.ForName(requiredArg.Name)
337 if foundArg == nil {
338 return gqlerror.ErrorPosf(foundField.Position,
339 `For %s to implement %s the field %s must have the same arguments but it is missing %s.`,
340 def.Name, intf.Name, requiredField.Name, requiredArg.Name,
341 )
342 }
343
344 if !requiredArg.Type.IsCompatible(foundArg.Type) {
345 return gqlerror.ErrorPosf(foundArg.Position,
346 `For %s to implement %s the field %s must have the same arguments but %s has the wrong type.`,
347 def.Name, intf.Name, requiredField.Name, requiredArg.Name,
348 )
349 }
350 }
351 for _, foundArgs := range foundField.Arguments {
352 if requiredField.Arguments.ForName(foundArgs.Name) == nil && foundArgs.Type.NonNull && foundArgs.DefaultValue == nil {
353 return gqlerror.ErrorPosf(foundArgs.Position,
354 `For %s to implement %s any additional arguments on %s must be optional or have a default value but %s is required.`,
355 def.Name, intf.Name, foundField.Name, foundArgs.Name,
356 )
357 }
358 }
359 }
360 return nil
361 }
362
363 func isCovariant(schema *Schema, required *Type, actual *Type) bool {
364 if required.NonNull && !actual.NonNull {
365 return false
366 }
367
368 if required.NamedType != "" {
369 if required.NamedType == actual.NamedType {
370 return true
371 }
372 for _, pt := range schema.PossibleTypes[required.NamedType] {
373 if pt.Name == actual.NamedType {
374 return true
375 }
376 }
377 return false
378 }
379
380 if required.Elem != nil && actual.Elem == nil {
381 return false
382 }
383
384 return isCovariant(schema, required.Elem, actual.Elem)
385 }
386
387 func validateName(pos *Position, name string) *gqlerror.Error {
388 if strings.HasPrefix(name, "__") {
389 return gqlerror.ErrorPosf(pos, `Name "%s" must not begin with "__", which is reserved by GraphQL introspection.`, name)
390 }
391 return nil
392 }
393
394 func isValidKind(kind DefinitionKind, valid ...DefinitionKind) bool {
395 for _, k := range valid {
396 if kind == k {
397 return true
398 }
399 }
400 return false
401 }
402
403 func kindList(kinds ...DefinitionKind) string {
404 s := make([]string, len(kinds))
405 for i, k := range kinds {
406 s[i] = string(k)
407 }
408 return strings.Join(s, ", ")
409 }
410
View as plain text