diff options
Diffstat (limited to 'vendor/github.com/vektah/gqlparser')
17 files changed, 412 insertions, 51 deletions
diff --git a/vendor/github.com/vektah/gqlparser/ast/definition.go b/vendor/github.com/vektah/gqlparser/ast/definition.go index 74f4ece5..f5c8ea37 100644 --- a/vendor/github.com/vektah/gqlparser/ast/definition.go +++ b/vendor/github.com/vektah/gqlparser/ast/definition.go @@ -30,6 +30,7 @@ type Definition struct { EnumValues EnumValueList // enum Position *Position `dump:"-"` + BuiltIn bool `dump:"-"` } func (d *Definition) IsLeafType() bool { diff --git a/vendor/github.com/vektah/gqlparser/ast/document.go b/vendor/github.com/vektah/gqlparser/ast/document.go index b7657d62..4672d0c0 100644 --- a/vendor/github.com/vektah/gqlparser/ast/document.go +++ b/vendor/github.com/vektah/gqlparser/ast/document.go @@ -32,6 +32,7 @@ type Schema struct { Directives map[string]*DirectiveDefinition PossibleTypes map[string][]*Definition + Implements map[string][]*Definition } func (s *Schema) AddPossibleType(name string, def *Definition) { @@ -40,17 +41,18 @@ func (s *Schema) AddPossibleType(name string, def *Definition) { // GetPossibleTypes will enumerate all the definitions for a given interface or union func (s *Schema) GetPossibleTypes(def *Definition) []*Definition { - if def.Kind == Union { - var defs []*Definition - for _, t := range def.Types { - defs = append(defs, s.Types[t]) - } - return defs - } - return s.PossibleTypes[def.Name] } +func (s *Schema) AddImplements(name string, iface *Definition) { + s.Implements[name] = append(s.Implements[name], iface) +} + +// GetImplements returns all the interface and union definitions that the given definition satisfies +func (s *Schema) GetImplements(def *Definition) []*Definition { + return s.Implements[def.Name] +} + type SchemaDefinition struct { Description string Directives DirectiveList diff --git a/vendor/github.com/vektah/gqlparser/ast/source.go b/vendor/github.com/vektah/gqlparser/ast/source.go index 9d44dd9c..acb07ba6 100644 --- a/vendor/github.com/vektah/gqlparser/ast/source.go +++ b/vendor/github.com/vektah/gqlparser/ast/source.go @@ -1,8 +1,9 @@ package ast type Source struct { - Name string - Input string + Name string + Input string + BuiltIn bool } type Position struct { diff --git a/vendor/github.com/vektah/gqlparser/ast/value.go b/vendor/github.com/vektah/gqlparser/ast/value.go index 3168b266..c25ef150 100644 --- a/vendor/github.com/vektah/gqlparser/ast/value.go +++ b/vendor/github.com/vektah/gqlparser/ast/value.go @@ -107,7 +107,7 @@ func (v *Value) String() string { case ObjectValue: var val []string for _, elem := range v.Children { - val = append(val, strconv.Quote(elem.Name)+":"+elem.Value.String()) + val = append(val, elem.Name+":"+elem.Value.String()) } return "{" + strings.Join(val, ",") + "}" default: diff --git a/vendor/github.com/vektah/gqlparser/lexer/lexer.go b/vendor/github.com/vektah/gqlparser/lexer/lexer.go index 3aaa7102..89687857 100644 --- a/vendor/github.com/vektah/gqlparser/lexer/lexer.go +++ b/vendor/github.com/vektah/gqlparser/lexer/lexer.go @@ -448,7 +448,7 @@ func (s *Lexer) readBlockString() (Token, *gqlerror.Error) { s.end += 4 s.endRunes += 4 } else if r == '\r' { - if s.end+1 <= inputLen && s.Input[s.end+1] == '\n' { + if s.end+1 < inputLen && s.Input[s.end+1] == '\n' { s.end++ s.endRunes++ } diff --git a/vendor/github.com/vektah/gqlparser/parser/parser.go b/vendor/github.com/vektah/gqlparser/parser/parser.go index f3648cb3..96e98402 100644 --- a/vendor/github.com/vektah/gqlparser/parser/parser.go +++ b/vendor/github.com/vektah/gqlparser/parser/parser.go @@ -82,6 +82,10 @@ func (p *parser) expect(kind lexer.Type) lexer.Token { } func (p *parser) skip(kind lexer.Type) bool { + if p.err != nil { + return false + } + tok := p.peek() if tok.Kind != kind { @@ -110,3 +114,23 @@ func (p *parser) many(start lexer.Type, end lexer.Type, cb func()) { } p.next() } + +func (p *parser) some(start lexer.Type, end lexer.Type, cb func()) { + hasDef := p.skip(start) + if !hasDef { + return + } + + called := false + for p.peek().Kind != end && p.err == nil { + called = true + cb() + } + + if !called { + p.error(p.peek(), "expected at least one definition, found %s", p.peek().Kind.String()) + return + } + + p.next() +} diff --git a/vendor/github.com/vektah/gqlparser/parser/query.go b/vendor/github.com/vektah/gqlparser/parser/query.go index 7fecb57f..89e1e2e3 100644 --- a/vendor/github.com/vektah/gqlparser/parser/query.go +++ b/vendor/github.com/vektah/gqlparser/parser/query.go @@ -46,7 +46,7 @@ func (p *parser) parseOperationDefinition() *OperationDefinition { return &OperationDefinition{ Position: p.peekPos(), Operation: Query, - SelectionSet: p.parseSelectionSet(), + SelectionSet: p.parseRequiredSelectionSet(), } } @@ -60,7 +60,7 @@ func (p *parser) parseOperationDefinition() *OperationDefinition { od.VariableDefinitions = p.parseVariableDefinitions() od.Directives = p.parseDirectives(false) - od.SelectionSet = p.parseSelectionSet() + od.SelectionSet = p.parseRequiredSelectionSet() return &od } @@ -109,9 +109,23 @@ func (p *parser) parseVariable() string { return p.parseName() } -func (p *parser) parseSelectionSet() SelectionSet { +func (p *parser) parseOptionalSelectionSet() SelectionSet { var selections []Selection - p.many(lexer.BraceL, lexer.BraceR, func() { + p.some(lexer.BraceL, lexer.BraceR, func() { + selections = append(selections, p.parseSelection()) + }) + + return SelectionSet(selections) +} + +func (p *parser) parseRequiredSelectionSet() SelectionSet { + if p.peek().Kind != lexer.BraceL { + p.error(p.peek(), "Expected %s, found %s", lexer.BraceL, p.peek().Kind.String()) + return nil + } + + var selections []Selection + p.some(lexer.BraceL, lexer.BraceR, func() { selections = append(selections, p.parseSelection()) }) @@ -139,7 +153,7 @@ func (p *parser) parseField() *Field { field.Arguments = p.parseArguments(false) field.Directives = p.parseDirectives(false) if p.peek().Kind == lexer.BraceL { - field.SelectionSet = p.parseSelectionSet() + field.SelectionSet = p.parseOptionalSelectionSet() } return &field @@ -184,7 +198,7 @@ func (p *parser) parseFragment() Selection { } def.Directives = p.parseDirectives(false) - def.SelectionSet = p.parseSelectionSet() + def.SelectionSet = p.parseRequiredSelectionSet() return &def } @@ -200,7 +214,7 @@ func (p *parser) parseFragmentDefinition() *FragmentDefinition { def.TypeCondition = p.parseName() def.Directives = p.parseDirectives(false) - def.SelectionSet = p.parseSelectionSet() + def.SelectionSet = p.parseRequiredSelectionSet() return &def } diff --git a/vendor/github.com/vektah/gqlparser/parser/query_test.yml b/vendor/github.com/vektah/gqlparser/parser/query_test.yml index f392eb8e..902bb15f 100644 --- a/vendor/github.com/vektah/gqlparser/parser/query_test.yml +++ b/vendor/github.com/vektah/gqlparser/parser/query_test.yml @@ -504,4 +504,17 @@ large queries: Value: $b - <Argument> Name: "obj" - Value: {"key":"value","block":"block string uses \"\"\""} + Value: {key:"value",block:"block string uses \"\"\""} + +fuzzer: +- name: 01 + input: '{__typename{...}}' + error: + message: 'Expected {, found }' + locations: [{ line: 1, column: 16 }] + +- name: 02 + input: '{...{__typename{...{}}}}' + error: + message: 'expected at least one definition, found }' + locations: [{ line: 1, column: 21 }] diff --git a/vendor/github.com/vektah/gqlparser/parser/schema.go b/vendor/github.com/vektah/gqlparser/parser/schema.go index f409f1f4..5689e433 100644 --- a/vendor/github.com/vektah/gqlparser/parser/schema.go +++ b/vendor/github.com/vektah/gqlparser/parser/schema.go @@ -10,7 +10,31 @@ func ParseSchema(source *Source) (*SchemaDocument, *gqlerror.Error) { p := parser{ lexer: lexer.New(source), } - return p.parseSchemaDocument(), p.err + ast, err := p.parseSchemaDocument(), p.err + if err != nil { + return nil, err + } + + for _, def := range ast.Definitions { + def.BuiltIn = source.BuiltIn + } + for _, def := range ast.Extensions { + def.BuiltIn = source.BuiltIn + } + + return ast, nil +} + +func ParseSchemas(inputs ...*Source) (*SchemaDocument, *gqlerror.Error) { + ast := &SchemaDocument{} + for _, input := range inputs { + inputAst, err := ParseSchema(input) + if err != nil { + return nil, err + } + ast.Merge(inputAst) + } + return ast, nil } func (p *parser) parseSchemaDocument() *SchemaDocument { @@ -96,7 +120,7 @@ func (p *parser) parseSchemaDefinition(description string) *SchemaDefinition { def.Description = description def.Directives = p.parseDirectives(true) - p.many(lexer.BraceL, lexer.BraceR, func() { + p.some(lexer.BraceL, lexer.BraceR, func() { def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition()) }) return &def @@ -154,7 +178,7 @@ func (p *parser) parseImplementsInterfaces() []string { func (p *parser) parseFieldsDefinition() FieldList { var defs FieldList - p.many(lexer.BraceL, lexer.BraceR, func() { + p.some(lexer.BraceL, lexer.BraceR, func() { defs = append(defs, p.parseFieldDefinition()) }) return defs @@ -175,7 +199,7 @@ func (p *parser) parseFieldDefinition() *FieldDefinition { func (p *parser) parseArgumentDefs() ArgumentDefinitionList { var args ArgumentDefinitionList - p.many(lexer.ParenL, lexer.ParenR, func() { + p.some(lexer.ParenL, lexer.ParenR, func() { args = append(args, p.parseArgumentDef()) }) return args @@ -264,7 +288,7 @@ func (p *parser) parseEnumTypeDefinition(description string) *Definition { func (p *parser) parseEnumValuesDefinition() EnumValueList { var values EnumValueList - p.many(lexer.BraceL, lexer.BraceR, func() { + p.some(lexer.BraceL, lexer.BraceR, func() { values = append(values, p.parseEnumValueDefinition()) }) return values @@ -294,7 +318,7 @@ func (p *parser) parseInputObjectTypeDefinition(description string) *Definition func (p *parser) parseInputFieldsDefinition() FieldList { var values FieldList - p.many(lexer.BraceL, lexer.BraceR, func() { + p.some(lexer.BraceL, lexer.BraceR, func() { values = append(values, p.parseInputValueDef()) }) return values @@ -329,7 +353,7 @@ func (p *parser) parseSchemaExtension() *SchemaDefinition { var def SchemaDefinition def.Position = p.peekPos() def.Directives = p.parseDirectives(true) - p.many(lexer.BraceL, lexer.BraceR, func() { + p.some(lexer.BraceL, lexer.BraceR, func() { def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition()) }) if len(def.Directives) == 0 && len(def.OperationTypes) == 0 { diff --git a/vendor/github.com/vektah/gqlparser/parser/schema_test.yml b/vendor/github.com/vektah/gqlparser/parser/schema_test.yml index c65239a5..394bd363 100644 --- a/vendor/github.com/vektah/gqlparser/parser/schema_test.yml +++ b/vendor/github.com/vektah/gqlparser/parser/schema_test.yml @@ -136,6 +136,12 @@ object types: Name: "argTwo" Type: Int Type: String + - name: must define one or more fields + input: | + type Hello {} + error: + message: "expected at least one definition, found }" + locations: [{ line: 1, column: 13 }] type extensions: - name: Object extension @@ -378,6 +384,12 @@ enums: Name: "WO" - <EnumValueDefinition> Name: "RLD" + - name: must define one or more unique enum values + input: | + enum Hello {} + error: + message: "expected at least one definition, found }" + locations: [{ line: 1, column: 13 }] interface: - name: simple @@ -395,6 +407,12 @@ interface: - <FieldDefinition> Name: "world" Type: String + - name: must define one or more fields + input: | + interface Hello {} + error: + message: "expected at least one definition, found }" + locations: [{ line: 1, column: 18 }] unions: - name: simple @@ -485,6 +503,12 @@ input object: error: message: "Expected :, found (" locations: [{ line: 2, column: 8 }] + - name: must define one or more input fields + input: | + input Hello {} + error: + message: "expected at least one definition, found }" + locations: [{ line: 1, column: 14 }] directives: - name: simple @@ -503,3 +527,14 @@ directives: message: 'Unexpected Name "INCORRECT_LOCATION"' locations: [{ line: 1, column: 27 }] +fuzzer: + - name: 1 + input: "type o{d(g:[" + error: + message: 'Expected Name, found <EOF>' + locations: [{ line: 1, column: 13 }] + - name: 2 + input: "\"\"\"\r" + error: + message: 'Unexpected <Invalid>' + locations: [{ line: 1, column: 5 }] diff --git a/vendor/github.com/vektah/gqlparser/validator/prelude.go b/vendor/github.com/vektah/gqlparser/validator/prelude.go index 80ce8a21..c7a4d35b 100644 --- a/vendor/github.com/vektah/gqlparser/validator/prelude.go +++ b/vendor/github.com/vektah/gqlparser/validator/prelude.go @@ -2,4 +2,8 @@ package validator import "github.com/vektah/gqlparser/ast" -var Prelude = &ast.Source{Name: "prelude.graphql", Input: "# This file defines all the implicitly declared types that are required by the graphql spec. It is implicitly included by calls to LoadSchema\n\n# The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.\nscalar Int\n\n# The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\nscalar Float\n\n# The `String`scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.\nscalar String\n\n# The `Boolean` scalar type represents ` + \"`\" + `true` + \"`\" + ` or ` + \"`\" + `false` + \"`\" + `.\nscalar Boolean\n\n# The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as \"4\") or integer (such as 4) input value will be accepted as an ID.\nscalar ID\n\n# The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.\ndirective @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT\n\n# The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.\ndirective @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT\n\n# The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema, such as deprecated fields on a type or deprecated enum values.\ndirective @deprecated(reason: String = \"No longer supported\") on FIELD_DEFINITION | ENUM_VALUE\n\ntype __Schema {\n types: [__Type!]!\n queryType: __Type!\n mutationType: __Type\n subscriptionType: __Type\n directives: [__Directive!]!\n}\n\ntype __Type {\n kind: __TypeKind!\n name: String\n description: String\n\n # OBJECT and INTERFACE only\n fields(includeDeprecated: Boolean = false): [__Field!]\n\n # OBJECT only\n interfaces: [__Type!]\n\n # INTERFACE and UNION only\n possibleTypes: [__Type!]\n\n # ENUM only\n enumValues(includeDeprecated: Boolean = false): [__EnumValue!]\n\n # INPUT_OBJECT only\n inputFields: [__InputValue!]\n\n # NON_NULL and LIST only\n ofType: __Type\n}\n\ntype __Field {\n name: String!\n description: String\n args: [__InputValue!]!\n type: __Type!\n isDeprecated: Boolean!\n deprecationReason: String\n}\n\ntype __InputValue {\n name: String!\n description: String\n type: __Type!\n defaultValue: String\n}\n\ntype __EnumValue {\n name: String!\n description: String\n isDeprecated: Boolean!\n deprecationReason: String\n}\n\nenum __TypeKind {\n SCALAR\n OBJECT\n INTERFACE\n UNION\n ENUM\n INPUT_OBJECT\n LIST\n NON_NULL\n}\n\ntype __Directive {\n name: String!\n description: String\n locations: [__DirectiveLocation!]!\n args: [__InputValue!]!\n}\n\nenum __DirectiveLocation {\n QUERY\n MUTATION\n SUBSCRIPTION\n FIELD\n FRAGMENT_DEFINITION\n FRAGMENT_SPREAD\n INLINE_FRAGMENT\n SCHEMA\n SCALAR\n OBJECT\n FIELD_DEFINITION\n ARGUMENT_DEFINITION\n INTERFACE\n UNION\n ENUM\n ENUM_VALUE\n INPUT_OBJECT\n INPUT_FIELD_DEFINITION\n}\n"} +var Prelude = &ast.Source{ + Name: "prelude.graphql", + Input: "# This file defines all the implicitly declared types that are required by the graphql spec. It is implicitly included by calls to LoadSchema\n\n# The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.\nscalar Int\n\n# The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\nscalar Float\n\n# The `String`scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.\nscalar String\n\n# The `Boolean` scalar type represents ` + \"`\" + `true` + \"`\" + ` or ` + \"`\" + `false` + \"`\" + `.\nscalar Boolean\n\n# The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as \"4\") or integer (such as 4) input value will be accepted as an ID.\nscalar ID\n\n# The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.\ndirective @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT\n\n# The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.\ndirective @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT\n\n# The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema, such as deprecated fields on a type or deprecated enum values.\ndirective @deprecated(reason: String = \"No longer supported\") on FIELD_DEFINITION | ENUM_VALUE\n\ntype __Schema {\n types: [__Type!]!\n queryType: __Type!\n mutationType: __Type\n subscriptionType: __Type\n directives: [__Directive!]!\n}\n\ntype __Type {\n kind: __TypeKind!\n name: String\n description: String\n\n # OBJECT and INTERFACE only\n fields(includeDeprecated: Boolean = false): [__Field!]\n\n # OBJECT only\n interfaces: [__Type!]\n\n # INTERFACE and UNION only\n possibleTypes: [__Type!]\n\n # ENUM only\n enumValues(includeDeprecated: Boolean = false): [__EnumValue!]\n\n # INPUT_OBJECT only\n inputFields: [__InputValue!]\n\n # NON_NULL and LIST only\n ofType: __Type\n}\n\ntype __Field {\n name: String!\n description: String\n args: [__InputValue!]!\n type: __Type!\n isDeprecated: Boolean!\n deprecationReason: String\n}\n\ntype __InputValue {\n name: String!\n description: String\n type: __Type!\n defaultValue: String\n}\n\ntype __EnumValue {\n name: String!\n description: String\n isDeprecated: Boolean!\n deprecationReason: String\n}\n\nenum __TypeKind {\n SCALAR\n OBJECT\n INTERFACE\n UNION\n ENUM\n INPUT_OBJECT\n LIST\n NON_NULL\n}\n\ntype __Directive {\n name: String!\n description: String\n locations: [__DirectiveLocation!]!\n args: [__InputValue!]!\n}\n\nenum __DirectiveLocation {\n QUERY\n MUTATION\n SUBSCRIPTION\n FIELD\n FRAGMENT_DEFINITION\n FRAGMENT_SPREAD\n INLINE_FRAGMENT\n SCHEMA\n SCALAR\n OBJECT\n FIELD_DEFINITION\n ARGUMENT_DEFINITION\n INTERFACE\n UNION\n ENUM\n ENUM_VALUE\n INPUT_OBJECT\n INPUT_FIELD_DEFINITION\n}\n", + BuiltIn: true, +} diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go b/vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go index 83b47387..1a46431d 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go +++ b/vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go @@ -9,7 +9,7 @@ func init() { AddRule("KnownArgumentNames", func(observers *Events, addError AddErrFunc) { // A GraphQL field is only valid if all supplied arguments are defined by that field. observers.OnField(func(walker *Walker, field *ast.Field) { - if field.Definition == nil { + if field.Definition == nil || field.ObjectDefinition == nil { return } for _, arg := range field.Arguments { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go b/vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go index 52eab3a2..bb2f1831 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go +++ b/vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go @@ -292,7 +292,11 @@ func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFieldsAndFr // (E) Then collect any conflicts between the provided collection of fields // and any fragment names found in the given fragment. + baseFragmentSpread := fragmentSpread for _, fragmentSpread := range fragmentSpreads { + if fragmentSpread.Name == baseFragmentSpread.Name { + continue + } m.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsMap, fragmentSpread) } } diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go b/vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go index 971decbf..04611834 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go +++ b/vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go @@ -20,7 +20,7 @@ func init() { case ast.Interface, ast.Union: parentDefs = walker.Schema.GetPossibleTypes(parentDef) default: - panic("unexpected type") + return } fragmentDefType := walker.Schema.Types[fragmentName] diff --git a/vendor/github.com/vektah/gqlparser/validator/schema.go b/vendor/github.com/vektah/gqlparser/validator/schema.go index 8fa18d7e..57d2022e 100644 --- a/vendor/github.com/vektah/gqlparser/validator/schema.go +++ b/vendor/github.com/vektah/gqlparser/validator/schema.go @@ -4,6 +4,7 @@ package validator import ( "strconv" + "strings" . "github.com/vektah/gqlparser/ast" "github.com/vektah/gqlparser/gqlerror" @@ -11,20 +12,19 @@ import ( ) 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) + ast, err := parser.ParseSchemas(inputs...) + if err != nil { + return nil, err } + return ValidateSchemaDocument(ast) +} +func ValidateSchemaDocument(ast *SchemaDocument) (*Schema, *gqlerror.Error) { schema := Schema{ Types: map[string]*Definition{}, Directives: map[string]*DirectiveDefinition{}, PossibleTypes: map[string][]*Definition{}, + Implements: map[string][]*Definition{}, } for i, def := range ast.Definitions { @@ -32,13 +32,6 @@ func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) { 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 { @@ -58,6 +51,22 @@ func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) { def.EnumValues = append(def.EnumValues, ext.EnumValues...) } + for _, def := range ast.Definitions { + switch def.Kind { + case Union: + for _, t := range def.Types { + schema.AddPossibleType(def.Name, schema.Types[t]) + schema.AddImplements(t, def) + } + case InputObject, Object: + for _, intf := range def.Interfaces { + schema.AddPossibleType(intf, def) + schema.AddImplements(def.Name, schema.Types[intf]) + } + schema.AddPossibleType(def.Name, def) + } + } + for i, dir := range ast.Directives { if schema.Directives[dir.Name] != nil { return nil, gqlerror.ErrorPosf(dir.Position, "Cannot redeclare directive %s.", dir.Name) @@ -150,11 +159,20 @@ func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) { } func validateDirective(schema *Schema, def *DirectiveDefinition) *gqlerror.Error { + if err := validateName(def.Position, def.Name); err != nil { + // now, GraphQL spec doesn't have reserved directive name + return err + } + return validateArgs(schema, def.Arguments, def) } func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error { for _, field := range def.Fields { + if err := validateName(field.Position, field.Name); err != nil { + // now, GraphQL spec doesn't have reserved field name + return err + } if err := validateTypeRef(schema, field.Type); err != nil { return err } @@ -176,6 +194,37 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error { } } + switch def.Kind { + case Object, Interface: + if len(def.Fields) == 0 { + return gqlerror.ErrorPosf(def.Position, "%s must define one or more fields.", def.Kind) + } + case Enum: + if len(def.EnumValues) == 0 { + return gqlerror.ErrorPosf(def.Position, "%s must define one or more unique enum values.", def.Kind) + } + case InputObject: + if len(def.Fields) == 0 { + return gqlerror.ErrorPosf(def.Position, "%s must define one or more input fields.", def.Kind) + } + } + + for idx, field1 := range def.Fields { + for _, field2 := range def.Fields[idx+1:] { + if field1.Name == field2.Name { + return gqlerror.ErrorPosf(field2.Position, "Field %s.%s can only be defined once.", def.Name, field2.Name) + } + } + } + + if !def.BuiltIn { + // GraphQL spec has reserved type names a lot! + err := validateName(def.Position, def.Name) + if err != nil { + return err + } + } + return validateDirectives(schema, def.Directives, nil) } @@ -188,6 +237,10 @@ func validateTypeRef(schema *Schema, typ *Type) *gqlerror.Error { func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective *DirectiveDefinition) *gqlerror.Error { for _, arg := range args { + if err := validateName(arg.Position, arg.Name); err != nil { + // now, GraphQL spec doesn't have reserved argument name + return err + } if err := validateTypeRef(schema, arg.Type); err != nil { return err } @@ -200,6 +253,10 @@ func validateArgs(schema *Schema, args ArgumentDefinitionList, currentDirective func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *DirectiveDefinition) *gqlerror.Error { for _, dir := range dirs { + if err := validateName(dir.Position, dir.Name); err != nil { + // now, GraphQL spec doesn't have reserved directive name + return err + } if currentDirective != nil && dir.Name == currentDirective.Name { return gqlerror.ErrorPosf(dir.Position, "Directive %s cannot refer to itself.", currentDirective.Name) } @@ -210,3 +267,10 @@ func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *Di } return nil } + +func validateName(pos *Position, name string) *gqlerror.Error { + if strings.HasPrefix(name, "__") { + return gqlerror.ErrorPosf(pos, `Name "%s" must not begin with "__", which is reserved by GraphQL introspection.`, name) + } + return nil +} diff --git a/vendor/github.com/vektah/gqlparser/validator/schema_test.yml b/vendor/github.com/vektah/gqlparser/validator/schema_test.yml index 59e7145c..abc8dd7e 100644 --- a/vendor/github.com/vektah/gqlparser/validator/schema_test.yml +++ b/vendor/github.com/vektah/gqlparser/validator/schema_test.yml @@ -10,6 +10,84 @@ types: error: message: "Cannot redeclare type A." locations: [{line: 4, column: 6}] + - name: cannot be duplicated field at same definition 1 + input: | + type A { + name: String + name: String + } + error: + message: "Field A.name can only be defined once." + locations: [{line: 3, column: 3}] + - name: cannot be duplicated field at same definition 2 + input: | + type A { + name: String + } + extend type A { + name: String + } + error: + message: "Field A.name can only be defined once." + locations: [{line: 5, column: 3}] + - name: cannot be duplicated field at same definition 3 + input: | + type A { + name: String + } + extend type A { + age: Int + age: Int + } + error: + message: "Field A.age can only be defined once." + locations: [{line: 6, column: 3}] + +object types: + - name: must define one or more fields + input: | + directive @D on OBJECT + + # This pattern rejected by parser + # type InvalidObject1 {} + + type InvalidObject2 @D + + type ValidObject { + id: ID + } + extend type ValidObject @D + extend type ValidObject { + b: Int + } + error: + message: 'OBJECT must define one or more fields.' + locations: [{line: 6, column: 6}] + - name: check reserved names on type name + input: | + type __FooBar { + id: ID + } + error: + message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 1, column: 6}] + - name: check reserved names on type field + input: | + type FooBar { + __id: ID + } + error: + message: 'Name "__id" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 2, column: 3}] + + - name: check reserved names on type field argument + input: | + type FooBar { + foo(__bar: ID): ID + } + error: + message: 'Name "__bar" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 2, column: 7}] interfaces: - name: must exist @@ -42,6 +120,93 @@ interfaces: message: '"Object" is a non interface type OBJECT.' locations: [{line: 1, column: 6}] + - name: must define one or more fields + input: | + directive @D on INTERFACE + + # This pattern rejected by parser + # interface InvalidInterface1 {} + + interface InvalidInterface2 @D + + interface ValidInterface { + id: ID + } + extend interface ValidInterface @D + extend interface ValidInterface { + b: Int + } + error: + message: 'INTERFACE must define one or more fields.' + locations: [{line: 6, column: 11}] + - name: check reserved names on type name + input: | + interface __FooBar { + id: ID + } + error: + message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 1, column: 11}] + +inputs: + - name: must define one or more input fields + input: | + directive @D on INPUT_OBJECT + + # This pattern rejected by parser + # input InvalidInput1 {} + + input InvalidInput2 @D + + input ValidInput { + id: ID + } + extend input ValidInput @D + extend input ValidInput { + b: Int + } + error: + message: 'INPUT_OBJECT must define one or more input fields.' + locations: [{line: 6, column: 7}] + - name: check reserved names on type name + input: | + input __FooBar { + id: ID + } + error: + message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 1, column: 7}] + +enums: + - name: must define one or more unique enum values + input: | + directive @D on ENUM + + # This pattern rejected by parser + # enum InvalidEmum1 {} + + enum InvalidEnum2 @D + + enum ValidEnum { + FOO + } + extend enum ValidEnum @D + extend enum ValidEnum { + BAR + } + error: + message: 'ENUM must define one or more unique enum values.' + locations: [{line: 6, column: 6}] + - name: check reserved names on type name + input: | + enum __FooBar { + A + B + } + error: + message: 'Name "__FooBar" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 1, column: 6}] + type extensions: - name: cannot extend non existant types input: | @@ -86,6 +251,12 @@ directives: error: message: "Directive A cannot refer to itself." locations: [{line: 1, column: 25}] + - name: check reserved names on type name + input: | + directive @__A on FIELD_DEFINITION + error: + message: 'Name "__A" must not begin with "__", which is reserved by GraphQL introspection.' + locations: [{line: 1, column: 12}] entry points: - name: multiple schema entry points diff --git a/vendor/github.com/vektah/gqlparser/validator/vars.go b/vendor/github.com/vektah/gqlparser/validator/vars.go index 0743f5cc..aaf3a0d1 100644 --- a/vendor/github.com/vektah/gqlparser/validator/vars.go +++ b/vendor/github.com/vektah/gqlparser/validator/vars.go @@ -73,12 +73,19 @@ type varValidator struct { } func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerror.Error { + currentPath := v.path + resetPath := func() { + v.path = currentPath + } + defer resetPath() + if typ.Elem != nil { if val.Kind() != reflect.Slice { return gqlerror.ErrorPathf(v.path, "must be an array") } for i := 0; i < val.Len(); i++ { + resetPath() v.path = append(v.path, i) field := val.Index(i) @@ -92,8 +99,6 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr if err := v.validateVarType(typ.Elem, field); err != nil { return err } - - v.path = v.path[0 : len(v.path)-1] } return nil @@ -150,15 +155,16 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr for _, name := range val.MapKeys() { val.MapIndex(name) fieldDef := def.Fields.ForName(name.String()) - v.path = append(v.path, name) + resetPath() + v.path = append(v.path, name.String()) if fieldDef == nil { return gqlerror.ErrorPathf(v.path, "unknown field") } - v.path = v.path[0 : len(v.path)-1] } for _, fieldDef := range def.Fields { + resetPath() v.path = append(v.path, fieldDef.Name) field := val.MapIndex(reflect.ValueOf(fieldDef.Name)) @@ -184,8 +190,6 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr if err != nil { return err } - - v.path = v.path[0 : len(v.path)-1] } default: panic(fmt.Errorf("unsupported type %s", def.Kind)) |