schema.go

  1//go:generate go run ./inliner/inliner.go
  2
  3package validator
  4
  5import (
  6	"strconv"
  7
  8	. "github.com/vektah/gqlparser/ast"
  9	"github.com/vektah/gqlparser/gqlerror"
 10	"github.com/vektah/gqlparser/parser"
 11)
 12
 13func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) {
 14	ast := &SchemaDocument{}
 15	for _, input := range inputs {
 16		inputAst, err := parser.ParseSchema(input)
 17		if err != nil {
 18			return nil, err
 19		}
 20
 21		ast.Merge(inputAst)
 22	}
 23
 24	schema := Schema{
 25		Types:         map[string]*Definition{},
 26		Directives:    map[string]*DirectiveDefinition{},
 27		PossibleTypes: 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		if def.Kind != Interface {
 37			for _, intf := range def.Interfaces {
 38				schema.AddPossibleType(intf, ast.Definitions[i])
 39			}
 40			schema.AddPossibleType(def.Name, ast.Definitions[i])
 41		}
 42	}
 43
 44	for _, ext := range ast.Extensions {
 45		def := schema.Types[ext.Name]
 46		if def == nil {
 47			return nil, gqlerror.ErrorPosf(ext.Position, "Cannot extend type %s because it does not exist.", ext.Name)
 48		}
 49
 50		if def.Kind != ext.Kind {
 51			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)
 52		}
 53
 54		def.Directives = append(def.Directives, ext.Directives...)
 55		def.Interfaces = append(def.Interfaces, ext.Interfaces...)
 56		def.Fields = append(def.Fields, ext.Fields...)
 57		def.Types = append(def.Types, ext.Types...)
 58		def.EnumValues = append(def.EnumValues, ext.EnumValues...)
 59	}
 60
 61	for i, dir := range ast.Directives {
 62		if schema.Directives[dir.Name] != nil {
 63			return nil, gqlerror.ErrorPosf(dir.Position, "Cannot redeclare directive %s.", dir.Name)
 64		}
 65		schema.Directives[dir.Name] = ast.Directives[i]
 66	}
 67
 68	if len(ast.Schema) > 1 {
 69		return nil, gqlerror.ErrorPosf(ast.Schema[1].Position, "Cannot have multiple schema entry points, consider schema extensions instead.")
 70	}
 71
 72	if len(ast.Schema) == 1 {
 73		for _, entrypoint := range ast.Schema[0].OperationTypes {
 74			def := schema.Types[entrypoint.Type]
 75			if def == nil {
 76				return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
 77			}
 78			switch entrypoint.Operation {
 79			case Query:
 80				schema.Query = def
 81			case Mutation:
 82				schema.Mutation = def
 83			case Subscription:
 84				schema.Subscription = def
 85			}
 86		}
 87	}
 88
 89	for _, ext := range ast.SchemaExtension {
 90		for _, entrypoint := range ext.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 _, typ := range schema.Types {
107		err := validateDefinition(&schema, typ)
108		if err != nil {
109			return nil, err
110		}
111	}
112
113	for _, dir := range schema.Directives {
114		err := validateDirective(&schema, dir)
115		if err != nil {
116			return nil, err
117		}
118	}
119
120	if schema.Query == nil && schema.Types["Query"] != nil {
121		schema.Query = schema.Types["Query"]
122	}
123
124	if schema.Mutation == nil && schema.Types["Mutation"] != nil {
125		schema.Mutation = schema.Types["Mutation"]
126	}
127
128	if schema.Subscription == nil && schema.Types["Subscription"] != nil {
129		schema.Subscription = schema.Types["Subscription"]
130	}
131
132	if schema.Query != nil {
133		schema.Query.Fields = append(
134			schema.Query.Fields,
135			&FieldDefinition{
136				Name: "__schema",
137				Type: NonNullNamedType("__Schema", nil),
138			},
139			&FieldDefinition{
140				Name: "__type",
141				Type: NonNullNamedType("__Type", nil),
142				Arguments: ArgumentDefinitionList{
143					{Name: "name", Type: NamedType("String", nil)},
144				},
145			},
146		)
147	}
148
149	return &schema, nil
150}
151
152func validateDirective(schema *Schema, def *DirectiveDefinition) *gqlerror.Error {
153	return validateArgs(schema, def.Arguments, def)
154}
155
156func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
157	for _, field := range def.Fields {
158		if err := validateTypeRef(schema, field.Type); err != nil {
159			return err
160		}
161		if err := validateArgs(schema, field.Arguments, nil); err != nil {
162			return err
163		}
164		if err := validateDirectives(schema, field.Directives, nil); err != nil {
165			return err
166		}
167	}
168
169	for _, intf := range def.Interfaces {
170		intDef := schema.Types[intf]
171		if intDef == nil {
172			return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intf))
173		}
174		if intDef.Kind != Interface {
175			return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intf), intDef.Kind)
176		}
177	}
178
179	return validateDirectives(schema, def.Directives, nil)
180}
181
182func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error {
183	if schema.Types[typ.Name()] == nil {
184		return gqlerror.ErrorPosf(typ.Position, "Undefined type %s.", typ.Name())
185	}
186	return nil
187}
188
189func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective *DirectiveDefinition) *gqlerror.Error {
190	for _, arg := range args {
191		if err := validateTypeRef(schema, arg.Type); err != nil {
192			return err
193		}
194		if err := validateDirectives(schema, arg.Directives, currentDirective); err != nil {
195			return err
196		}
197	}
198	return nil
199}
200
201func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *DirectiveDefinition) *gqlerror.Error {
202	for _, dir := range dirs {
203		if currentDirective != nil && dir.Name == currentDirective.Name {
204			return gqlerror.ErrorPosf(dir.Position, "Directive %s cannot refer to itself.", currentDirective.Name)
205		}
206		if schema.Directives[dir.Name] == nil {
207			return gqlerror.ErrorPosf(dir.Position, "Undefined directive %s.", dir.Name)
208		}
209		dir.Definition = schema.Directives[dir.Name]
210	}
211	return nil
212}