//go:generate go run ./inliner/inliner.go
package validator
import (
"strconv"
. "github.com/vektah/gqlparser/ast"
"github.com/vektah/gqlparser/gqlerror"
"github.com/vektah/gqlparser/parser"
)
func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) {
ast := &SchemaDocument{}
for _, input := range inputs {
inputAst, err := parser.ParseSchema(input)
if err != nil {
return nil, err
}
ast.Merge(inputAst)
}
schema := Schema{
Types: map[string]*Definition{},
Directives: map[string]*DirectiveDefinition{},
PossibleTypes: map[string][]*Definition{},
}
for i, def := range ast.Definitions {
if schema.Types[def.Name] != nil {
return nil, gqlerror.ErrorPosf(def.Position, "Cannot redeclare type %s.", def.Name)
}
schema.Types[def.Name] = ast.Definitions[i]
if def.Kind != Interface {
for _, intf := range def.Interfaces {
schema.AddPossibleType(intf, ast.Definitions[i])
}
schema.AddPossibleType(def.Name, ast.Definitions[i])
}
}
for _, ext := range ast.Extensions {
def := schema.Types[ext.Name]
if def == nil {
return nil, gqlerror.ErrorPosf(ext.Position, "Cannot extend type %s because it does not exist.", ext.Name)
}
if def.Kind != ext.Kind {
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)
}
def.Directives = append(def.Directives, ext.Directives...)
def.Interfaces = append(def.Interfaces, ext.Interfaces...)
def.Fields = append(def.Fields, ext.Fields...)
def.Types = append(def.Types, ext.Types...)
def.EnumValues = append(def.EnumValues, ext.EnumValues...)
}
for i, dir := range ast.Directives {
if schema.Directives[dir.Name] != nil {
return nil, gqlerror.ErrorPosf(dir.Position, "Cannot redeclare directive %s.", dir.Name)
}
schema.Directives[dir.Name] = ast.Directives[i]
}
if len(ast.Schema) > 1 {
return nil, gqlerror.ErrorPosf(ast.Schema[1].Position, "Cannot have multiple schema entry points, consider schema extensions instead.")
}
if len(ast.Schema) == 1 {
for _, entrypoint := range ast.Schema[0].OperationTypes {
def := schema.Types[entrypoint.Type]
if def == nil {
return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
}
switch entrypoint.Operation {
case Query:
schema.Query = def
case Mutation:
schema.Mutation = def
case Subscription:
schema.Subscription = def
}
}
}
for _, ext := range ast.SchemaExtension {
for _, entrypoint := range ext.OperationTypes {
def := schema.Types[entrypoint.Type]
if def == nil {
return nil, gqlerror.ErrorPosf(entrypoint.Position, "Schema root %s refers to a type %s that does not exist.", entrypoint.Operation, entrypoint.Type)
}
switch entrypoint.Operation {
case Query:
schema.Query = def
case Mutation:
schema.Mutation = def
case Subscription:
schema.Subscription = def
}
}
}
for _, typ := range schema.Types {
err := validateDefinition(&schema, typ)
if err != nil {
return nil, err
}
}
for _, dir := range schema.Directives {
err := validateDirective(&schema, dir)
if err != nil {
return nil, err
}
}
if schema.Query == nil && schema.Types["Query"] != nil {
schema.Query = schema.Types["Query"]
}
if schema.Mutation == nil && schema.Types["Mutation"] != nil {
schema.Mutation = schema.Types["Mutation"]
}
if schema.Subscription == nil && schema.Types["Subscription"] != nil {
schema.Subscription = schema.Types["Subscription"]
}
if schema.Query != nil {
schema.Query.Fields = append(
schema.Query.Fields,
&FieldDefinition{
Name: "__schema",
Type: NonNullNamedType("__Schema", nil),
},
&FieldDefinition{
Name: "__type",
Type: NonNullNamedType("__Type", nil),
Arguments: ArgumentDefinitionList{
{Name: "name", Type: NamedType("String", nil)},
},
},
)
}
return &schema, nil
}
func validateDirective(schema *Schema, def *DirectiveDefinition) *gqlerror.Error {
return validateArgs(schema, def.Arguments, def)
}
func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
for _, field := range def.Fields {
if err := validateTypeRef(schema, field.Type); err != nil {
return err
}
if err := validateArgs(schema, field.Arguments, nil); err != nil {
return err
}
if err := validateDirectives(schema, field.Directives, nil); err != nil {
return err
}
}
for _, intf := range def.Interfaces {
intDef := schema.Types[intf]
if intDef == nil {
return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intf))
}
if intDef.Kind != Interface {
return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intf), intDef.Kind)
}
}
return validateDirectives(schema, def.Directives, nil)
}
func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error {
if schema.Types[typ.Name()] == nil {
return gqlerror.ErrorPosf(typ.Position, "Undefined type %s.", typ.Name())
}
return nil
}
func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective *DirectiveDefinition) *gqlerror.Error {
for _, arg := range args {
if err := validateTypeRef(schema, arg.Type); err != nil {
return err
}
if err := validateDirectives(schema, arg.Directives, currentDirective); err != nil {
return err
}
}
return nil
}
func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *DirectiveDefinition) *gqlerror.Error {
for _, dir := range dirs {
if currentDirective != nil && dir.Name == currentDirective.Name {
return gqlerror.ErrorPosf(dir.Position, "Directive %s cannot refer to itself.", currentDirective.Name)
}
if schema.Directives[dir.Name] == nil {
return gqlerror.ErrorPosf(dir.Position, "Undefined directive %s.", dir.Name)
}
dir.Definition = schema.Directives[dir.Name]
}
return nil
}