1//go:generate go run ./inliner/inliner.go
2
3package validator
4
5import (
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
14func 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
22func 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 for _, ext := range ast.Extensions {
38 def := schema.Types[ext.Name]
39 if def == nil {
40 return nil, gqlerror.ErrorPosf(ext.Position, "Cannot extend type %s because it does not exist.", ext.Name)
41 }
42
43 if def.Kind != ext.Kind {
44 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)
45 }
46
47 def.Directives = append(def.Directives, ext.Directives...)
48 def.Interfaces = append(def.Interfaces, ext.Interfaces...)
49 def.Fields = append(def.Fields, ext.Fields...)
50 def.Types = append(def.Types, ext.Types...)
51 def.EnumValues = append(def.EnumValues, ext.EnumValues...)
52 }
53
54 for _, def := range ast.Definitions {
55 switch def.Kind {
56 case Union:
57 for _, t := range def.Types {
58 schema.AddPossibleType(def.Name, schema.Types[t])
59 schema.AddImplements(t, def)
60 }
61 case InputObject, Object:
62 for _, intf := range def.Interfaces {
63 schema.AddPossibleType(intf, def)
64 schema.AddImplements(def.Name, schema.Types[intf])
65 }
66 schema.AddPossibleType(def.Name, def)
67 }
68 }
69
70 for i, dir := range ast.Directives {
71 if schema.Directives[dir.Name] != nil {
72 return nil, gqlerror.ErrorPosf(dir.Position, "Cannot redeclare directive %s.", dir.Name)
73 }
74 schema.Directives[dir.Name] = ast.Directives[i]
75 }
76
77 if len(ast.Schema) > 1 {
78 return nil, gqlerror.ErrorPosf(ast.Schema[1].Position, "Cannot have multiple schema entry points, consider schema extensions instead.")
79 }
80
81 if len(ast.Schema) == 1 {
82 for _, entrypoint := range ast.Schema[0].OperationTypes {
83 def := schema.Types[entrypoint.Type]
84 if def == nil {
85 return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
86 }
87 switch entrypoint.Operation {
88 case Query:
89 schema.Query = def
90 case Mutation:
91 schema.Mutation = def
92 case Subscription:
93 schema.Subscription = def
94 }
95 }
96 }
97
98 for _, ext := range ast.SchemaExtension {
99 for _, entrypoint := range ext.OperationTypes {
100 def := schema.Types[entrypoint.Type]
101 if def == nil {
102 return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
103 }
104 switch entrypoint.Operation {
105 case Query:
106 schema.Query = def
107 case Mutation:
108 schema.Mutation = def
109 case Subscription:
110 schema.Subscription = def
111 }
112 }
113 }
114
115 for _, typ := range schema.Types {
116 err := validateDefinition(&schema, typ)
117 if err != nil {
118 return nil, err
119 }
120 }
121
122 for _, dir := range schema.Directives {
123 err := validateDirective(&schema, dir)
124 if err != nil {
125 return nil, err
126 }
127 }
128
129 if schema.Query == nil && schema.Types["Query"] != nil {
130 schema.Query = schema.Types["Query"]
131 }
132
133 if schema.Mutation == nil && schema.Types["Mutation"] != nil {
134 schema.Mutation = schema.Types["Mutation"]
135 }
136
137 if schema.Subscription == nil && schema.Types["Subscription"] != nil {
138 schema.Subscription = schema.Types["Subscription"]
139 }
140
141 if schema.Query != nil {
142 schema.Query.Fields = append(
143 schema.Query.Fields,
144 &FieldDefinition{
145 Name: "__schema",
146 Type: NonNullNamedType("__Schema", nil),
147 },
148 &FieldDefinition{
149 Name: "__type",
150 Type: NonNullNamedType("__Type", nil),
151 Arguments: ArgumentDefinitionList{
152 {Name: "name", Type: NamedType("String", nil)},
153 },
154 },
155 )
156 }
157
158 return &schema, nil
159}
160
161func validateDirective(schema *Schema, def *DirectiveDefinition) *gqlerror.Error {
162 if err := validateName(def.Position, def.Name); err != nil {
163 // now, GraphQL spec doesn't have reserved directive name
164 return err
165 }
166
167 return validateArgs(schema, def.Arguments, def)
168}
169
170func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
171 for _, field := range def.Fields {
172 if err := validateName(field.Position, field.Name); err != nil {
173 // now, GraphQL spec doesn't have reserved field name
174 return err
175 }
176 if err := validateTypeRef(schema, field.Type); err != nil {
177 return err
178 }
179 if err := validateArgs(schema, field.Arguments, nil); err != nil {
180 return err
181 }
182 if err := validateDirectives(schema, field.Directives, nil); err != nil {
183 return err
184 }
185 }
186
187 for _, intf := range def.Interfaces {
188 intDef := schema.Types[intf]
189 if intDef == nil {
190 return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intf))
191 }
192 if intDef.Kind != Interface {
193 return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intf), intDef.Kind)
194 }
195 }
196
197 switch def.Kind {
198 case Object, Interface:
199 if len(def.Fields) == 0 {
200 return gqlerror.ErrorPosf(def.Position, "%s must define one or more fields.", def.Kind)
201 }
202 case Enum:
203 if len(def.EnumValues) == 0 {
204 return gqlerror.ErrorPosf(def.Position, "%s must define one or more unique enum values.", def.Kind)
205 }
206 case InputObject:
207 if len(def.Fields) == 0 {
208 return gqlerror.ErrorPosf(def.Position, "%s must define one or more input fields.", def.Kind)
209 }
210 }
211
212 for idx, field1 := range def.Fields {
213 for _, field2 := range def.Fields[idx+1:] {
214 if field1.Name == field2.Name {
215 return gqlerror.ErrorPosf(field2.Position, "Field %s.%s can only be defined once.", def.Name, field2.Name)
216 }
217 }
218 }
219
220 if !def.BuiltIn {
221 // GraphQL spec has reserved type names a lot!
222 err := validateName(def.Position, def.Name)
223 if err != nil {
224 return err
225 }
226 }
227
228 return validateDirectives(schema, def.Directives, nil)
229}
230
231func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error {
232 if schema.Types[typ.Name()] == nil {
233 return gqlerror.ErrorPosf(typ.Position, "Undefined type %s.", typ.Name())
234 }
235 return nil
236}
237
238func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective *DirectiveDefinition) *gqlerror.Error {
239 for _, arg := range args {
240 if err := validateName(arg.Position, arg.Name); err != nil {
241 // now, GraphQL spec doesn't have reserved argument name
242 return err
243 }
244 if err := validateTypeRef(schema, arg.Type); err != nil {
245 return err
246 }
247 if err := validateDirectives(schema, arg.Directives, currentDirective); err != nil {
248 return err
249 }
250 }
251 return nil
252}
253
254func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *DirectiveDefinition) *gqlerror.Error {
255 for _, dir := range dirs {
256 if err := validateName(dir.Position, dir.Name); err != nil {
257 // now, GraphQL spec doesn't have reserved directive name
258 return err
259 }
260 if currentDirective != nil && dir.Name == currentDirective.Name {
261 return gqlerror.ErrorPosf(dir.Position, "Directive %s cannot refer to itself.", currentDirective.Name)
262 }
263 if schema.Directives[dir.Name] == nil {
264 return gqlerror.ErrorPosf(dir.Position, "Undefined directive %s.", dir.Name)
265 }
266 dir.Definition = schema.Directives[dir.Name]
267 }
268 return nil
269}
270
271func validateName(pos *Position, name string) *gqlerror.Error {
272 if strings.HasPrefix(name, "__") {
273 return gqlerror.ErrorPosf(pos, `Name "%s" must not begin with "__", which is reserved by GraphQL introspection.`, name)
274 }
275 return nil
276}