diff --git a/abstract_test.go b/abstract_test.go index 37f2eb3d..2a356ea6 100644 --- a/abstract_test.go +++ b/abstract_test.go @@ -36,13 +36,13 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForInterface(t *testing.T) { }) // ie declare that Dog belongs to Pet interface - _ = graphql.NewObject(graphql.ObjectConfig{ + dogType := graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", Interfaces: []*graphql.Interface{ petType, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testDog) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testDog) return ok }, Fields: graphql.Fields{ @@ -67,13 +67,13 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForInterface(t *testing.T) { }, }) // ie declare that Cat belongs to Pet interface - _ = graphql.NewObject(graphql.ObjectConfig{ + catType := graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", Interfaces: []*graphql.Interface{ petType, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testCat) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testCat) return ok }, Fields: graphql.Fields{ @@ -112,6 +112,7 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForInterface(t *testing.T) { }, }, }), + Types: []graphql.Type{catType, dogType}, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) @@ -161,8 +162,8 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForUnion(t *testing.T) { dogType := graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testDog) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testDog) return ok }, Fields: graphql.Fields{ @@ -176,8 +177,8 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForUnion(t *testing.T) { }) catType := graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testCat) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testCat) return ok }, Fields: graphql.Fields{ @@ -269,14 +270,14 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { Type: graphql.String, }, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { - if _, ok := value.(*testCat); ok { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + if _, ok := p.Value.(*testCat); ok { return catType } - if _, ok := value.(*testDog); ok { + if _, ok := p.Value.(*testDog); ok { return dogType } - if _, ok := value.(*testHuman); ok { + if _, ok := p.Value.(*testHuman); ok { return humanType } return nil @@ -288,12 +289,6 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - if human, ok := p.Source.(*testHuman); ok { - return human.Name, nil - } - return nil, nil - }, }, }, }) @@ -302,28 +297,12 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { Interfaces: []*graphql.Interface{ petType, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testDog) - return ok - }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - if dog, ok := p.Source.(*testDog); ok { - return dog.Name, nil - } - return nil, nil - }, }, "woofs": &graphql.Field{ Type: graphql.Boolean, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - if dog, ok := p.Source.(*testDog); ok { - return dog.Woofs, nil - } - return nil, nil - }, }, }, }) @@ -332,28 +311,12 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { Interfaces: []*graphql.Interface{ petType, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testCat) - return ok - }, Fields: graphql.Fields{ "name": &graphql.Field{ Type: graphql.String, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - if cat, ok := p.Source.(*testCat); ok { - return cat.Name, nil - } - return nil, nil - }, }, "meows": &graphql.Field{ Type: graphql.Boolean, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - if cat, ok := p.Source.(*testCat); ok { - return cat.Meows, nil - } - return nil, nil - }, }, }, }) @@ -373,6 +336,7 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { }, }, }), + Types: []graphql.Type{catType, dogType}, }) if err != nil { t.Fatalf("Error in schema %v", err.Error()) @@ -405,7 +369,7 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Runtime Object type "Human" is not a possible type for "Pet".`, Locations: []location.SourceLocation{}, }, @@ -461,14 +425,14 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) { Types: []*graphql.Object{ dogType, catType, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { - if _, ok := value.(*testCat); ok { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + if _, ok := p.Value.(*testCat); ok { return catType } - if _, ok := value.(*testDog); ok { + if _, ok := p.Value.(*testDog); ok { return dogType } - if _, ok := value.(*testHuman); ok { + if _, ok := p.Value.(*testHuman); ok { return humanType } return nil @@ -523,7 +487,7 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Runtime Object type "Human" is not a possible type for "Pet".`, Locations: []location.SourceLocation{}, }, diff --git a/definition.go b/definition.go index bf42b530..a3b03d22 100644 --- a/definition.go +++ b/definition.go @@ -77,6 +77,18 @@ func IsOutputType(ttype Type) bool { return false } +// Leaf interface for types that may be leaf values +type Leaf interface { + Name() string + Description() string + String() string + Error() error + Serialize(value interface{}) interface{} +} + +var _ Leaf = (*Scalar)(nil) +var _ Leaf = (*Enum)(nil) + // IsLeafType determines if given type is a leaf value func IsLeafType(ttype Type) bool { named := GetNamed(ttype) @@ -131,14 +143,21 @@ func IsCompositeType(ttype interface{}) bool { // Abstract interface for types that may describe the parent context of a selection set. type Abstract interface { Name() string - ObjectType(value interface{}, info ResolveInfo) *Object - PossibleTypes() []*Object - IsPossibleType(ttype *Object) bool } var _ Abstract = (*Interface)(nil) var _ Abstract = (*Union)(nil) +func IsAbstractType(ttype interface{}) bool { + if _, ok := ttype.(*Interface); ok { + return true + } + if _, ok := ttype.(*Union); ok { + return true + } + return false +} + // Nullable interface for types that can accept null as a value. type Nullable interface { } @@ -350,7 +369,22 @@ type Object struct { err error } -type IsTypeOfFn func(value interface{}, info ResolveInfo) bool +// IsTypeOfParams Params for IsTypeOfFn() +type IsTypeOfParams struct { + // Value that needs to be resolve. + // Use this to decide which GraphQLObject this value maps to. + Value interface{} + + // Info is a collection of information about the current execution state. + Info ResolveInfo + + // Context argument is a context value that is provided to every resolve function within an execution. + // It is commonly + // used to represent an authenticated user, or request-specific caches. + Context context.Context +} + +type IsTypeOfFn func(p IsTypeOfParams) bool type InterfacesThunk func() []*Interface @@ -382,21 +416,6 @@ func NewObject(config ObjectConfig) *Object { objectType.IsTypeOf = config.IsTypeOf objectType.typeConfig = config - /* - addImplementationToInterfaces() - Update the interfaces to know about this implementation. - This is an rare and unfortunate use of mutation in the type definition - implementations, but avoids an expensive "getPossibleTypes" - implementation for Interface - */ - interfaces := objectType.Interfaces() - if interfaces == nil { - return objectType - } - for _, iface := range interfaces { - iface.implementations = append(iface.implementations, objectType) - } - return objectType } func (gt *Object) AddFieldConfig(fieldName string, fieldConfig *Field) { @@ -556,14 +575,19 @@ func defineFieldMap(ttype Named, fields Fields) (FieldDefinitionMap, error) { } // ResolveParams Params for FieldResolveFn() -// TODO: clean up GQLFRParams fields type ResolveParams struct { + // Source is the source value Source interface{} - Args map[string]interface{} - Info ResolveInfo - Schema Schema - //This can be used to provide per-request state - //from the application. + + // Args is a map of arguments for current GraphQL request + Args map[string]interface{} + + // Info is a collection of information about the current execution state. + Info ResolveInfo + + // Context argument is a context value that is provided to every resolve function within an execution. + // It is commonly + // used to represent an authenticated user, or request-specific caches. Context context.Context } @@ -660,12 +684,9 @@ type Interface struct { PrivateDescription string `json:"description"` ResolveType ResolveTypeFn - typeConfig InterfaceConfig - fields FieldDefinitionMap - implementations []*Object - possibleTypes map[string]bool - - err error + typeConfig InterfaceConfig + fields FieldDefinitionMap + err error } type InterfaceConfig struct { Name string `json:"name"` @@ -674,7 +695,22 @@ type InterfaceConfig struct { Description string `json:"description"` } -type ResolveTypeFn func(value interface{}, info ResolveInfo) *Object +// ResolveTypeParams Params for ResolveTypeFn() +type ResolveTypeParams struct { + // Value that needs to be resolve. + // Use this to decide which GraphQLObject this value maps to. + Value interface{} + + // Info is a collection of information about the current execution state. + Info ResolveInfo + + // Context argument is a context value that is provided to every resolve function within an execution. + // It is commonly + // used to represent an authenticated user, or request-specific caches. + Context context.Context +} + +type ResolveTypeFn func(p ResolveTypeParams) *Object func NewInterface(config InterfaceConfig) *Interface { it := &Interface{} @@ -693,7 +729,6 @@ func NewInterface(config InterfaceConfig) *Interface { it.PrivateDescription = config.Description it.ResolveType = config.ResolveType it.typeConfig = config - it.implementations = []*Object{} return it } @@ -726,34 +761,6 @@ func (it *Interface) Fields() (fields FieldDefinitionMap) { it.fields = fields return it.fields } -func (it *Interface) PossibleTypes() []*Object { - return it.implementations -} -func (it *Interface) IsPossibleType(ttype *Object) bool { - if ttype == nil { - return false - } - if len(it.possibleTypes) == 0 { - possibleTypes := map[string]bool{} - for _, possibleType := range it.PossibleTypes() { - if possibleType == nil { - continue - } - possibleTypes[possibleType.PrivateName] = true - } - it.possibleTypes = possibleTypes - } - if val, ok := it.possibleTypes[ttype.PrivateName]; ok { - return val - } - return false -} -func (it *Interface) ObjectType(value interface{}, info ResolveInfo) *Object { - if it.ResolveType != nil { - return it.ResolveType(value, info) - } - return getTypeOf(value, info, it) -} func (it *Interface) String() string { return it.PrivateName } @@ -761,19 +768,6 @@ func (it *Interface) Error() error { return it.err } -func getTypeOf(value interface{}, info ResolveInfo, abstractType Abstract) *Object { - possibleTypes := abstractType.PossibleTypes() - for _, possibleType := range possibleTypes { - if possibleType.IsTypeOf == nil { - continue - } - if res := possibleType.IsTypeOf(value, info); res { - return possibleType - } - } - return nil -} - // Union Type Definition // // When a field can return one of a heterogeneous set of types, a Union type @@ -865,36 +859,9 @@ func NewUnion(config UnionConfig) *Union { return objectType } -func (ut *Union) PossibleTypes() []*Object { +func (ut *Union) Types() []*Object { return ut.types } -func (ut *Union) IsPossibleType(ttype *Object) bool { - - if ttype == nil { - return false - } - if len(ut.possibleTypes) == 0 { - possibleTypes := map[string]bool{} - for _, possibleType := range ut.PossibleTypes() { - if possibleType == nil { - continue - } - possibleTypes[possibleType.PrivateName] = true - } - ut.possibleTypes = possibleTypes - } - - if val, ok := ut.possibleTypes[ttype.PrivateName]; ok { - return val - } - return false -} -func (ut *Union) ObjectType(value interface{}, info ResolveInfo) *Object { - if ut.ResolveType != nil { - return ut.ResolveType(value, info) - } - return getTypeOf(value, info, ut) -} func (ut *Union) String() string { return ut.PrivateName } diff --git a/definition_test.go b/definition_test.go index 363c8024..8b574e2e 100644 --- a/definition_test.go +++ b/definition_test.go @@ -114,7 +114,7 @@ var blogSubscription = graphql.NewObject(graphql.ObjectConfig{ var objectType = graphql.NewObject(graphql.ObjectConfig{ Name: "Object", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, }) @@ -352,7 +352,7 @@ func TestTypeSystem_DefinitionExample_IncludesInterfacesSubTypesInTheTypeMap(t * }, }, Interfaces: []*graphql.Interface{someInterface}, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, }) @@ -365,6 +365,7 @@ func TestTypeSystem_DefinitionExample_IncludesInterfacesSubTypesInTheTypeMap(t * }, }, }), + Types: []graphql.Type{someSubType}, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) @@ -395,7 +396,7 @@ func TestTypeSystem_DefinitionExample_IncludesInterfacesThunkSubtypesInTheTypeMa Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someInterface} }), - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, }) @@ -408,6 +409,7 @@ func TestTypeSystem_DefinitionExample_IncludesInterfacesThunkSubtypesInTheTypeMa }, }, }), + Types: []graphql.Type{someSubType}, }) if err != nil { t.Fatalf("unexpected error, got: %v", err) @@ -424,17 +426,17 @@ func TestTypeSystem_DefinitionExample_StringifiesSimpleTypes(t *testing.T) { expected string } tests := []Test{ - Test{graphql.Int, "Int"}, - Test{blogArticle, "Article"}, - Test{interfaceType, "Interface"}, - Test{unionType, "Union"}, - Test{enumType, "Enum"}, - Test{inputObjectType, "InputObject"}, - Test{graphql.NewNonNull(graphql.Int), "Int!"}, - Test{graphql.NewList(graphql.Int), "[Int]"}, - Test{graphql.NewNonNull(graphql.NewList(graphql.Int)), "[Int]!"}, - Test{graphql.NewList(graphql.NewNonNull(graphql.Int)), "[Int!]"}, - Test{graphql.NewList(graphql.NewList(graphql.Int)), "[[Int]]"}, + {graphql.Int, "Int"}, + {blogArticle, "Article"}, + {interfaceType, "Interface"}, + {unionType, "Union"}, + {enumType, "Enum"}, + {inputObjectType, "InputObject"}, + {graphql.NewNonNull(graphql.Int), "Int!"}, + {graphql.NewList(graphql.Int), "[Int]"}, + {graphql.NewNonNull(graphql.NewList(graphql.Int)), "[Int]!"}, + {graphql.NewList(graphql.NewNonNull(graphql.Int)), "[Int!]"}, + {graphql.NewList(graphql.NewList(graphql.Int)), "[[Int]]"}, } for _, test := range tests { ttypeStr := fmt.Sprintf("%v", test.ttype) @@ -450,12 +452,12 @@ func TestTypeSystem_DefinitionExample_IdentifiesInputTypes(t *testing.T) { expected bool } tests := []Test{ - Test{graphql.Int, true}, - Test{objectType, false}, - Test{interfaceType, false}, - Test{unionType, false}, - Test{enumType, true}, - Test{inputObjectType, true}, + {graphql.Int, true}, + {objectType, false}, + {interfaceType, false}, + {unionType, false}, + {enumType, true}, + {inputObjectType, true}, } for _, test := range tests { ttypeStr := fmt.Sprintf("%v", test.ttype) @@ -477,12 +479,12 @@ func TestTypeSystem_DefinitionExample_IdentifiesOutputTypes(t *testing.T) { expected bool } tests := []Test{ - Test{graphql.Int, true}, - Test{objectType, true}, - Test{interfaceType, true}, - Test{unionType, true}, - Test{enumType, true}, - Test{inputObjectType, false}, + {graphql.Int, true}, + {objectType, true}, + {interfaceType, true}, + {unionType, true}, + {enumType, true}, + {inputObjectType, false}, } for _, test := range tests { ttypeStr := fmt.Sprintf("%v", test.ttype) diff --git a/directives.go b/directives.go index 63411104..676eab75 100644 --- a/directives.go +++ b/directives.go @@ -1,60 +1,113 @@ package graphql +const ( + DirectiveLocationQuery = "QUERY" + DirectiveLocationMutation = "MUTATION" + DirectiveLocationSubscription = "SUBSCRIPTION" + DirectiveLocationField = "FIELD" + DirectiveLocationFragmentDefinition = "FRAGMENT_DEFINITION" + DirectiveLocationFragmentSpread = "FRAGMENT_SPREAD" + DirectiveLocationInlineFragment = "INLINE_FRAGMENT" +) + // Directive structs are used by the GraphQL runtime as a way of modifying execution // behavior. Type system creators will usually not create these directly. type Directive struct { Name string `json:"name"` Description string `json:"description"` + Locations []string `json:"locations"` Args []*Argument `json:"args"` - OnOperation bool `json:"onOperation"` - OnFragment bool `json:"onFragment"` - OnField bool `json:"onField"` + + err error +} + +// DirectiveConfig options for creating a new GraphQLDirective +type DirectiveConfig struct { + Name string `json:"name"` + Description string `json:"description"` + Locations []string `json:"locations"` + Args FieldConfigArgument `json:"args"` } -func NewDirective(config *Directive) *Directive { - if config == nil { - config = &Directive{} +func NewDirective(config DirectiveConfig) *Directive { + dir := &Directive{} + + // Ensure directive is named + err := invariant(config.Name != "", "Directive must be named.") + if err != nil { + dir.err = err + return dir + } + + // Ensure directive name is valid + err = assertValidName(config.Name) + if err != nil { + dir.err = err + return dir } - return &Directive{ - Name: config.Name, - Description: config.Description, - Args: config.Args, - OnOperation: config.OnOperation, - OnFragment: config.OnFragment, - OnField: config.OnField, + + // Ensure locations are provided for directive + err = invariant(len(config.Locations) > 0, "Must provide locations for directive.") + if err != nil { + dir.err = err + return dir + } + + args := []*Argument{} + + for argName, argConfig := range config.Args { + err := assertValidName(argName) + if err != nil { + dir.err = err + return dir + } + args = append(args, &Argument{ + PrivateName: argName, + PrivateDescription: argConfig.Description, + Type: argConfig.Type, + DefaultValue: argConfig.DefaultValue, + }) } + + dir.Name = config.Name + dir.Description = config.Description + dir.Locations = config.Locations + dir.Args = args + return dir } // IncludeDirective is used to conditionally include fields or fragments -var IncludeDirective = NewDirective(&Directive{ +var IncludeDirective = NewDirective(DirectiveConfig{ Name: "include", Description: "Directs the executor to include this field or fragment only when " + "the `if` argument is true.", - Args: []*Argument{ - &Argument{ - PrivateName: "if", - Type: NewNonNull(Boolean), - PrivateDescription: "Included when true.", + Locations: []string{ + DirectiveLocationField, + DirectiveLocationFragmentSpread, + DirectiveLocationInlineFragment, + }, + Args: FieldConfigArgument{ + "if": &ArgumentConfig{ + Type: NewNonNull(Boolean), + Description: "Included when true.", }, }, - OnOperation: false, - OnFragment: true, - OnField: true, }) // SkipDirective Used to conditionally skip (exclude) fields or fragments -var SkipDirective = NewDirective(&Directive{ +var SkipDirective = NewDirective(DirectiveConfig{ Name: "skip", Description: "Directs the executor to skip this field or fragment when the `if` " + "argument is true.", - Args: []*Argument{ - &Argument{ - PrivateName: "if", - Type: NewNonNull(Boolean), - PrivateDescription: "Skipped when true.", + Args: FieldConfigArgument{ + "if": &ArgumentConfig{ + Type: NewNonNull(Boolean), + Description: "Skipped when true.", }, }, - OnOperation: false, - OnFragment: true, - OnField: true, + Locations: []string{ + DirectiveLocationField, + DirectiveLocationFragmentSpread, + DirectiveLocationInlineFragment, + }, }) diff --git a/directives_test.go b/directives_test.go index f376015c..164ecd7b 100644 --- a/directives_test.go +++ b/directives_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/gqlerrors" + "github.com/graphql-go/graphql/language/location" "github.com/graphql-go/graphql/testutil" ) @@ -37,6 +39,120 @@ func executeDirectivesTestQuery(t *testing.T, doc string) *graphql.Result { return testutil.TestExecute(t, ep) } +func TestDirectives_DirectivesMustBeNamed(t *testing.T) { + invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ + Locations: []string{ + graphql.DirectiveLocationField, + }, + }) + _, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "TestType", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Directives: []*graphql.Directive{invalidDirective}, + }) + expectedErr := gqlerrors.FormattedError{ + Message: "Directive must be named.", + Locations: []location.SourceLocation{}, + } + if !reflect.DeepEqual(expectedErr, err) { + t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, err)) + } +} + +func TestDirectives_DirectiveNameMustBeValid(t *testing.T) { + invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ + Name: "123invalid name", + Locations: []string{ + graphql.DirectiveLocationField, + }, + }) + _, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "TestType", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Directives: []*graphql.Directive{invalidDirective}, + }) + expectedErr := gqlerrors.FormattedError{ + Message: `Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "123invalid name" does not.`, + Locations: []location.SourceLocation{}, + } + if !reflect.DeepEqual(expectedErr, err) { + t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, err)) + } +} + +func TestDirectives_DirectiveNameMustProvideLocations(t *testing.T) { + invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ + Name: "skip", + }) + _, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "TestType", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Directives: []*graphql.Directive{invalidDirective}, + }) + expectedErr := gqlerrors.FormattedError{ + Message: `Must provide locations for directive.`, + Locations: []location.SourceLocation{}, + } + if !reflect.DeepEqual(expectedErr, err) { + t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, err)) + } +} + +func TestDirectives_DirectiveArgNamesMustBeValid(t *testing.T) { + invalidDirective := graphql.NewDirective(graphql.DirectiveConfig{ + Name: "skip", + Description: "Directs the executor to skip this field or fragment when the `if` " + + "argument is true.", + Args: graphql.FieldConfigArgument{ + "123if": &graphql.ArgumentConfig{ + Type: graphql.NewNonNull(graphql.Boolean), + Description: "Skipped when true.", + }, + }, + Locations: []string{ + graphql.DirectiveLocationField, + graphql.DirectiveLocationFragmentSpread, + graphql.DirectiveLocationInlineFragment, + }, + }) + _, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "TestType", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Directives: []*graphql.Directive{invalidDirective}, + }) + expectedErr := gqlerrors.FormattedError{ + Message: `Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "123if" does not.`, + Locations: []location.SourceLocation{}, + } + if !reflect.DeepEqual(expectedErr, err) { + t.Fatalf("Expected error to be equal, got: %v", testutil.Diff(expectedErr, err)) + } +} + func TestDirectivesWorksWithoutDirectives(t *testing.T) { query := `{ a, b }` expected := &graphql.Result{ @@ -418,19 +534,12 @@ func TestDirectivesWorksOnAnonymousInlineFragmentUnlessTrueIncludesAnonymousInli } } -func TestDirectivesWorksOnFragmentIfFalseOmitsFragment(t *testing.T) { - query := ` - query Q { - a - ...Frag - } - fragment Frag on TestType @include(if: false) { - b - } - ` +func TestDirectivesWorksWithSkipAndIncludeDirectives_IncludeAndNoSkip(t *testing.T) { + query := `{ a, b @include(if: true) @skip(if: false) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", + "b": "b", }, } result := executeDirectivesTestQuery(t, query) @@ -442,20 +551,11 @@ func TestDirectivesWorksOnFragmentIfFalseOmitsFragment(t *testing.T) { } } -func TestDirectivesWorksOnFragmentIfTrueIncludesFragment(t *testing.T) { - query := ` - query Q { - a - ...Frag - } - fragment Frag on TestType @include(if: true) { - b - } - ` +func TestDirectivesWorksWithSkipAndIncludeDirectives_IncludeAndSkip(t *testing.T) { + query := `{ a, b @include(if: true) @skip(if: true) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", - "b": "b", }, } result := executeDirectivesTestQuery(t, query) @@ -467,20 +567,11 @@ func TestDirectivesWorksOnFragmentIfTrueIncludesFragment(t *testing.T) { } } -func TestDirectivesWorksOnFragmentUnlessFalseIncludesFragment(t *testing.T) { - query := ` - query Q { - a - ...Frag - } - fragment Frag on TestType @skip(if: false) { - b - } - ` +func TestDirectivesWorksWithSkipAndIncludeDirectives_NoIncludeAndSkip(t *testing.T) { + query := `{ a, b @include(if: false) @skip(if: true) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", - "b": "b", }, } result := executeDirectivesTestQuery(t, query) @@ -492,16 +583,8 @@ func TestDirectivesWorksOnFragmentUnlessFalseIncludesFragment(t *testing.T) { } } -func TestDirectivesWorksOnFragmentUnlessTrueOmitsFragment(t *testing.T) { - query := ` - query Q { - a - ...Frag - } - fragment Frag on TestType @skip(if: true) { - b - } - ` +func TestDirectivesWorksWithSkipAndIncludeDirectives_NoIncludeOrSkip(t *testing.T) { + query := `{ a, b @include(if: false) @skip(if: false) }` expected := &graphql.Result{ Data: map[string]interface{}{ "a": "a", diff --git a/enum_type_test.go b/enum_type_test.go index b436dd09..d7f13cec 100644 --- a/enum_type_test.go +++ b/enum_type_test.go @@ -178,7 +178,7 @@ func TestTypeSystem_EnumValues_DoesNotAcceptStringLiterals(t *testing.T) { expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Argument \"fromEnum\" has invalid value \"GREEN\".\nExpected type \"Color\", found \"GREEN\".", Locations: []location.SourceLocation{ {Line: 1, Column: 23}, @@ -208,7 +208,7 @@ func TestTypeSystem_EnumValues_DoesNotAcceptInternalValueInPlaceOfEnumLiteral(t expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Argument \"fromEnum\" has invalid value 1.\nExpected type \"Color\", found 1.", Locations: []location.SourceLocation{ {Line: 1, Column: 23}, @@ -227,7 +227,7 @@ func TestTypeSystem_EnumValues_DoesNotAcceptEnumLiteralInPlaceOfInt(t *testing.T expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Argument \"fromInt\" has invalid value GREEN.\nExpected type \"Int\", found GREEN.", Locations: []location.SourceLocation{ {Line: 1, Column: 23}, @@ -296,7 +296,7 @@ func TestTypeSystem_EnumValues_DoesNotAcceptInternalValueAsEnumVariable(t *testi expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Variable \"$color\" got invalid value 2.\nExpected type \"Color\", found \"2\".", Locations: []location.SourceLocation{ {Line: 1, Column: 12}, @@ -317,7 +317,7 @@ func TestTypeSystem_EnumValues_DoesNotAcceptStringVariablesAsEnumInput(t *testin expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$color" of type "String!" used in position expecting type "Color".`, }, }, @@ -335,7 +335,7 @@ func TestTypeSystem_EnumValues_DoesNotAcceptInternalValueVariableAsEnumInput(t * expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$color" of type "Int!" used in position expecting type "Color".`, }, }, diff --git a/executor.go b/executor.go index 55a5869c..138b0c42 100644 --- a/executor.go +++ b/executor.go @@ -134,10 +134,10 @@ func buildExecutionContext(p BuildExecutionCtxParams) (*ExecutionContext, error) } if operation == nil { - if p.OperationName == "" { + if p.OperationName != "" { return nil, fmt.Errorf(`Unknown operation named "%v".`, p.OperationName) } - return nil, fmt.Errorf(`Must provide an operation`) + return nil, fmt.Errorf(`Must provide an operation.`) } variableValues, err := getVariableValues(p.Schema, operation.GetVariableDefinitions(), p.Args) @@ -180,7 +180,7 @@ func executeOperation(p ExecuteOperationParams) *Result { Fields: fields, } - if p.Operation.GetOperation() == "mutation" { + if p.Operation.GetOperation() == ast.OperationTypeMutation { return executeFieldsSerially(executeFieldsParams) } return executeFields(executeFieldsParams) @@ -194,9 +194,9 @@ func getOperationRootType(schema Schema, operation ast.Definition) (*Object, err } switch operation.GetOperation() { - case "query": + case ast.OperationTypeQuery: return schema.QueryType(), nil - case "mutation": + case ast.OperationTypeMutation: mutationType := schema.MutationType() if mutationType.PrivateName == "" { return nil, gqlerrors.NewError( @@ -209,7 +209,7 @@ func getOperationRootType(schema Schema, operation ast.Definition) (*Object, err ) } return mutationType, nil - case "subscription": + case ast.OperationTypeSubscription: subscriptionType := schema.SubscriptionType() if subscriptionType.PrivateName == "" { return nil, gqlerrors.NewError( @@ -392,8 +392,7 @@ func collectFields(p CollectFieldsParams) map[string][]*ast.Field { } if fragment, ok := fragment.(*ast.FragmentDefinition); ok { - if !shouldIncludeNode(p.ExeContext, fragment.Directives) || - !doesFragmentConditionMatch(p.ExeContext, fragment, p.RuntimeType) { + if !doesFragmentConditionMatch(p.ExeContext, fragment, p.RuntimeType) { continue } innerParams := CollectFieldsParams{ @@ -436,12 +435,11 @@ func shouldIncludeNode(eCtx *ExecutionContext, directives []*ast.Directive) bool if err != nil { return defaultReturnValue } - if skipIf, ok := argValues["if"]; ok { - if boolSkipIf, ok := skipIf.(bool); ok { - return !boolSkipIf + if skipIf, ok := argValues["if"].(bool); ok { + if skipIf == true { + return false } } - return defaultReturnValue } for _, directive := range directives { if directive == nil || directive.Name == nil { @@ -461,12 +459,11 @@ func shouldIncludeNode(eCtx *ExecutionContext, directives []*ast.Directive) bool if err != nil { return defaultReturnValue } - if includeIf, ok := argValues["if"]; ok { - if boolIncludeIf, ok := includeIf.(bool); ok { - return boolIncludeIf + if includeIf, ok := argValues["if"].(bool); ok { + if includeIf == false { + return false } } - return defaultReturnValue } return defaultReturnValue } @@ -490,8 +487,11 @@ func doesFragmentConditionMatch(eCtx *ExecutionContext, fragment ast.Node, ttype if conditionalType.Name() == ttype.Name() { return true } - if conditionalType, ok := conditionalType.(Abstract); ok { - return conditionalType.IsPossibleType(ttype) + if conditionalType, ok := conditionalType.(*Interface); ok { + return eCtx.Schema.IsPossibleType(conditionalType, ttype) + } + if conditionalType, ok := conditionalType.(*Union); ok { + return eCtx.Schema.IsPossibleType(conditionalType, ttype) } case *ast.InlineFragment: typeConditionAST := fragment.TypeCondition @@ -508,8 +508,11 @@ func doesFragmentConditionMatch(eCtx *ExecutionContext, fragment ast.Node, ttype if conditionalType.Name() == ttype.Name() { return true } - if conditionalType, ok := conditionalType.(Abstract); ok { - return conditionalType.IsPossibleType(ttype) + if conditionalType, ok := conditionalType.(*Interface); ok { + return eCtx.Schema.IsPossibleType(conditionalType, ttype) + } + if conditionalType, ok := conditionalType.(*Union); ok { + return eCtx.Schema.IsPossibleType(conditionalType, ttype) } } @@ -584,8 +587,6 @@ func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{} // TODO: find a way to memoize, in case this field is within a List type. args, _ := getArgumentValues(fieldDef.Args, fieldAST.Arguments, eCtx.VariableValues) - // The resolve function's optional third argument is a collection of - // information about the current execution state. info := ResolveInfo{ FieldName: fieldName, FieldASTs: fieldASTs, @@ -637,21 +638,11 @@ func completeValueCatchingError(eCtx *ExecutionContext, returnType Type, fieldAS return completed } completed = completeValue(eCtx, returnType, fieldASTs, info, result) - resultVal := reflect.ValueOf(completed) - if resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func { - if propertyFn, ok := completed.(func() interface{}); ok { - return propertyFn() - } - err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` signature") - panic(gqlerrors.FormatError(err)) - } return completed } func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { - // TODO: explore resolving go-routines in completeValue - resultVal := reflect.ValueOf(result) if resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func { if propertyFn, ok := result.(func() interface{}); ok { @@ -661,6 +652,8 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie panic(gqlerrors.FormatError(err)) } + // If field type is NonNull, complete for inner type, and throw field error + // if result is null. if returnType, ok := returnType.(*NonNull); ok { completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, result) if completed == nil { @@ -673,79 +666,102 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie return completed } + // If result value is null-ish (null, undefined, or NaN) then return null. if isNullish(result) { return nil } // If field type is List, complete each item in the list with the inner type if returnType, ok := returnType.(*List); ok { - - resultVal := reflect.ValueOf(result) - parentTypeName := "" - if info.ParentType != nil { - parentTypeName = info.ParentType.Name() - } - err := invariant( - resultVal.IsValid() && resultVal.Type().Kind() == reflect.Slice, - fmt.Sprintf("User Error: expected iterable, but did not find one "+ - "for field %v.%v.", parentTypeName, info.FieldName), - ) - if err != nil { - panic(gqlerrors.FormatError(err)) - } - - itemType := returnType.OfType - completedResults := []interface{}{} - for i := 0; i < resultVal.Len(); i++ { - val := resultVal.Index(i).Interface() - completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val) - completedResults = append(completedResults, completedItem) - } - return completedResults + return completeListValue(eCtx, returnType, fieldASTs, info, result) } - // If field type is Scalar or Enum, serialize to a valid value, returning - // null if serialization is not possible. + // If field type is a leaf type, Scalar or Enum, serialize to a valid value, + // returning null if serialization is not possible. if returnType, ok := returnType.(*Scalar); ok { - serializedResult := returnType.Serialize(result) - if isNullish(serializedResult) { - return nil - } - return serializedResult + return completeLeafValue(returnType, result) } if returnType, ok := returnType.(*Enum); ok { - serializedResult := returnType.Serialize(result) - if isNullish(serializedResult) { - return nil - } - return serializedResult + return completeLeafValue(returnType, result) + } + + // If field type is an abstract type, Interface or Union, determine the + // runtime Object type and complete for that type. + if returnType, ok := returnType.(*Union); ok { + return completeAbstractValue(eCtx, returnType, fieldASTs, info, result) + } + if returnType, ok := returnType.(*Interface); ok { + return completeAbstractValue(eCtx, returnType, fieldASTs, info, result) + } + + // If field type is Object, execute and complete all sub-selections. + if returnType, ok := returnType.(*Object); ok { + return completeObjectValue(eCtx, returnType, fieldASTs, info, result) + } + + // Not reachable. All possible output types have been considered. + err := invariant(false, + fmt.Sprintf(`Cannot complete value of unexpected type "%v."`, returnType), + ) + if err != nil { + panic(gqlerrors.FormatError(err)) } + return nil +} + +// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type +// of that value, then completing based on that type. +func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { - // ast.Field type must be Object, Interface or Union and expect sub-selections. var runtimeType *Object - switch returnType := returnType.(type) { - case *Object: - runtimeType = returnType - case Abstract: - runtimeType = returnType.ObjectType(result, info) - if runtimeType != nil && !returnType.IsPossibleType(runtimeType) { - panic(gqlerrors.NewFormattedError( - fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+ - `for "%v".`, runtimeType, returnType), - )) - } + + resolveTypeParams := ResolveTypeParams{ + Value: result, + Info: info, + Context: eCtx.Context, } - if runtimeType == nil { - return nil + if unionReturnType, ok := returnType.(*Union); ok && unionReturnType.ResolveType != nil { + runtimeType = unionReturnType.ResolveType(resolveTypeParams) + } else if interfaceReturnType, ok := returnType.(*Interface); ok && interfaceReturnType.ResolveType != nil { + runtimeType = interfaceReturnType.ResolveType(resolveTypeParams) + } else { + runtimeType = defaultResolveTypeFn(resolveTypeParams, returnType) } + err := invariant(runtimeType != nil, + fmt.Sprintf(`Could not determine runtime type of value "%v" for field %v.%v.`, result, info.ParentType, info.FieldName), + ) + if err != nil { + panic(err) + } + + if !eCtx.Schema.IsPossibleType(returnType, runtimeType) { + panic(gqlerrors.NewFormattedError( + fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+ + `for "%v".`, runtimeType, returnType), + )) + } + + return completeObjectValue(eCtx, runtimeType, fieldASTs, info, result) +} + +// completeObjectValue complete an Object value by executing all sub-selections. +func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { + // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. - if runtimeType.IsTypeOf != nil && !runtimeType.IsTypeOf(result, info) { - panic(gqlerrors.NewFormattedError( - fmt.Sprintf(`Expected value of type "%v" but got: %T.`, runtimeType, result), - )) + if returnType.IsTypeOf != nil { + p := IsTypeOfParams{ + Value: result, + Info: info, + Context: eCtx.Context, + } + if !returnType.IsTypeOf(p) { + panic(gqlerrors.NewFormattedError( + fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, result), + )) + } } // Collect sub-fields to execute to complete this value. @@ -759,7 +775,7 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie if selectionSet != nil { innerParams := CollectFieldsParams{ ExeContext: eCtx, - RuntimeType: runtimeType, + RuntimeType: returnType, SelectionSet: selectionSet, Fields: subFieldASTs, VisitedFragmentNames: visitedFragmentNames, @@ -769,7 +785,7 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie } executeFieldsParams := ExecuteFieldsParams{ ExecutionContext: eCtx, - ParentType: runtimeType, + ParentType: returnType, Source: result, Fields: subFieldASTs, } @@ -779,6 +795,66 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie } +// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible. +func completeLeafValue(returnType Leaf, result interface{}) interface{} { + serializedResult := returnType.Serialize(result) + if isNullish(serializedResult) { + return nil + } + return serializedResult +} + +// completeListValue complete a list value by completing each item in the list with the inner type +func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { + resultVal := reflect.ValueOf(result) + parentTypeName := "" + if info.ParentType != nil { + parentTypeName = info.ParentType.Name() + } + err := invariant( + resultVal.IsValid() && resultVal.Type().Kind() == reflect.Slice, + fmt.Sprintf("User Error: expected iterable, but did not find one "+ + "for field %v.%v.", parentTypeName, info.FieldName), + ) + if err != nil { + panic(gqlerrors.FormatError(err)) + } + + itemType := returnType.OfType + completedResults := []interface{}{} + for i := 0; i < resultVal.Len(); i++ { + val := resultVal.Index(i).Interface() + completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val) + completedResults = append(completedResults, completedItem) + } + return completedResults +} + +// defaultResolveTypeFn If a resolveType function is not given, then a default resolve behavior is +// used which tests each possible type for the abstract type by calling +// isTypeOf for the object being coerced, returning the first type that matches. +func defaultResolveTypeFn(p ResolveTypeParams, abstractType Abstract) *Object { + possibleTypes := p.Info.Schema.PossibleTypes(abstractType) + for _, possibleType := range possibleTypes { + if possibleType.IsTypeOf == nil { + continue + } + isTypeOfParams := IsTypeOfParams{ + Value: p.Value, + Info: p.Info, + Context: p.Context, + } + if res := possibleType.IsTypeOf(isTypeOfParams); res { + return possibleType + } + } + return nil +} + +// defaultResolveFn If a resolve function is not given, then a default resolve behavior is used +// which takes the property of the source object of the same name as the field +// and returns it as the result, or if it's a function, returns the result +// of calling that function. func defaultResolveFn(p ResolveParams) (interface{}, error) { // try to resolve p.Source as a struct first sourceVal := reflect.ValueOf(p.Source) @@ -832,7 +908,7 @@ func defaultResolveFn(p ResolveParams) (interface{}, error) { return nil, nil } -// This method looks up the field on the given type defintion. +// This method looks up the field on the given type definition. // It has special casing for the two introspection fields, __schema // and __typename. __typename is special because it can always be // queried as a field, even in situations where no other fields diff --git a/executor_resolve_test.go b/executor_resolve_test.go new file mode 100644 index 00000000..1617e0d0 --- /dev/null +++ b/executor_resolve_test.go @@ -0,0 +1,266 @@ +package graphql_test + +import ( + "encoding/json" + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/testutil" + "reflect" + "testing" +) + +func testSchema(t *testing.T, testField *graphql.Field) graphql.Schema { + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ + "test": testField, + }, + }), + }) + if err != nil { + t.Fatalf("Invalid schema: %v", err) + } + return schema +} + +func TestExecutesResolveFunction_DefaultFunctionAccessesProperties(t *testing.T) { + schema := testSchema(t, &graphql.Field{Type: graphql.String}) + + source := map[string]interface{}{ + "test": "testValue", + } + + expected := map[string]interface{}{ + "test": "testValue", + } + + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test }`, + RootObject: source, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } +} + +func TestExecutesResolveFunction_DefaultFunctionCallsMethods(t *testing.T) { + schema := testSchema(t, &graphql.Field{Type: graphql.String}) + + source := map[string]interface{}{ + "test": func() interface{} { + return "testValue" + }, + } + + expected := map[string]interface{}{ + "test": "testValue", + } + + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test }`, + RootObject: source, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } +} + +func TestExecutesResolveFunction_UsesProvidedResolveFunction(t *testing.T) { + schema := testSchema(t, &graphql.Field{ + Type: graphql.String, + Args: graphql.FieldConfigArgument{ + "aStr": &graphql.ArgumentConfig{Type: graphql.String}, + "aInt": &graphql.ArgumentConfig{Type: graphql.Int}, + }, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + b, err := json.Marshal(p.Args) + return string(b), err + }, + }) + + expected := map[string]interface{}{ + "test": "{}", + } + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } + + expected = map[string]interface{}{ + "test": `{"aStr":"String!"}`, + } + result = graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test(aStr: "String!") }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } + + expected = map[string]interface{}{ + "test": `{"aInt":-123,"aStr":"String!"}`, + } + result = graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test(aInt: -123, aStr: "String!") }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } +} + +func TestExecutesResolveFunction_UsesProvidedResolveFunction_SourceIsStruct_WithoutJSONTags(t *testing.T) { + + // For structs without JSON tags, it will map to upper-cased exported field names + type SubObjectWithoutJSONTags struct { + Str string + Int int + } + + schema := testSchema(t, &graphql.Field{ + Type: graphql.NewObject(graphql.ObjectConfig{ + Name: "SubObject", + Description: "Maps GraphQL Object `SubObject` to Go struct `SubObjectWithoutJSONTags`", + Fields: graphql.Fields{ + "Str": &graphql.Field{Type: graphql.String}, + "Int": &graphql.Field{Type: graphql.Int}, + }, + }), + Args: graphql.FieldConfigArgument{ + "aStr": &graphql.ArgumentConfig{Type: graphql.String}, + "aInt": &graphql.ArgumentConfig{Type: graphql.Int}, + }, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + aStr, _ := p.Args["aStr"].(string) + aInt, _ := p.Args["aInt"].(int) + return &SubObjectWithoutJSONTags{ + Str: aStr, + Int: aInt, + }, nil + }, + }) + + expected := map[string]interface{}{ + "test": map[string]interface{}{ + "Str": nil, + "Int": 0, + }, + } + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test { Str, Int } }`, + }) + + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } + + expected = map[string]interface{}{ + "test": map[string]interface{}{ + "Str": "String!", + "Int": 0, + }, + } + result = graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test(aStr: "String!") { Str, Int } }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } + + expected = map[string]interface{}{ + "test": map[string]interface{}{ + "Str": "String!", + "Int": -123, + }, + } + result = graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test(aInt: -123, aStr: "String!") { Str, Int } }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } +} + +func TestExecutesResolveFunction_UsesProvidedResolveFunction_SourceIsStruct_WithJSONTags(t *testing.T) { + + // For structs without JSON tags, it will map to upper-cased exported field names + type SubObjectWithJSONTags struct { + OtherField string `json:""` + Str string `json:"str"` + Int int `json:"int"` + } + + schema := testSchema(t, &graphql.Field{ + Type: graphql.NewObject(graphql.ObjectConfig{ + Name: "SubObject", + Description: "Maps GraphQL Object `SubObject` to Go struct `SubObjectWithJSONTags`", + Fields: graphql.Fields{ + "str": &graphql.Field{Type: graphql.String}, + "int": &graphql.Field{Type: graphql.Int}, + }, + }), + Args: graphql.FieldConfigArgument{ + "aStr": &graphql.ArgumentConfig{Type: graphql.String}, + "aInt": &graphql.ArgumentConfig{Type: graphql.Int}, + }, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + aStr, _ := p.Args["aStr"].(string) + aInt, _ := p.Args["aInt"].(int) + return &SubObjectWithJSONTags{ + Str: aStr, + Int: aInt, + }, nil + }, + }) + + expected := map[string]interface{}{ + "test": map[string]interface{}{ + "str": nil, + "int": 0, + }, + } + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test { str, int } }`, + }) + + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } + + expected = map[string]interface{}{ + "test": map[string]interface{}{ + "str": "String!", + "int": 0, + }, + } + result = graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test(aStr: "String!") { str, int } }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } + + expected = map[string]interface{}{ + "test": map[string]interface{}{ + "str": "String!", + "int": -123, + }, + } + result = graphql.Do(graphql.Params{ + Schema: schema, + RequestString: `{ test(aInt: -123, aStr: "String!") { str, int } }`, + }) + if !reflect.DeepEqual(expected, result.Data) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result.Data)) + } +} diff --git a/executor_test.go b/executor_test.go index f7915ec6..a8cbecf3 100644 --- a/executor_test.go +++ b/executor_test.go @@ -405,6 +405,56 @@ func TestCorrectlyThreadsArguments(t *testing.T) { } } +func TestThreadsRootValueContextCorrectly(t *testing.T) { + + query := ` + query Example { a } + ` + + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "Type", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + val, _ := p.Info.RootValue.(map[string]interface{})["stringKey"].(string) + return val, nil + }, + }, + }, + }), + }) + if err != nil { + t.Fatalf("Error in schema %v", err.Error()) + } + + // parse query + ast := testutil.TestParse(t, query) + + // execute + ep := graphql.ExecuteParams{ + Schema: schema, + AST: ast, + Root: map[string]interface{}{ + "stringKey": "stringValue", + }, + } + result := testutil.TestExecute(t, ep) + if len(result.Errors) > 0 { + t.Fatalf("wrong result, unexpected errors: %v", result.Errors) + } + + expected := &graphql.Result{ + Data: map[string]interface{}{ + "a": "stringValue", + }, + } + if !reflect.DeepEqual(expected, result) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) + } +} + func TestThreadsContextCorrectly(t *testing.T) { query := ` @@ -465,10 +515,10 @@ func TestNullsOutErrorSubtrees(t *testing.T) { "syncError": nil, } expectedErrors := []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Error getting syncError", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 3, Column: 7, }, }, @@ -521,7 +571,7 @@ func TestNullsOutErrorSubtrees(t *testing.T) { } } -func TestUsesTheInlineOperationIfNoOperationIsProvided(t *testing.T) { +func TestUsesTheInlineOperationIfNoOperationNameIsProvided(t *testing.T) { doc := `{ a }` data := map[string]interface{}{ @@ -566,7 +616,7 @@ func TestUsesTheInlineOperationIfNoOperationIsProvided(t *testing.T) { } } -func TestUsesTheOnlyOperationIfNoOperationIsProvided(t *testing.T) { +func TestUsesTheOnlyOperationIfNoOperationNameIsProvided(t *testing.T) { doc := `query Example { a }` data := map[string]interface{}{ @@ -611,7 +661,100 @@ func TestUsesTheOnlyOperationIfNoOperationIsProvided(t *testing.T) { } } -func TestThrowsIfNoOperationIsProvidedWithMultipleOperations(t *testing.T) { +func TestUsesTheNamedOperationIfOperationNameIsProvided(t *testing.T) { + + doc := `query Example { first: a } query OtherExample { second: a }` + data := map[string]interface{}{ + "a": "b", + } + + expected := &graphql.Result{ + Data: map[string]interface{}{ + "second": "b", + }, + } + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "Type", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + }) + if err != nil { + t.Fatalf("Error in schema %v", err.Error()) + } + + // parse query + ast := testutil.TestParse(t, doc) + + // execute + ep := graphql.ExecuteParams{ + Schema: schema, + AST: ast, + Root: data, + OperationName: "OtherExample", + } + result := testutil.TestExecute(t, ep) + if len(result.Errors) > 0 { + t.Fatalf("wrong result, unexpected errors: %v", result.Errors) + } + if !reflect.DeepEqual(expected, result) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) + } +} + +func TestThrowsIfNoOperationIsProvided(t *testing.T) { + + doc := `fragment Example on Type { a }` + data := map[string]interface{}{ + "a": "b", + } + + expectedErrors := []gqlerrors.FormattedError{ + { + Message: "Must provide an operation.", + Locations: []location.SourceLocation{}, + }, + } + + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "Type", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + }) + if err != nil { + t.Fatalf("Error in schema %v", err.Error()) + } + + // parse query + ast := testutil.TestParse(t, doc) + + // execute + ep := graphql.ExecuteParams{ + Schema: schema, + AST: ast, + Root: data, + } + result := testutil.TestExecute(t, ep) + if len(result.Errors) != 1 { + t.Fatalf("wrong result, expected len(1) unexpected len: %v", len(result.Errors)) + } + if result.Data != nil { + t.Fatalf("wrong result, expected nil result.Data, got %v", result.Data) + } + if !reflect.DeepEqual(expectedErrors, result.Errors) { + t.Fatalf("unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) + } +} +func TestThrowsIfNoOperationNameIsProvidedWithMultipleOperations(t *testing.T) { doc := `query Example { a } query OtherExample { a }` data := map[string]interface{}{ @@ -619,7 +762,7 @@ func TestThrowsIfNoOperationIsProvidedWithMultipleOperations(t *testing.T) { } expectedErrors := []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Must provide operation name if query contains multiple operations.", Locations: []location.SourceLocation{}, }, @@ -660,6 +803,52 @@ func TestThrowsIfNoOperationIsProvidedWithMultipleOperations(t *testing.T) { } } +func TestThrowsIfUnknownOperationNameIsProvided(t *testing.T) { + + doc := `query Example { a } query OtherExample { a }` + data := map[string]interface{}{ + "a": "b", + } + + expectedErrors := []gqlerrors.FormattedError{ + { + Message: `Unknown operation named "UnknownExample".`, + Locations: []location.SourceLocation{}, + }, + } + + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "Type", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + }) + if err != nil { + t.Fatalf("Error in schema %v", err.Error()) + } + + // parse query + ast := testutil.TestParse(t, doc) + + // execute + ep := graphql.ExecuteParams{ + Schema: schema, + AST: ast, + Root: data, + OperationName: "UnknownExample", + } + result := testutil.TestExecute(t, ep) + if result.Data != nil { + t.Fatalf("wrong result, expected nil result.Data, got %v", result.Data) + } + if !reflect.DeepEqual(expectedErrors, result.Errors) { + t.Fatalf("unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) + } +} func TestUsesTheQuerySchemaForQueries(t *testing.T) { doc := `query Q { a } mutation M { c } subscription S { a }` @@ -1113,7 +1302,7 @@ func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Expected value of type "SpecialType" but got: graphql_test.testNotSpecialType.`, Locations: []location.SourceLocation{}, }, @@ -1122,8 +1311,8 @@ func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) { specialType := graphql.NewObject(graphql.ObjectConfig{ Name: "SpecialType", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - if _, ok := value.(testSpecialType); ok { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + if _, ok := p.Value.(testSpecialType); ok { return true } return false @@ -1182,7 +1371,7 @@ func TestFailsToExecuteQueryContainingATypeDefinition(t *testing.T) { expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "GraphQL cannot execute a request containing a ObjectDefinition", Locations: []location.SourceLocation{}, }, diff --git a/graphql_test.go b/graphql_test.go index d7d59105..365d11bd 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -19,7 +19,7 @@ var Tests = []T{} func init() { Tests = []T{ - T{ + { Query: ` query HeroNameQuery { hero { @@ -36,7 +36,7 @@ func init() { }, }, }, - T{ + { Query: ` query HeroNameAndFriendsQuery { hero { diff --git a/introspection.go b/introspection.go index 99e7c04e..e837be2e 100644 --- a/introspection.go +++ b/introspection.go @@ -27,6 +27,7 @@ var inputValueType *Object var enumValueType *Object var typeKindEnum *Enum +var directiveLocationEnum *Enum var SchemaMetaFieldDef *FieldDefinition var TypeMetaFieldDef *FieldDefinition @@ -80,6 +81,42 @@ func init() { }, }) + directiveLocationEnum = NewEnum(EnumConfig{ + Name: "__DirectiveLocation", + Description: "A Directive can be adjacent to many parts of the GraphQL language, a " + + "__DirectiveLocation describes one such possible adjacencies.", + Values: EnumValueConfigMap{ + "QUERY": &EnumValueConfig{ + Value: DirectiveLocationQuery, + Description: "Location adjacent to a query operation.", + }, + "MUTATION": &EnumValueConfig{ + Value: DirectiveLocationMutation, + Description: "Location adjacent to a mutation operation.", + }, + "SUBSCRIPTION": &EnumValueConfig{ + Value: DirectiveLocationSubscription, + Description: "Location adjacent to a subscription operation.", + }, + "FIELD": &EnumValueConfig{ + Value: DirectiveLocationField, + Description: "Location adjacent to a field.", + }, + "FRAGMENT_DEFINITION": &EnumValueConfig{ + Value: DirectiveLocationFragmentDefinition, + Description: "Location adjacent to a fragment definition.", + }, + "FRAGMENT_SPREAD": &EnumValueConfig{ + Value: DirectiveLocationFragmentSpread, + Description: "Location adjacent to a fragment spread.", + }, + "INLINE_FRAGMENT": &EnumValueConfig{ + Value: DirectiveLocationInlineFragment, + Description: "Location adjacent to an inline fragment.", + }, + }, + }) + // Note: some fields (for e.g "fields", "interfaces") are defined later due to cyclic reference typeType = NewObject(ObjectConfig{ Name: "__Type", @@ -228,19 +265,72 @@ func init() { "description": &Field{ Type: String, }, + "locations": &Field{ + Type: NewNonNull(NewList( + NewNonNull(directiveLocationEnum), + )), + }, "args": &Field{ Type: NewNonNull(NewList( NewNonNull(inputValueType), )), }, + // NOTE: the following three fields are deprecated and are no longer part + // of the GraphQL specification. "onOperation": &Field{ - Type: NewNonNull(Boolean), + DeprecationReason: "Use `locations`.", + Type: NewNonNull(Boolean), + Resolve: func(p ResolveParams) (interface{}, error) { + if dir, ok := p.Source.(*Directive); ok { + res := false + for _, loc := range dir.Locations { + if loc == DirectiveLocationQuery || + loc == DirectiveLocationMutation || + loc == DirectiveLocationSubscription { + res = true + break + } + } + return res, nil + } + return false, nil + }, }, "onFragment": &Field{ - Type: NewNonNull(Boolean), + DeprecationReason: "Use `locations`.", + Type: NewNonNull(Boolean), + Resolve: func(p ResolveParams) (interface{}, error) { + if dir, ok := p.Source.(*Directive); ok { + res := false + for _, loc := range dir.Locations { + if loc == DirectiveLocationFragmentSpread || + loc == DirectiveLocationInlineFragment || + loc == DirectiveLocationFragmentDefinition { + res = true + break + } + } + return res, nil + } + return false, nil + }, }, "onField": &Field{ - Type: NewNonNull(Boolean), + DeprecationReason: "Use `locations`.", + Type: NewNonNull(Boolean), + Resolve: func(p ResolveParams) (interface{}, error) { + if dir, ok := p.Source.(*Directive); ok { + res := false + for _, loc := range dir.Locations { + if loc == DirectiveLocationField { + res = true + break + } + } + return res, nil + } + return false, nil + }, }, }, }) @@ -401,9 +491,9 @@ func init() { Resolve: func(p ResolveParams) (interface{}, error) { switch ttype := p.Source.(type) { case *Interface: - return ttype.PossibleTypes(), nil + return p.Info.Schema.PossibleTypes(ttype), nil case *Union: - return ttype.PossibleTypes(), nil + return p.Info.Schema.PossibleTypes(ttype), nil } return nil, nil }, @@ -469,7 +559,7 @@ func init() { Type: typeType, Description: "Request the type information of a single type.", Args: []*Argument{ - &Argument{ + { PrivateName: "name", Type: NewNonNull(String), }, diff --git a/introspection_test.go b/introspection_test.go index 6425b9db..3b4f2a46 100644 --- a/introspection_test.go +++ b/introspection_test.go @@ -626,6 +626,28 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { "isDeprecated": false, "deprecationReason": nil, }, + map[string]interface{}{ + "name": "locations", + "args": []interface{}{}, + "type": map[string]interface{}{ + "kind": "NON_NULL", + "name": nil, + "ofType": map[string]interface{}{ + "kind": "LIST", + "name": nil, + "ofType": map[string]interface{}{ + "kind": "NON_NULL", + "name": nil, + "ofType": map[string]interface{}{ + "kind": "ENUM", + "name": "__DirectiveLocation", + }, + }, + }, + }, + "isDeprecated": false, + "deprecationReason": nil, + }, map[string]interface{}{ "name": "args", "args": []interface{}{}, @@ -660,8 +682,8 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { "ofType": nil, }, }, - "isDeprecated": false, - "deprecationReason": nil, + "isDeprecated": true, + "deprecationReason": "Use `locations`.", }, map[string]interface{}{ "name": "onFragment", @@ -675,8 +697,8 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { "ofType": nil, }, }, - "isDeprecated": false, - "deprecationReason": nil, + "isDeprecated": true, + "deprecationReason": "Use `locations`.", }, map[string]interface{}{ "name": "onField", @@ -690,8 +712,8 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { "ofType": nil, }, }, - "isDeprecated": false, - "deprecationReason": nil, + "isDeprecated": true, + "deprecationReason": "Use `locations`.", }, }, "inputFields": nil, @@ -699,10 +721,60 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { "enumValues": nil, "possibleTypes": nil, }, + map[string]interface{}{ + "kind": "ENUM", + "name": "__DirectiveLocation", + "fields": nil, + "inputFields": nil, + "interfaces": nil, + "enumValues": []interface{}{ + map[string]interface{}{ + "name": "QUERY", + "isDeprecated": false, + "deprecationReason": nil, + }, + map[string]interface{}{ + "name": "MUTATION", + "isDeprecated": false, + "deprecationReason": nil, + }, + map[string]interface{}{ + "name": "SUBSCRIPTION", + "isDeprecated": false, + "deprecationReason": nil, + }, + map[string]interface{}{ + "name": "FIELD", + "isDeprecated": false, + "deprecationReason": nil, + }, + map[string]interface{}{ + "name": "FRAGMENT_DEFINITION", + "isDeprecated": false, + "deprecationReason": nil, + }, + map[string]interface{}{ + "name": "FRAGMENT_SPREAD", + "isDeprecated": false, + "deprecationReason": nil, + }, + map[string]interface{}{ + "name": "INLINE_FRAGMENT", + "isDeprecated": false, + "deprecationReason": nil, + }, + }, + "possibleTypes": nil, + }, }, "directives": []interface{}{ map[string]interface{}{ "name": "include", + "locations": []interface{}{ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT", + }, "args": []interface{}{ map[string]interface{}{ "defaultValue": nil, @@ -718,12 +790,18 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { }, }, }, + // deprecated, but included for coverage till removed "onOperation": false, "onFragment": true, "onField": true, }, map[string]interface{}{ "name": "skip", + "locations": []interface{}{ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT", + }, "args": []interface{}{ map[string]interface{}{ "defaultValue": nil, @@ -739,6 +817,7 @@ func TestIntrospection_ExecutesAnIntrospectionQuery(t *testing.T) { }, }, }, + // deprecated, but included for coverage till removed "onOperation": false, "onFragment": true, "onField": true, @@ -1222,11 +1301,11 @@ func TestIntrospection_FailsAsExpectedOnThe__TypeRootFieldWithoutAnArg(t *testin ` expected := &graphql.Result{ Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Field "__type" argument "name" of type "String!" ` + `is required but not provided.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 3, Column: 9}, + {Line: 3, Column: 9}, }, }, }, diff --git a/language/ast/definitions.go b/language/ast/definitions.go index a619e78d..c224a9fd 100644 --- a/language/ast/definitions.go +++ b/language/ast/definitions.go @@ -5,7 +5,6 @@ import ( ) type Definition interface { - // TODO: determine the minimal set of interface for `Definition` GetOperation() string GetVariableDefinitions() []*VariableDefinition GetSelectionSet() *SelectionSet @@ -16,8 +15,14 @@ type Definition interface { // Ensure that all definition types implements Definition interface var _ Definition = (*OperationDefinition)(nil) var _ Definition = (*FragmentDefinition)(nil) -var _ Definition = (*TypeExtensionDefinition)(nil) -var _ Definition = (Definition)(nil) +var _ Definition = (TypeSystemDefinition)(nil) // experimental non-spec addition. + +// Note: subscription is an experimental non-spec addition. +const ( + OperationTypeQuery = "query" + OperationTypeMutation = "mutation" + OperationTypeSubscription = "subscription" +) // OperationDefinition implements Node, Definition type OperationDefinition struct { @@ -192,3 +197,45 @@ func (def *TypeExtensionDefinition) GetSelectionSet() *SelectionSet { func (def *TypeExtensionDefinition) GetOperation() string { return "" } + +// DirectiveDefinition implements Node, Definition +type DirectiveDefinition struct { + Kind string + Loc *Location + Name *Name + Arguments []*InputValueDefinition + Locations []*Name +} + +func NewDirectiveDefinition(def *DirectiveDefinition) *DirectiveDefinition { + if def == nil { + def = &DirectiveDefinition{} + } + return &DirectiveDefinition{ + Kind: kinds.DirectiveDefinition, + Loc: def.Loc, + Name: def.Name, + Arguments: def.Arguments, + Locations: def.Locations, + } +} + +func (def *DirectiveDefinition) GetKind() string { + return def.Kind +} + +func (def *DirectiveDefinition) GetLoc() *Location { + return def.Loc +} + +func (def *DirectiveDefinition) GetVariableDefinitions() []*VariableDefinition { + return []*VariableDefinition{} +} + +func (def *DirectiveDefinition) GetSelectionSet() *SelectionSet { + return &SelectionSet{} +} + +func (def *DirectiveDefinition) GetOperation() string { + return "" +} diff --git a/language/ast/node.go b/language/ast/node.go index 22879877..cd63a0fc 100644 --- a/language/ast/node.go +++ b/language/ast/node.go @@ -30,13 +30,16 @@ var _ Node = (*Directive)(nil) var _ Node = (*Named)(nil) var _ Node = (*List)(nil) var _ Node = (*NonNull)(nil) +var _ Node = (*SchemaDefinition)(nil) +var _ Node = (*OperationTypeDefinition)(nil) +var _ Node = (*ScalarDefinition)(nil) var _ Node = (*ObjectDefinition)(nil) var _ Node = (*FieldDefinition)(nil) var _ Node = (*InputValueDefinition)(nil) var _ Node = (*InterfaceDefinition)(nil) var _ Node = (*UnionDefinition)(nil) -var _ Node = (*ScalarDefinition)(nil) var _ Node = (*EnumDefinition)(nil) var _ Node = (*EnumValueDefinition)(nil) var _ Node = (*InputObjectDefinition)(nil) var _ Node = (*TypeExtensionDefinition)(nil) +var _ Node = (*DirectiveDefinition)(nil) diff --git a/language/ast/type_definitions.go b/language/ast/type_definitions.go index 95810070..dd7940c9 100644 --- a/language/ast/type_definitions.go +++ b/language/ast/type_definitions.go @@ -4,13 +4,141 @@ import ( "github.com/graphql-go/graphql/language/kinds" ) -// Ensure that all typeDefinition types implements Definition interface -var _ Definition = (*ObjectDefinition)(nil) -var _ Definition = (*InterfaceDefinition)(nil) -var _ Definition = (*UnionDefinition)(nil) -var _ Definition = (*ScalarDefinition)(nil) -var _ Definition = (*EnumDefinition)(nil) -var _ Definition = (*InputObjectDefinition)(nil) +type TypeDefinition interface { + GetOperation() string + GetVariableDefinitions() []*VariableDefinition + GetSelectionSet() *SelectionSet + GetKind() string + GetLoc() *Location +} + +var _ TypeDefinition = (*ScalarDefinition)(nil) +var _ TypeDefinition = (*ObjectDefinition)(nil) +var _ TypeDefinition = (*InterfaceDefinition)(nil) +var _ TypeDefinition = (*UnionDefinition)(nil) +var _ TypeDefinition = (*EnumDefinition)(nil) +var _ TypeDefinition = (*InputObjectDefinition)(nil) + +type TypeSystemDefinition interface { + GetOperation() string + GetVariableDefinitions() []*VariableDefinition + GetSelectionSet() *SelectionSet + GetKind() string + GetLoc() *Location +} + +var _ TypeSystemDefinition = (*SchemaDefinition)(nil) +var _ TypeSystemDefinition = (TypeDefinition)(nil) +var _ TypeSystemDefinition = (*TypeExtensionDefinition)(nil) +var _ TypeSystemDefinition = (*DirectiveDefinition)(nil) + +// SchemaDefinition implements Node, Definition +type SchemaDefinition struct { + Kind string + Loc *Location + OperationTypes []*OperationTypeDefinition +} + +func NewSchemaDefinition(def *SchemaDefinition) *SchemaDefinition { + if def == nil { + def = &SchemaDefinition{} + } + return &SchemaDefinition{ + Kind: kinds.SchemaDefinition, + Loc: def.Loc, + OperationTypes: def.OperationTypes, + } +} + +func (def *SchemaDefinition) GetKind() string { + return def.Kind +} + +func (def *SchemaDefinition) GetLoc() *Location { + return def.Loc +} + +func (def *SchemaDefinition) GetVariableDefinitions() []*VariableDefinition { + return []*VariableDefinition{} +} + +func (def *SchemaDefinition) GetSelectionSet() *SelectionSet { + return &SelectionSet{} +} + +func (def *SchemaDefinition) GetOperation() string { + return "" +} + +// ScalarDefinition implements Node, Definition +type OperationTypeDefinition struct { + Kind string + Loc *Location + Operation string + Type *Named +} + +func NewOperationTypeDefinition(def *OperationTypeDefinition) *OperationTypeDefinition { + if def == nil { + def = &OperationTypeDefinition{} + } + return &OperationTypeDefinition{ + Kind: kinds.OperationTypeDefinition, + Loc: def.Loc, + Operation: def.Operation, + Type: def.Type, + } +} + +func (def *OperationTypeDefinition) GetKind() string { + return def.Kind +} + +func (def *OperationTypeDefinition) GetLoc() *Location { + return def.Loc +} + +// ScalarDefinition implements Node, Definition +type ScalarDefinition struct { + Kind string + Loc *Location + Name *Name +} + +func NewScalarDefinition(def *ScalarDefinition) *ScalarDefinition { + if def == nil { + def = &ScalarDefinition{} + } + return &ScalarDefinition{ + Kind: kinds.ScalarDefinition, + Loc: def.Loc, + Name: def.Name, + } +} + +func (def *ScalarDefinition) GetKind() string { + return def.Kind +} + +func (def *ScalarDefinition) GetLoc() *Location { + return def.Loc +} + +func (def *ScalarDefinition) GetName() *Name { + return def.Name +} + +func (def *ScalarDefinition) GetVariableDefinitions() []*VariableDefinition { + return []*VariableDefinition{} +} + +func (def *ScalarDefinition) GetSelectionSet() *SelectionSet { + return &SelectionSet{} +} + +func (def *ScalarDefinition) GetOperation() string { + return "" +} // ObjectDefinition implements Node, Definition type ObjectDefinition struct { @@ -206,48 +334,6 @@ func (def *UnionDefinition) GetOperation() string { return "" } -// ScalarDefinition implements Node, Definition -type ScalarDefinition struct { - Kind string - Loc *Location - Name *Name -} - -func NewScalarDefinition(def *ScalarDefinition) *ScalarDefinition { - if def == nil { - def = &ScalarDefinition{} - } - return &ScalarDefinition{ - Kind: kinds.ScalarDefinition, - Loc: def.Loc, - Name: def.Name, - } -} - -func (def *ScalarDefinition) GetKind() string { - return def.Kind -} - -func (def *ScalarDefinition) GetLoc() *Location { - return def.Loc -} - -func (def *ScalarDefinition) GetName() *Name { - return def.Name -} - -func (def *ScalarDefinition) GetVariableDefinitions() []*VariableDefinition { - return []*VariableDefinition{} -} - -func (def *ScalarDefinition) GetSelectionSet() *SelectionSet { - return &SelectionSet{} -} - -func (def *ScalarDefinition) GetOperation() string { - return "" -} - // EnumDefinition implements Node, Definition type EnumDefinition struct { Kind string diff --git a/language/kinds/kinds.go b/language/kinds/kinds.go index d1161763..40bc994e 100644 --- a/language/kinds/kinds.go +++ b/language/kinds/kinds.go @@ -1,38 +1,59 @@ package kinds const ( - OperationDefinition = "OperationDefinition" - FragmentDefinition = "FragmentDefinition" - Document = "Document" - SelectionSet = "SelectionSet" - Name = "Name" - Directive = "Directive" - VariableDefinition = "VariableDefinition" - Variable = "Variable" - Named = "Named" // previously NamedType - List = "List" // previously ListType - NonNull = "NonNull" - InlineFragment = "InlineFragment" - FragmentSpread = "FragmentSpread" - Field = "Field" - Array = "Array" - Argument = "Argument" - IntValue = "IntValue" - FloatValue = "FloatValue" - StringValue = "StringValue" - BooleanValue = "BooleanValue" - EnumValue = "EnumValue" - ListValue = "ListValue" - ObjectValue = "ObjectValue" - ObjectField = "ObjectField" - ObjectDefinition = "ObjectDefinition" - FieldDefinition = "FieldDefinition" - InputValueDefinition = "InputValueDefinition" - InterfaceDefinition = "InterfaceDefinition" - UnionDefinition = "UnionDefinition" - ScalarDefinition = "ScalarDefinition" - EnumDefinition = "EnumDefinition" - EnumValueDefinition = "EnumValueDefinition" - InputObjectDefinition = "InputObjectDefinition" + // Name + Name = "Name" + + // Document + Document = "Document" + OperationDefinition = "OperationDefinition" + VariableDefinition = "VariableDefinition" + Variable = "Variable" + SelectionSet = "SelectionSet" + Field = "Field" + Argument = "Argument" + + // Fragments + FragmentSpread = "FragmentSpread" + InlineFragment = "InlineFragment" + FragmentDefinition = "FragmentDefinition" + + // Values + IntValue = "IntValue" + FloatValue = "FloatValue" + StringValue = "StringValue" + BooleanValue = "BooleanValue" + EnumValue = "EnumValue" + ListValue = "ListValue" + ObjectValue = "ObjectValue" + ObjectField = "ObjectField" + + // Directives + Directive = "Directive" + + // Types + Named = "Named" // previously NamedType + List = "List" // previously ListType + NonNull = "NonNull" // previously NonNull + + // Type System Definitions + SchemaDefinition = "SchemaDefinition" + OperationTypeDefinition = "OperationTypeDefinition" + + // Types Definitions + ScalarDefinition = "ScalarDefinition" // previously ScalarTypeDefinition + ObjectDefinition = "ObjectDefinition" // previously ObjectTypeDefinition + FieldDefinition = "FieldDefinition" + InputValueDefinition = "InputValueDefinition" + InterfaceDefinition = "InterfaceDefinition" // previously InterfaceTypeDefinition + UnionDefinition = "UnionDefinition" // previously UnionTypeDefinition + EnumDefinition = "EnumDefinition" // previously EnumTypeDefinition + EnumValueDefinition = "EnumValueDefinition" + InputObjectDefinition = "InputObjectDefinition" // previously InputObjectTypeDefinition + + // Types Extensions TypeExtensionDefinition = "TypeExtensionDefinition" + + // Directive Definitions + DirectiveDefinition = "DirectiveDefinition" ) diff --git a/language/lexer/lexer_test.go b/language/lexer/lexer_test.go index 13690d1e..f18a8b66 100644 --- a/language/lexer/lexer_test.go +++ b/language/lexer/lexer_test.go @@ -18,7 +18,7 @@ func createSource(body string) *source.Source { func TestDisallowsUncommonControlCharacters(t *testing.T) { tests := []Test{ - Test{ + { Body: "\u0007", Expected: `Syntax Error GraphQL (1:1) Invalid character "\\u0007" @@ -40,7 +40,7 @@ func TestDisallowsUncommonControlCharacters(t *testing.T) { func TestAcceptsBOMHeader(t *testing.T) { tests := []Test{ - Test{ + { Body: "\uFEFF foo", Expected: Token{ Kind: TokenKind[NAME], @@ -63,7 +63,7 @@ func TestAcceptsBOMHeader(t *testing.T) { func TestSkipsWhiteSpace(t *testing.T) { tests := []Test{ - Test{ + { Body: ` foo @@ -76,7 +76,7 @@ func TestSkipsWhiteSpace(t *testing.T) { Value: "foo", }, }, - Test{ + { Body: ` #comment foo#comment @@ -88,7 +88,7 @@ func TestSkipsWhiteSpace(t *testing.T) { Value: "foo", }, }, - Test{ + { Body: `,,,foo,,,`, Expected: Token{ Kind: TokenKind[NAME], @@ -127,7 +127,7 @@ func TestErrorsRespectWhitespace(t *testing.T) { func TestLexesStrings(t *testing.T) { tests := []Test{ - Test{ + { Body: "\"simple\"", Expected: Token{ Kind: TokenKind[STRING], @@ -136,7 +136,7 @@ func TestLexesStrings(t *testing.T) { Value: "simple", }, }, - Test{ + { Body: "\" white space \"", Expected: Token{ Kind: TokenKind[STRING], @@ -145,7 +145,7 @@ func TestLexesStrings(t *testing.T) { Value: " white space ", }, }, - Test{ + { Body: "\"quote \\\"\"", Expected: Token{ Kind: TokenKind[STRING], @@ -154,7 +154,7 @@ func TestLexesStrings(t *testing.T) { Value: `quote "`, }, }, - Test{ + { Body: "\"escaped \\n\\r\\b\\t\\f\"", Expected: Token{ Kind: TokenKind[STRING], @@ -163,7 +163,7 @@ func TestLexesStrings(t *testing.T) { Value: "escaped \n\r\b\t\f", }, }, - Test{ + { Body: "\"slashes \\\\ \\/\"", Expected: Token{ Kind: TokenKind[STRING], @@ -172,7 +172,7 @@ func TestLexesStrings(t *testing.T) { Value: "slashes \\ \\/", }, }, - Test{ + { Body: "\"unicode \\u1234\\u5678\\u90AB\\uCDEF\"", Expected: Token{ Kind: TokenKind[STRING], @@ -195,7 +195,7 @@ func TestLexesStrings(t *testing.T) { func TestLexReportsUsefulStringErrors(t *testing.T) { tests := []Test{ - Test{ + { Body: "\"", Expected: `Syntax Error GraphQL (1:2) Unterminated string. @@ -203,7 +203,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"no end quote", Expected: `Syntax Error GraphQL (1:14) Unterminated string. @@ -211,7 +211,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"contains unescaped \u0007 control char\"", Expected: `Syntax Error GraphQL (1:21) Invalid character within String: "\\u0007". @@ -219,7 +219,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"null-byte is not \u0000 end of file\"", Expected: `Syntax Error GraphQL (1:19) Invalid character within String: "\\u0000". @@ -227,7 +227,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"multi\nline\"", Expected: `Syntax Error GraphQL (1:7) Unterminated string. @@ -236,7 +236,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { 2: line" `, }, - Test{ + { Body: "\"multi\rline\"", Expected: `Syntax Error GraphQL (1:7) Unterminated string. @@ -245,7 +245,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { 2: line" `, }, - Test{ + { Body: "\"bad \\z esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \\z. @@ -253,7 +253,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"bad \\x esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \\x. @@ -261,7 +261,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"bad \\u1 esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \u1 es @@ -269,7 +269,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"bad \\u0XX1 esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \u0XX1 @@ -277,7 +277,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"bad \\uXXXX esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \uXXXX @@ -285,7 +285,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"bad \\uFXXX esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \uFXXX @@ -293,7 +293,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "\"bad \\uXXXF esc\"", Expected: `Syntax Error GraphQL (1:7) Invalid character escape sequence: \uXXXF @@ -315,7 +315,7 @@ func TestLexReportsUsefulStringErrors(t *testing.T) { func TestLexesNumbers(t *testing.T) { tests := []Test{ - Test{ + { Body: "4", Expected: Token{ Kind: TokenKind[INT], @@ -324,7 +324,7 @@ func TestLexesNumbers(t *testing.T) { Value: "4", }, }, - Test{ + { Body: "4.123", Expected: Token{ Kind: TokenKind[FLOAT], @@ -333,7 +333,7 @@ func TestLexesNumbers(t *testing.T) { Value: "4.123", }, }, - Test{ + { Body: "-4", Expected: Token{ Kind: TokenKind[INT], @@ -342,7 +342,7 @@ func TestLexesNumbers(t *testing.T) { Value: "-4", }, }, - Test{ + { Body: "9", Expected: Token{ Kind: TokenKind[INT], @@ -351,7 +351,7 @@ func TestLexesNumbers(t *testing.T) { Value: "9", }, }, - Test{ + { Body: "0", Expected: Token{ Kind: TokenKind[INT], @@ -360,7 +360,7 @@ func TestLexesNumbers(t *testing.T) { Value: "0", }, }, - Test{ + { Body: "-4.123", Expected: Token{ Kind: TokenKind[FLOAT], @@ -369,7 +369,7 @@ func TestLexesNumbers(t *testing.T) { Value: "-4.123", }, }, - Test{ + { Body: "0.123", Expected: Token{ Kind: TokenKind[FLOAT], @@ -378,7 +378,7 @@ func TestLexesNumbers(t *testing.T) { Value: "0.123", }, }, - Test{ + { Body: "123e4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -387,7 +387,7 @@ func TestLexesNumbers(t *testing.T) { Value: "123e4", }, }, - Test{ + { Body: "123E4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -396,7 +396,7 @@ func TestLexesNumbers(t *testing.T) { Value: "123E4", }, }, - Test{ + { Body: "123e-4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -405,7 +405,7 @@ func TestLexesNumbers(t *testing.T) { Value: "123e-4", }, }, - Test{ + { Body: "123e+4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -414,7 +414,7 @@ func TestLexesNumbers(t *testing.T) { Value: "123e+4", }, }, - Test{ + { Body: "-1.123e4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -423,7 +423,7 @@ func TestLexesNumbers(t *testing.T) { Value: "-1.123e4", }, }, - Test{ + { Body: "-1.123E4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -432,7 +432,7 @@ func TestLexesNumbers(t *testing.T) { Value: "-1.123E4", }, }, - Test{ + { Body: "-1.123e-4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -441,7 +441,7 @@ func TestLexesNumbers(t *testing.T) { Value: "-1.123e-4", }, }, - Test{ + { Body: "-1.123e+4", Expected: Token{ Kind: TokenKind[FLOAT], @@ -450,7 +450,7 @@ func TestLexesNumbers(t *testing.T) { Value: "-1.123e+4", }, }, - Test{ + { Body: "-1.123e4567", Expected: Token{ Kind: TokenKind[FLOAT], @@ -473,7 +473,7 @@ func TestLexesNumbers(t *testing.T) { func TestLexReportsUsefulNumbeErrors(t *testing.T) { tests := []Test{ - Test{ + { Body: "00", Expected: `Syntax Error GraphQL (1:2) Invalid number, unexpected digit after 0: "0". @@ -481,7 +481,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "+1", Expected: `Syntax Error GraphQL (1:1) Unexpected character "+". @@ -489,7 +489,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "1.", Expected: `Syntax Error GraphQL (1:3) Invalid number, expected digit but got: . @@ -497,7 +497,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: ".123", Expected: `Syntax Error GraphQL (1:1) Unexpected character ".". @@ -505,7 +505,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "1.A", Expected: `Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "A". @@ -513,7 +513,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "-A", Expected: `Syntax Error GraphQL (1:2) Invalid number, expected digit but got: "A". @@ -521,7 +521,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "1.0e", Expected: `Syntax Error GraphQL (1:5) Invalid number, expected digit but got: . @@ -530,7 +530,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { ^ `, }, - Test{ + { Body: "1.0eA", Expected: `Syntax Error GraphQL (1:5) Invalid number, expected digit but got: "A". @@ -552,7 +552,7 @@ func TestLexReportsUsefulNumbeErrors(t *testing.T) { func TestLexesPunctuation(t *testing.T) { tests := []Test{ - Test{ + { Body: "!", Expected: Token{ Kind: TokenKind[BANG], @@ -561,7 +561,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "$", Expected: Token{ Kind: TokenKind[DOLLAR], @@ -570,7 +570,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "(", Expected: Token{ Kind: TokenKind[PAREN_L], @@ -579,7 +579,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: ")", Expected: Token{ Kind: TokenKind[PAREN_R], @@ -588,7 +588,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "...", Expected: Token{ Kind: TokenKind[SPREAD], @@ -597,7 +597,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: ":", Expected: Token{ Kind: TokenKind[COLON], @@ -606,7 +606,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "=", Expected: Token{ Kind: TokenKind[EQUALS], @@ -615,7 +615,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "@", Expected: Token{ Kind: TokenKind[AT], @@ -624,7 +624,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "[", Expected: Token{ Kind: TokenKind[BRACKET_L], @@ -633,7 +633,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "]", Expected: Token{ Kind: TokenKind[BRACKET_R], @@ -642,7 +642,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "{", Expected: Token{ Kind: TokenKind[BRACE_L], @@ -651,7 +651,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "|", Expected: Token{ Kind: TokenKind[PIPE], @@ -660,7 +660,7 @@ func TestLexesPunctuation(t *testing.T) { Value: "", }, }, - Test{ + { Body: "}", Expected: Token{ Kind: TokenKind[BRACE_R], @@ -683,7 +683,7 @@ func TestLexesPunctuation(t *testing.T) { func TestLexReportsUsefulUnknownCharacterError(t *testing.T) { tests := []Test{ - Test{ + { Body: "..", Expected: `Syntax Error GraphQL (1:1) Unexpected character ".". @@ -691,7 +691,7 @@ func TestLexReportsUsefulUnknownCharacterError(t *testing.T) { ^ `, }, - Test{ + { Body: "?", Expected: `Syntax Error GraphQL (1:1) Unexpected character "?". @@ -699,7 +699,7 @@ func TestLexReportsUsefulUnknownCharacterError(t *testing.T) { ^ `, }, - Test{ + { Body: "\u203B", Expected: `Syntax Error GraphQL (1:1) Unexpected character "\\u203B". @@ -707,7 +707,7 @@ func TestLexReportsUsefulUnknownCharacterError(t *testing.T) { ^ `, }, - Test{ + { Body: "\u203b", Expected: `Syntax Error GraphQL (1:1) Unexpected character "\\u203B". diff --git a/language/parser/parser.go b/language/parser/parser.go index 7b480c3d..d1e01c50 100644 --- a/language/parser/parser.go +++ b/language/parser/parser.go @@ -133,6 +133,20 @@ func parseDocument(parser *Parser) (*ast.Document, error) { return nil, err } nodes = append(nodes, node) + + // Note: the Type System IDL is an experimental non-spec addition. + case "schema": + node, err := parseSchemaDefinition(parser) + if err != nil { + return nil, err + } + nodes = append(nodes, node) + case "scalar": + node, err := parseScalarTypeDefinition(parser) + if err != nil { + return nil, err + } + nodes = append(nodes, node) case "type": node, err := parseObjectTypeDefinition(parser) if err != nil { @@ -151,12 +165,6 @@ func parseDocument(parser *Parser) (*ast.Document, error) { return nil, err } nodes = append(nodes, node) - case "scalar": - node, err := parseScalarTypeDefinition(parser) - if err != nil { - return nil, err - } - nodes = append(nodes, node) case "enum": node, err := parseEnumTypeDefinition(parser) if err != nil { @@ -175,6 +183,12 @@ func parseDocument(parser *Parser) (*ast.Document, error) { return nil, err } nodes = append(nodes, node) + case "directive": + node, err := parseDirectiveDefinition(parser) + if err != nil { + return nil, err + } + nodes = append(nodes, node) default: if err := unexpected(parser, lexer.Token{}); err != nil { return nil, err @@ -198,8 +212,6 @@ func parseDocument(parser *Parser) (*ast.Document, error) { * OperationDefinition : * - SelectionSet * - OperationType Name? VariableDefinitions? Directives? SelectionSet - * - * OperationType : one of query mutation */ func parseOperationDefinition(parser *Parser) (*ast.OperationDefinition, error) { start := parser.Token.Start @@ -209,27 +221,17 @@ func parseOperationDefinition(parser *Parser) (*ast.OperationDefinition, error) return nil, err } return ast.NewOperationDefinition(&ast.OperationDefinition{ - Operation: "query", + Operation: ast.OperationTypeQuery, Directives: []*ast.Directive{}, SelectionSet: selectionSet, Loc: loc(parser, start), }), nil } - operationToken, err := expect(parser, lexer.TokenKind[lexer.NAME]) + operation, err := parseOperationType(parser) if err != nil { return nil, err } - operation := "" - switch operationToken.Value { - case "mutation": - fallthrough - case "subscription": - fallthrough - case "query": - operation = operationToken.Value - default: - return nil, unexpected(parser, operationToken) - } + var name *ast.Name if peek(parser, lexer.TokenKind[lexer.NAME]) { name, err = parseName(parser) @@ -256,6 +258,26 @@ func parseOperationDefinition(parser *Parser) (*ast.OperationDefinition, error) }), nil } +/** + * OperationType : one of query mutation subscription + */ +func parseOperationType(parser *Parser) (string, error) { + operationToken, err := expect(parser, lexer.TokenKind[lexer.NAME]) + if err != nil { + return "", err + } + switch operationToken.Value { + case ast.OperationTypeQuery: + return operationToken.Value, nil + case ast.OperationTypeMutation: + return operationToken.Value, nil + case ast.OperationTypeSubscription: + return operationToken.Value, nil + default: + return "", unexpected(parser, operationToken) + } +} + /** * VariableDefinitions : ( VariableDefinition+ ) */ @@ -854,6 +876,80 @@ func parseNamed(parser *Parser) (*ast.Named, error) { /* Implements the parsing rules in the Type Definition section. */ +/** + * SchemaDefinition : schema { OperationTypeDefinition+ } + * + * OperationTypeDefinition : OperationType : NamedType + */ +func parseSchemaDefinition(parser *Parser) (*ast.SchemaDefinition, error) { + start := parser.Token.Start + _, err := expectKeyWord(parser, "schema") + if err != nil { + return nil, err + } + operationTypesI, err := many( + parser, + lexer.TokenKind[lexer.BRACE_L], + parseOperationTypeDefinition, + lexer.TokenKind[lexer.BRACE_R], + ) + if err != nil { + return nil, err + } + operationTypes := []*ast.OperationTypeDefinition{} + for _, op := range operationTypesI { + if op, ok := op.(*ast.OperationTypeDefinition); ok { + operationTypes = append(operationTypes, op) + } + } + def := ast.NewSchemaDefinition(&ast.SchemaDefinition{ + OperationTypes: operationTypes, + Loc: loc(parser, start), + }) + return def, nil +} + +func parseOperationTypeDefinition(parser *Parser) (interface{}, error) { + start := parser.Token.Start + operation, err := parseOperationType(parser) + if err != nil { + return nil, err + } + _, err = expect(parser, lexer.TokenKind[lexer.COLON]) + if err != nil { + return nil, err + } + ttype, err := parseNamed(parser) + if err != nil { + return nil, err + } + return ast.NewOperationTypeDefinition(&ast.OperationTypeDefinition{ + Operation: operation, + Type: ttype, + Loc: loc(parser, start), + }), nil +} + +/** + * ScalarTypeDefinition : scalar Name + */ +func parseScalarTypeDefinition(parser *Parser) (*ast.ScalarDefinition, error) { + start := parser.Token.Start + _, err := expectKeyWord(parser, "scalar") + if err != nil { + return nil, err + } + name, err := parseName(parser) + if err != nil { + return nil, err + } + def := ast.NewScalarDefinition(&ast.ScalarDefinition{ + Name: name, + Loc: loc(parser, start), + }) + return def, nil +} + /** * ObjectTypeDefinition : type Name ImplementsInterfaces? { FieldDefinition+ } */ @@ -1079,26 +1175,6 @@ func parseUnionMembers(parser *Parser) ([]*ast.Named, error) { return members, nil } -/** - * ScalarTypeDefinition : scalar Name - */ -func parseScalarTypeDefinition(parser *Parser) (*ast.ScalarDefinition, error) { - start := parser.Token.Start - _, err := expectKeyWord(parser, "scalar") - if err != nil { - return nil, err - } - name, err := parseName(parser) - if err != nil { - return nil, err - } - def := ast.NewScalarDefinition(&ast.ScalarDefinition{ - Name: name, - Loc: loc(parser, start), - }) - return def, nil -} - /** * EnumTypeDefinition : enum Name { EnumValueDefinition+ } */ @@ -1196,6 +1272,70 @@ func parseTypeExtensionDefinition(parser *Parser) (*ast.TypeExtensionDefinition, }), nil } +/** + * DirectiveDefinition : + * - directive @ Name ArgumentsDefinition? on DirectiveLocations + */ +func parseDirectiveDefinition(parser *Parser) (*ast.DirectiveDefinition, error) { + start := parser.Token.Start + _, err := expectKeyWord(parser, "directive") + if err != nil { + return nil, err + } + _, err = expect(parser, lexer.TokenKind[lexer.AT]) + if err != nil { + return nil, err + } + name, err := parseName(parser) + if err != nil { + return nil, err + } + args, err := parseArgumentDefs(parser) + if err != nil { + return nil, err + } + _, err = expectKeyWord(parser, "on") + if err != nil { + return nil, err + } + locations, err := parseDirectiveLocations(parser) + if err != nil { + return nil, err + } + + return ast.NewDirectiveDefinition(&ast.DirectiveDefinition{ + Loc: loc(parser, start), + Name: name, + Arguments: args, + Locations: locations, + }), nil +} + +/** + * DirectiveLocations : + * - Name + * - DirectiveLocations | Name + */ +func parseDirectiveLocations(parser *Parser) ([]*ast.Name, error) { + locations := []*ast.Name{} + for { + name, err := parseName(parser) + if err != nil { + return locations, err + } + locations = append(locations, name) + + hasPipe, err := skip(parser, lexer.TokenKind[lexer.PIPE]) + if err != nil { + return locations, err + } + if !hasPipe { + break + } + } + return locations, nil +} + /* Core parsing utility functions */ // Returns a location object, used to identify the place in diff --git a/language/printer/printer.go b/language/printer/printer.go index 4cca90e4..47f9b2a4 100644 --- a/language/printer/printer.go +++ b/language/printer/printer.go @@ -130,6 +130,8 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ } return visitor.ActionNoChange, nil }, + + // Document "Document": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Document: @@ -144,7 +146,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ "OperationDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.OperationDefinition: - op := node.Operation + op := string(node.Operation) name := fmt.Sprintf("%v", node.Name) varDefs := wrap("(", join(toSliceString(node.VariableDefinitions), ", "), ")") @@ -153,7 +155,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ // Anonymous queries with no directives or variable definitions can use // the query short form. str := "" - if name == "" && directives == "" && varDefs == "" && op == "query" { + if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery { str = selectionSet } else { str = join([]string{ @@ -173,7 +175,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ directives := join(toSliceString(getMapValue(node, "Directives")), " ") selectionSet := getMapValueString(node, "SelectionSet") str := "" - if name == "" && directives == "" && varDefs == "" && op == "query" { + if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery { str = selectionSet } else { str = join([]string{ @@ -258,6 +260,8 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ } return visitor.ActionNoChange, nil }, + + // Fragments "FragmentSpread": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InlineFragment: @@ -306,6 +310,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ return visitor.ActionNoChange, nil }, + // Value "IntValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.IntValue: @@ -383,6 +388,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ return visitor.ActionNoChange, nil }, + // Directive "Directive": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Directive: @@ -397,6 +403,7 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ return visitor.ActionNoChange, nil }, + // Type "Named": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Named: @@ -425,6 +432,47 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ return visitor.ActionNoChange, nil }, + // Type System Definitions + "SchemaDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { + switch node := p.Node.(type) { + case *ast.SchemaDefinition: + operationTypesBlock := block(node.OperationTypes) + str := fmt.Sprintf("schema %v", operationTypesBlock) + return visitor.ActionUpdate, str + case map[string]interface{}: + operationTypes := toSliceString(getMapValue(node, "OperationTypes")) + operationTypesBlock := block(operationTypes) + str := fmt.Sprintf("schema %v", operationTypesBlock) + return visitor.ActionUpdate, str + } + return visitor.ActionNoChange, nil + }, + "OperationTypeDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { + switch node := p.Node.(type) { + case *ast.OperationTypeDefinition: + str := fmt.Sprintf("%v: %v", node.Operation, node.Type) + return visitor.ActionUpdate, str + case map[string]interface{}: + operation := getMapValueString(node, "Operation") + ttype := getMapValueString(node, "Type") + str := fmt.Sprintf("%v: %v", operation, ttype) + return visitor.ActionUpdate, str + } + return visitor.ActionNoChange, nil + }, + "ScalarDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { + switch node := p.Node.(type) { + case *ast.ScalarDefinition: + name := fmt.Sprintf("%v", node.Name) + str := "scalar " + name + return visitor.ActionUpdate, str + case map[string]interface{}: + name := getMapValueString(node, "Name") + str := "scalar " + name + return visitor.ActionUpdate, str + } + return visitor.ActionNoChange, nil + }, "ObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectDefinition: @@ -506,19 +554,6 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ } return visitor.ActionNoChange, nil }, - "ScalarDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.ScalarDefinition: - name := fmt.Sprintf("%v", node.Name) - str := "scalar " + name - return visitor.ActionUpdate, str - case map[string]interface{}: - name := getMapValueString(node, "Name") - str := "scalar " + name - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, "EnumDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumDefinition: @@ -571,6 +606,22 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ } return visitor.ActionNoChange, nil }, + "DirectiveDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { + switch node := p.Node.(type) { + case *ast.DirectiveDefinition: + args := wrap("(", join(toSliceString(node.Arguments), ", "), ")") + str := fmt.Sprintf("directive @%v%v on %v", node.Name, args, join(toSliceString(node.Locations), " | ")) + return visitor.ActionUpdate, str + case map[string]interface{}: + name := getMapValueString(node, "Name") + locations := toSliceString(getMapValue(node, "Locations")) + args := toSliceString(getMapValue(node, "Arguments")) + argsStr := wrap("(", join(args, ", "), ")") + str := fmt.Sprintf("directive @%v%v on %v", name, argsStr, join(locations, " | ")) + return visitor.ActionUpdate, str + } + return visitor.ActionNoChange, nil + }, } func Print(astNode ast.Node) (printed interface{}) { diff --git a/language/printer/printer_old.go b/language/printer/printer_old.go deleted file mode 100644 index 71d9157e..00000000 --- a/language/printer/printer_old.go +++ /dev/null @@ -1,359 +0,0 @@ -package printer - -import ( - "fmt" - - "github.com/graphql-go/graphql/language/ast" - "github.com/graphql-go/graphql/language/visitor" - // "log" -) - -var printDocASTReducer11 = map[string]visitor.VisitFunc{ - "Name": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Name: - return visitor.ActionUpdate, node.Value - } - return visitor.ActionNoChange, nil - - }, - "Variable": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Variable: - return visitor.ActionUpdate, fmt.Sprintf("$%v", node.Name) - } - return visitor.ActionNoChange, nil - }, - "Document": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Document: - definitions := toSliceString(node.Definitions) - return visitor.ActionUpdate, join(definitions, "\n\n") + "\n" - } - return visitor.ActionNoChange, nil - }, - "OperationDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.OperationDefinition: - op := node.Operation - name := fmt.Sprintf("%v", node.Name) - - defs := wrap("(", join(toSliceString(node.VariableDefinitions), ", "), ")") - directives := join(toSliceString(node.Directives), " ") - selectionSet := fmt.Sprintf("%v", node.SelectionSet) - str := "" - if name == "" { - str = selectionSet - } else { - str = join([]string{ - op, - join([]string{name, defs}, ""), - directives, - selectionSet, - }, " ") - } - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "VariableDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.VariableDefinition: - variable := fmt.Sprintf("%v", node.Variable) - ttype := fmt.Sprintf("%v", node.Type) - defaultValue := fmt.Sprintf("%v", node.DefaultValue) - - return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "") - - } - return visitor.ActionNoChange, nil - }, - "SelectionSet": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.SelectionSet: - str := block(node.Selections) - return visitor.ActionUpdate, str - - } - return visitor.ActionNoChange, nil - }, - "Field": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Field: - - alias := fmt.Sprintf("%v", node.Alias) - name := fmt.Sprintf("%v", node.Name) - args := toSliceString(node.Arguments) - directives := toSliceString(node.Directives) - selectionSet := fmt.Sprintf("%v", node.SelectionSet) - - str := join( - []string{ - wrap("", alias, ": ") + name + wrap("(", join(args, ", "), ")"), - join(directives, " "), - selectionSet, - }, - " ", - ) - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "Argument": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Argument: - name := fmt.Sprintf("%v", node.Name) - value := fmt.Sprintf("%v", node.Value) - return visitor.ActionUpdate, name + ": " + value - } - return visitor.ActionNoChange, nil - }, - "FragmentSpread": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.FragmentSpread: - name := fmt.Sprintf("%v", node.Name) - directives := toSliceString(node.Directives) - return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "") - } - return visitor.ActionNoChange, nil - }, - "InlineFragment": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.InlineFragment: - typeCondition := fmt.Sprintf("%v", node.TypeCondition) - directives := toSliceString(node.Directives) - selectionSet := fmt.Sprintf("%v", node.SelectionSet) - return visitor.ActionUpdate, "... on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet - } - return visitor.ActionNoChange, nil - }, - "FragmentDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.FragmentDefinition: - name := fmt.Sprintf("%v", node.Name) - typeCondition := fmt.Sprintf("%v", node.TypeCondition) - directives := toSliceString(node.Directives) - selectionSet := fmt.Sprintf("%v", node.SelectionSet) - return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet - } - return visitor.ActionNoChange, nil - }, - - "IntValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.IntValue: - return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) - } - return visitor.ActionNoChange, nil - }, - "FloatValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.FloatValue: - return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) - } - return visitor.ActionNoChange, nil - }, - "StringValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.StringValue: - return visitor.ActionUpdate, `"` + fmt.Sprintf("%v", node.Value) + `"` - } - return visitor.ActionNoChange, nil - }, - "BooleanValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.BooleanValue: - return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) - } - return visitor.ActionNoChange, nil - }, - "EnumValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.EnumValue: - return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) - } - return visitor.ActionNoChange, nil - }, - "ListValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.ListValue: - return visitor.ActionUpdate, "[" + join(toSliceString(node.Values), ", ") + "]" - } - return visitor.ActionNoChange, nil - }, - "ObjectValue": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.ObjectValue: - return visitor.ActionUpdate, "{" + join(toSliceString(node.Fields), ", ") + "}" - } - return visitor.ActionNoChange, nil - }, - "ObjectField": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.ObjectField: - name := fmt.Sprintf("%v", node.Name) - value := fmt.Sprintf("%v", node.Value) - return visitor.ActionUpdate, name + ": " + value - } - return visitor.ActionNoChange, nil - }, - - "Directive": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Directive: - name := fmt.Sprintf("%v", node.Name) - args := toSliceString(node.Arguments) - return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")") - } - return visitor.ActionNoChange, nil - }, - - "Named": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.Named: - return visitor.ActionUpdate, fmt.Sprintf("%v", node.Name) - } - return visitor.ActionNoChange, nil - }, - "List": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.List: - return visitor.ActionUpdate, "[" + fmt.Sprintf("%v", node.Type) + "]" - } - return visitor.ActionNoChange, nil - }, - "NonNull": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.NonNull: - return visitor.ActionUpdate, fmt.Sprintf("%v", node.Type) + "!" - } - return visitor.ActionNoChange, nil - }, - - "ObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.ObjectDefinition: - name := fmt.Sprintf("%v", node.Name) - interfaces := toSliceString(node.Interfaces) - fields := node.Fields - str := "type " + name + " " + wrap("implements ", join(interfaces, ", "), " ") + block(fields) - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "FieldDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.FieldDefinition: - name := fmt.Sprintf("%v", node.Name) - ttype := fmt.Sprintf("%v", node.Type) - args := toSliceString(node.Arguments) - str := name + wrap("(", join(args, ", "), ")") + ": " + ttype - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "InputValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.InputValueDefinition: - name := fmt.Sprintf("%v", node.Name) - ttype := fmt.Sprintf("%v", node.Type) - defaultValue := fmt.Sprintf("%v", node.DefaultValue) - str := name + ": " + ttype + wrap(" = ", defaultValue, "") - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "InterfaceDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.InterfaceDefinition: - name := fmt.Sprintf("%v", node.Name) - fields := node.Fields - str := "interface " + name + " " + block(fields) - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "UnionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.UnionDefinition: - name := fmt.Sprintf("%v", node.Name) - types := toSliceString(node.Types) - str := "union " + name + " = " + join(types, " | ") - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "ScalarDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.ScalarDefinition: - name := fmt.Sprintf("%v", node.Name) - str := "scalar " + name - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "EnumDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.EnumDefinition: - name := fmt.Sprintf("%v", node.Name) - values := node.Values - str := "enum " + name + " " + block(values) - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, - "EnumValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.EnumValueDefinition: - name := fmt.Sprintf("%v", node.Name) - return visitor.ActionUpdate, name - } - return visitor.ActionNoChange, nil - }, - "InputObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.InputObjectDefinition: - name := fmt.Sprintf("%v", node.Name) - fields := node.Fields - return visitor.ActionUpdate, "input " + name + " " + block(fields) - } - return visitor.ActionNoChange, nil - }, - "TypeExtensionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { - switch node := p.Node.(type) { - case *ast.TypeExtensionDefinition: - definition := fmt.Sprintf("%v", node.Definition) - str := "extend " + definition - return visitor.ActionUpdate, str - } - return visitor.ActionNoChange, nil - }, -} - -func Print11(astNode ast.Node) (printed interface{}) { - // defer func() interface{} { - // if r := recover(); r != nil { - // log.Println("Error: %v", r) - // return printed - // } - // return printed - // }() - printed = visitor.Visit(astNode, &visitor.VisitorOptions{ - LeaveKindMap: printDocASTReducer, - }, nil) - return printed -} - -// -//func PrintMap(astNodeMap map[string]interface{}) (printed interface{}) { -// defer func() interface{} { -// if r := recover(); r != nil { -// return fmt.Sprintf("%v", astNodeMap) -// } -// return printed -// }() -// printed = visitor.Visit(astNodeMap, &visitor.VisitorOptions{ -// LeaveKindMap: printDocASTReducer, -// }, nil) -// return printed -//} diff --git a/language/printer/schema_printer_test.go b/language/printer/schema_printer_test.go index 344c8ac4..3e0f6f69 100644 --- a/language/printer/schema_printer_test.go +++ b/language/printer/schema_printer_test.go @@ -53,7 +53,12 @@ func TestSchemaPrinter_PrintsKitchenSink(t *testing.T) { query := string(b) astDoc := parse(t, query) - expected := `type Foo implements Bar { + expected := `schema { + query: QueryType + mutation: MutationType +} + +type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int @@ -84,6 +89,10 @@ input InputType { extend type Foo { seven(argument: [String]): Type } + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ` results := printer.Print(astDoc) if !reflect.DeepEqual(expected, results) { diff --git a/language/visitor/visitor.go b/language/visitor/visitor.go index 62c50149..b59df235 100644 --- a/language/visitor/visitor.go +++ b/language/visitor/visitor.go @@ -83,6 +83,10 @@ var QueryDocumentKeys = KeyMap{ "List": []string{"Type"}, "NonNull": []string{"Type"}, + "SchemaDefinition": []string{"OperationTypes"}, + "OperationTypeDefinition": []string{"Type"}, + + "ScalarDefinition": []string{"Name"}, "ObjectDefinition": []string{ "Name", "Interfaces", @@ -106,7 +110,6 @@ var QueryDocumentKeys = KeyMap{ "Name", "Types", }, - "ScalarDefinition": []string{"Name"}, "EnumDefinition": []string{ "Name", "Values", @@ -116,7 +119,10 @@ var QueryDocumentKeys = KeyMap{ "Name", "Fields", }, + "TypeExtensionDefinition": []string{"Definition"}, + + "DirectiveDefinition": []string{"Name", "Arguments", "Locations"}, } type stack struct { diff --git a/language/visitor/visitor_test.go b/language/visitor/visitor_test.go index b0770125..436560d8 100644 --- a/language/visitor/visitor_test.go +++ b/language/visitor/visitor_test.go @@ -442,7 +442,7 @@ func TestVisitor_AllowsANamedFunctionsVisitorAPI(t *testing.T) { v := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - "Name": visitor.NamedVisitFuncs{ + "Name": { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: @@ -451,7 +451,7 @@ func TestVisitor_AllowsANamedFunctionsVisitorAPI(t *testing.T) { return visitor.ActionNoChange, nil }, }, - "SelectionSet": visitor.NamedVisitFuncs{ + "SelectionSet": { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SelectionSet: diff --git a/lists_test.go b/lists_test.go index 4c30b500..7fd360c8 100644 --- a/lists_test.go +++ b/lists_test.go @@ -255,10 +255,10 @@ func TestLists_NonNullListOfNullableObjectsReturnsNull(t *testing.T) { "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -325,10 +325,10 @@ func TestLists_NonNullListOfNullableFunc_ReturnsNull(t *testing.T) { "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -421,10 +421,10 @@ func TestLists_NullableListOfNonNullObjects_ContainsNull(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -486,10 +486,10 @@ func TestLists_NullableListOfNonNullFunc_ContainsNull(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -597,10 +597,10 @@ func TestLists_NonNullListOfNonNullObjects_ContainsNull(t *testing.T) { "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -618,10 +618,10 @@ func TestLists_NonNullListOfNonNullObjects_ReturnsNull(t *testing.T) { "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -669,10 +669,10 @@ func TestLists_NonNullListOfNonNullFunc_ContainsNull(t *testing.T) { "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -695,10 +695,10 @@ func TestLists_NonNullListOfNonNullFunc_ReturnsNull(t *testing.T) { "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Cannot return null for non-nullable field DataType.test.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 1, Column: 10, }, @@ -772,7 +772,7 @@ func TestLists_UserErrorExpectIterableButDidNotGetOne(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "User Error: expected iterable, but did not find one for field DataType.test.", Locations: []location.SourceLocation{}, }, diff --git a/mutations_test.go b/mutations_test.go index 0a52d136..a97dda52 100644 --- a/mutations_test.go +++ b/mutations_test.go @@ -218,16 +218,16 @@ func TestMutations_EvaluatesMutationsCorrectlyInThePresenceOfAFailedMutation(t * "sixth": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot change the number`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 8, Column: 7}, + {Line: 8, Column: 7}, }, }, - gqlerrors.FormattedError{ + { Message: `Cannot change the number`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 17, Column: 7}, + {Line: 17, Column: 7}, }, }, }, diff --git a/nonnull_test.go b/nonnull_test.go index b52c6829..75b3f6cb 100644 --- a/nonnull_test.go +++ b/nonnull_test.go @@ -121,10 +121,10 @@ func TestNonNull_NullsANullableFieldThatThrowsSynchronously(t *testing.T) { "sync": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 3, Column: 9, }, }, @@ -159,10 +159,10 @@ func TestNonNull_NullsANullableFieldThatThrowsInAPromise(t *testing.T) { "promise": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 3, Column: 9, }, }, @@ -199,10 +199,10 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANullableFieldThat "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullSyncError, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 4, Column: 11, }, }, @@ -239,10 +239,10 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullPromiseError, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 4, Column: 11, }, }, @@ -279,10 +279,10 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullSyncError, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 4, Column: 11, }, }, @@ -319,10 +319,10 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullPromiseError, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 4, Column: 11, }, }, @@ -404,76 +404,76 @@ func TestNonNull_NullsAComplexTreeOfNullableFieldsThatThrow(t *testing.T) { }, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 4, Column: 11}, + {Line: 4, Column: 11}, }, }, - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 7, Column: 13}, + {Line: 7, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 11, Column: 13}, + {Line: 11, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 16, Column: 11}, + {Line: 16, Column: 11}, }, }, - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 19, Column: 13}, + {Line: 19, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: syncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 23, Column: 13}, + {Line: 23, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 5, Column: 11}, + {Line: 5, Column: 11}, }, }, - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 8, Column: 13}, + {Line: 8, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 12, Column: 13}, + {Line: 12, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 17, Column: 11}, + {Line: 17, Column: 11}, }, }, - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 20, Column: 13}, + {Line: 20, Column: 13}, }, }, - gqlerrors.FormattedError{ + { Message: promiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 24, Column: 13}, + {Line: 24, Column: 13}, }, }, }, @@ -557,28 +557,28 @@ func TestNonNull_NullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfField "anotherPromiseNest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullSyncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 8, Column: 19}, + {Line: 8, Column: 19}, }, }, - gqlerrors.FormattedError{ + { Message: nonNullSyncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 19, Column: 19}, + {Line: 19, Column: 19}, }, }, - gqlerrors.FormattedError{ + { Message: nonNullPromiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 30, Column: 19}, + {Line: 30, Column: 19}, }, }, - gqlerrors.FormattedError{ + { Message: nonNullPromiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 41, Column: 19}, + {Line: 41, Column: 19}, }, }, }, @@ -681,10 +681,10 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 4, Column: 11}, + {Line: 4, Column: 11}, }, }, }, @@ -719,10 +719,10 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT "nest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 4, Column: 11}, + {Line: 4, Column: 11}, }, }, }, @@ -758,10 +758,10 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 4, Column: 11}, + {Line: 4, Column: 11}, }, }, }, @@ -796,10 +796,10 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha "promiseNest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 4, Column: 11}, + {Line: 4, Column: 11}, }, }, }, @@ -955,28 +955,28 @@ func TestNonNull_NullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChainOf "anotherPromiseNest": nil, }, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 8, Column: 19}, + {Line: 8, Column: 19}, }, }, - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 19, Column: 19}, + {Line: 19, Column: 19}, }, }, - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 30, Column: 19}, + {Line: 30, Column: 19}, }, }, - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 41, Column: 19}, + {Line: 41, Column: 19}, }, }, }, @@ -1011,10 +1011,10 @@ func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldThrows(t *testing.T) { expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullSyncError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 2, Column: 17}, + {Line: 2, Column: 17}, }, }, }, @@ -1043,10 +1043,10 @@ func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldErrors(t *testing.T) { expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: nonNullPromiseError, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 2, Column: 17}, + {Line: 2, Column: 17}, }, }, }, @@ -1075,10 +1075,10 @@ func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldReturnsNull(t *testing.T) expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 2, Column: 17}, + {Line: 2, Column: 17}, }, }, }, @@ -1107,10 +1107,10 @@ func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldResolvesNull(t *testing.T expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ - location.SourceLocation{Line: 2, Column: 17}, + {Line: 2, Column: 17}, }, }, }, diff --git a/rules.go b/rules.go index 1b518846..c5be3f03 100644 --- a/rules.go +++ b/rules.go @@ -67,7 +67,7 @@ func reportError(context *ValidationContext, message string, nodes []ast.Node) ( func ArgumentsOfCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Argument: visitor.NamedVisitFuncs{ + kinds.Argument: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if argAST, ok := p.Node.(*ast.Argument); ok { value := argAST.Value @@ -111,7 +111,7 @@ func ArgumentsOfCorrectTypeRule(context *ValidationContext) *ValidationRuleInsta func DefaultValuesOfCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.VariableDefinition: visitor.NamedVisitFuncs{ + kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if varDefAST, ok := p.Node.(*ast.VariableDefinition); ok { name := "" @@ -196,7 +196,7 @@ func UndefinedFieldMessage(fieldName string, ttypeName string, suggestedTypes [] func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Field: visitor.NamedVisitFuncs{ + kinds.Field: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} @@ -214,9 +214,9 @@ func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance nodeName = node.Name.Value } - if ttype, ok := ttype.(Abstract); ok { - siblingInterfaces := getSiblingInterfacesIncludingField(ttype, nodeName) - implementations := getImplementationsIncludingField(ttype, nodeName) + if ttype, ok := ttype.(Abstract); ok && IsAbstractType(ttype) { + siblingInterfaces := getSiblingInterfacesIncludingField(context.Schema(), ttype, nodeName) + implementations := getImplementationsIncludingField(context.Schema(), ttype, nodeName) suggestedMaps := map[string]bool{} for _, s := range siblingInterfaces { if _, ok := suggestedMaps[s]; !ok { @@ -253,10 +253,10 @@ func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance } // Return implementations of `type` that include `fieldName` as a valid field. -func getImplementationsIncludingField(ttype Abstract, fieldName string) []string { +func getImplementationsIncludingField(schema *Schema, ttype Abstract, fieldName string) []string { result := []string{} - for _, t := range ttype.PossibleTypes() { + for _, t := range schema.PossibleTypes(ttype) { fields := t.Fields() if _, ok := fields[fieldName]; ok { result = append(result, fmt.Sprintf(`%v`, t.Name())) @@ -271,8 +271,8 @@ func getImplementationsIncludingField(ttype Abstract, fieldName string) []string // that they implement. If those interfaces include `field` as a valid field, // return them, sorted by how often the implementations include the other // interface. -func getSiblingInterfacesIncludingField(ttype Abstract, fieldName string) []string { - implementingObjects := ttype.PossibleTypes() +func getSiblingInterfacesIncludingField(schema *Schema, ttype Abstract, fieldName string) []string { + implementingObjects := schema.PossibleTypes(ttype) result := []string{} suggestedInterfaceSlice := []*suggestedInterface{} @@ -339,7 +339,7 @@ func (s suggestedInterfaceSortedSlice) Less(i, j int) bool { func FragmentsOnCompositeTypesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.InlineFragment: visitor.NamedVisitFuncs{ + kinds.InlineFragment: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.InlineFragment); ok { ttype := context.Type() @@ -354,7 +354,7 @@ func FragmentsOnCompositeTypesRule(context *ValidationContext) *ValidationRuleIn return visitor.ActionNoChange, nil }, }, - kinds.FragmentDefinition: visitor.NamedVisitFuncs{ + kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok { ttype := context.Type() @@ -387,7 +387,7 @@ func FragmentsOnCompositeTypesRule(context *ValidationContext) *ValidationRuleIn func KnownArgumentNamesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Argument: visitor.NamedVisitFuncs{ + kinds.Argument: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} @@ -461,6 +461,10 @@ func KnownArgumentNamesRule(context *ValidationContext) *ValidationRuleInstance } } +func MisplaceDirectiveMessage(directiveName string, location string) string { + return fmt.Sprintf(`Directive "%v" may not be used on %v.`, directiveName, location) +} + // KnownDirectivesRule Known directives // // A GraphQL document is only valid if all `@directives` are known by the @@ -468,7 +472,7 @@ func KnownArgumentNamesRule(context *ValidationContext) *ValidationRuleInstance func KnownDirectivesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Directive: visitor.NamedVisitFuncs{ + kinds.Directive: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} @@ -501,26 +505,26 @@ func KnownDirectivesRule(context *ValidationContext) *ValidationRuleInstance { return action, result } - if appliedTo.GetKind() == kinds.OperationDefinition && directiveDef.OnOperation == false { - reportError( - context, - fmt.Sprintf(`Directive "%v" may not be used on "%v".`, nodeName, "operation"), - []ast.Node{node}, - ) + candidateLocation := getLocationForAppliedNode(appliedTo) + + directiveHasLocation := false + for _, loc := range directiveDef.Locations { + if loc == candidateLocation { + directiveHasLocation = true + break + } } - if appliedTo.GetKind() == kinds.Field && directiveDef.OnField == false { + + if candidateLocation == "" { reportError( context, - fmt.Sprintf(`Directive "%v" may not be used on "%v".`, nodeName, "field"), + MisplaceDirectiveMessage(nodeName, node.GetKind()), []ast.Node{node}, ) - } - if (appliedTo.GetKind() == kinds.FragmentSpread || - appliedTo.GetKind() == kinds.InlineFragment || - appliedTo.GetKind() == kinds.FragmentDefinition) && directiveDef.OnFragment == false { + } else if !directiveHasLocation { reportError( context, - fmt.Sprintf(`Directive "%v" may not be used on "%v".`, nodeName, "fragment"), + MisplaceDirectiveMessage(nodeName, candidateLocation), []ast.Node{node}, ) } @@ -536,6 +540,35 @@ func KnownDirectivesRule(context *ValidationContext) *ValidationRuleInstance { } } +func getLocationForAppliedNode(appliedTo ast.Node) string { + kind := appliedTo.GetKind() + if kind == kinds.OperationDefinition { + appliedTo, _ := appliedTo.(*ast.OperationDefinition) + if appliedTo.Operation == ast.OperationTypeQuery { + return DirectiveLocationQuery + } + if appliedTo.Operation == ast.OperationTypeMutation { + return DirectiveLocationMutation + } + if appliedTo.Operation == ast.OperationTypeSubscription { + return DirectiveLocationSubscription + } + } + if kind == kinds.Field { + return DirectiveLocationField + } + if kind == kinds.FragmentSpread { + return DirectiveLocationFragmentSpread + } + if kind == kinds.InlineFragment { + return DirectiveLocationInlineFragment + } + if kind == kinds.FragmentDefinition { + return DirectiveLocationFragmentDefinition + } + return "" +} + // KnownFragmentNamesRule Known fragment names // // A GraphQL document is only valid if all `...Fragment` fragment spreads refer @@ -543,7 +576,7 @@ func KnownDirectivesRule(context *ValidationContext) *ValidationRuleInstance { func KnownFragmentNamesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.FragmentSpread: visitor.NamedVisitFuncs{ + kinds.FragmentSpread: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} @@ -580,27 +613,27 @@ func KnownFragmentNamesRule(context *ValidationContext) *ValidationRuleInstance func KnownTypeNamesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.ObjectDefinition: visitor.NamedVisitFuncs{ + kinds.ObjectDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, - kinds.InterfaceDefinition: visitor.NamedVisitFuncs{ + kinds.InterfaceDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, - kinds.UnionDefinition: visitor.NamedVisitFuncs{ + kinds.UnionDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, - kinds.InputObjectDefinition: visitor.NamedVisitFuncs{ + kinds.InputObjectDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, - kinds.Named: visitor.NamedVisitFuncs{ + kinds.Named: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Named); ok { typeNameValue := "" @@ -635,7 +668,7 @@ func LoneAnonymousOperationRule(context *ValidationContext) *ValidationRuleInsta var operationCount = 0 visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Document: visitor.NamedVisitFuncs{ + kinds.Document: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Document); ok { operationCount = 0 @@ -648,7 +681,7 @@ func LoneAnonymousOperationRule(context *ValidationContext) *ValidationRuleInsta return visitor.ActionNoChange, nil }, }, - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok { if node.Name == nil && operationCount > 1 { @@ -778,12 +811,12 @@ func NoFragmentCyclesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, - kinds.FragmentDefinition: visitor.NamedVisitFuncs{ + kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok && node != nil { nodeName := "" @@ -820,7 +853,7 @@ func NoUndefinedVariablesRule(context *ValidationContext) *ValidationRuleInstanc visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { variableNameDefined = map[string]bool{} return visitor.ActionNoChange, nil @@ -856,7 +889,7 @@ func NoUndefinedVariablesRule(context *ValidationContext) *ValidationRuleInstanc return visitor.ActionNoChange, nil }, }, - kinds.VariableDefinition: visitor.NamedVisitFuncs{ + kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.VariableDefinition); ok && node != nil { variableName := "" @@ -886,7 +919,7 @@ func NoUnusedFragmentsRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok && node != nil { operationDefs = append(operationDefs, node) @@ -894,7 +927,7 @@ func NoUnusedFragmentsRule(context *ValidationContext) *ValidationRuleInstance { return visitor.ActionSkip, nil }, }, - kinds.FragmentDefinition: visitor.NamedVisitFuncs{ + kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok && node != nil { fragmentDefs = append(fragmentDefs, node) @@ -902,7 +935,7 @@ func NoUnusedFragmentsRule(context *ValidationContext) *ValidationRuleInstance { return visitor.ActionSkip, nil }, }, - kinds.Document: visitor.NamedVisitFuncs{ + kinds.Document: { Leave: func(p visitor.VisitFuncParams) (string, interface{}) { fragmentNameUsed := map[string]bool{} for _, operation := range operationDefs { @@ -958,7 +991,7 @@ func NoUnusedVariablesRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { variableDefs = []*ast.VariableDefinition{} return visitor.ActionNoChange, nil @@ -1000,7 +1033,7 @@ func NoUnusedVariablesRule(context *ValidationContext) *ValidationRuleInstance { return visitor.ActionNoChange, nil }, }, - kinds.VariableDefinition: visitor.NamedVisitFuncs{ + kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if def, ok := p.Node.(*ast.VariableDefinition); ok && def != nil { variableDefs = append(variableDefs, def) @@ -1212,6 +1245,40 @@ func sameType(typeA, typeB Type) bool { return false } +// Two types conflict if both types could not apply to a value simultaneously. +// Composite types are ignored as their individual field types will be compared +// later recursively. However List and Non-Null types must match. +func doTypesConflict(type1 Output, type2 Output) bool { + if type1, ok := type1.(*List); ok { + if type2, ok := type2.(*List); ok { + return doTypesConflict(type1.OfType, type2.OfType) + } + return true + } + if type2, ok := type2.(*List); ok { + if type1, ok := type1.(*List); ok { + return doTypesConflict(type1.OfType, type2.OfType) + } + return true + } + if type1, ok := type1.(*NonNull); ok { + if type2, ok := type2.(*NonNull); ok { + return doTypesConflict(type1.OfType, type2.OfType) + } + return true + } + if type2, ok := type2.(*NonNull); ok { + if type1, ok := type1.(*NonNull); ok { + return doTypesConflict(type1.OfType, type2.OfType) + } + return true + } + if IsLeafType(type1) || IsLeafType(type2) { + return type1 != type2 + } + return false +} + // OverlappingFieldsCanBeMergedRule Overlapping fields can be merged // // A selection set is only valid if all fields (including spreading any @@ -1219,9 +1286,12 @@ func sameType(typeA, typeB Type) bool { // without ambiguity. func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRuleInstance { + var getSubfieldMap func(ast1 *ast.Field, type1 Output, ast2 *ast.Field, type2 Output) map[string][]*fieldDefPair + var subfieldConflicts func(conflicts []*conflict, responseName string, ast1 *ast.Field, ast2 *ast.Field) *conflict + var findConflicts func(parentFieldsAreMutuallyExclusive bool, fieldMap map[string][]*fieldDefPair) (conflicts []*conflict) + comparedSet := newPairSet() - var findConflicts func(fieldMap map[string][]*fieldDefPair) (conflicts []*conflict) - findConflict := func(responseName string, field *fieldDefPair, field2 *fieldDefPair) *conflict { + findConflict := func(parentFieldsAreMutuallyExclusive bool, responseName string, field *fieldDefPair, field2 *fieldDefPair) *conflict { parentType1 := field.ParentType ast1 := field.Field @@ -1236,46 +1306,21 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul return nil } - // If the statically known parent types could not possibly apply at the same - // time, then it is safe to permit them to diverge as they will not present - // any ambiguity by differing. - // It is known that two parent types could never overlap if they are - // different Object types. Interface or Union types might overlap - if not - // in the current state of the schema, then perhaps in some future version, - // thus may not safely diverge. - if parentType1 != parentType2 { - _, ok1 := parentType1.(*Object) - _, ok2 := parentType2.(*Object) - if ok1 && ok2 { - return nil - } - } - // Memoize, do not report the same issue twice. + // Note: Two overlapping ASTs could be encountered both when + // `parentFieldsAreMutuallyExclusive` is true and is false, which could + // produce different results (when `true` being a subset of `false`). + // However we do not need to include this piece of information when + // memoizing since this rule visits leaf fields before their parent fields, + // ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first + // time two overlapping fields are encountered, ensuring that the full + // set of validation rules are always checked when necessary. if comparedSet.Has(ast1, ast2) { return nil } comparedSet.Add(ast1, ast2) - name1 := "" - if ast1.Name != nil { - name1 = ast1.Name.Value - } - name2 := "" - if ast2.Name != nil { - name2 = ast2.Name.Value - } - if name1 != name2 { - return &conflict{ - Reason: conflictReason{ - Name: responseName, - Message: fmt.Sprintf(`%v and %v are different fields`, name1, name2), - }, - FieldsLeft: []ast.Node{ast1}, - FieldsRight: []ast.Node{ast2}, - } - } - + // The return type for each field. var type1 Type var type2 Type if def1 != nil { @@ -1285,27 +1330,74 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul type2 = def2.Type } - if type1 != nil && type2 != nil && !sameType(type1, type2) { - return &conflict{ - Reason: conflictReason{ - Name: responseName, - Message: fmt.Sprintf(`they return differing types %v and %v`, type1, type2), - }, - FieldsLeft: []ast.Node{ast1}, - FieldsRight: []ast.Node{ast2}, + // If it is known that two fields could not possibly apply at the same + // time, due to the parent types, then it is safe to permit them to diverge + // in aliased field or arguments used as they will not present any ambiguity + // by differing. + // It is known that two parent types could never overlap if they are + // different Object types. Interface or Union types might overlap - if not + // in the current state of the schema, then perhaps in some future version, + // thus may not safely diverge. + _, isParentType1Object := parentType1.(*Object) + _, isParentType2Object := parentType2.(*Object) + fieldsAreMutuallyExclusive := parentFieldsAreMutuallyExclusive || parentType1 != parentType2 && isParentType1Object && isParentType2Object + + if !fieldsAreMutuallyExclusive { + // Two aliases must refer to the same field. + name1 := "" + name2 := "" + + if ast1.Name != nil { + name1 = ast1.Name.Value + } + if ast2.Name != nil { + name2 = ast2.Name.Value + } + if name1 != name2 { + return &conflict{ + Reason: conflictReason{ + Name: responseName, + Message: fmt.Sprintf(`%v and %v are different fields`, name1, name2), + }, + FieldsLeft: []ast.Node{ast1}, + FieldsRight: []ast.Node{ast2}, + } + } + + // Two field calls must have the same arguments. + if !sameArguments(ast1.Arguments, ast2.Arguments) { + return &conflict{ + Reason: conflictReason{ + Name: responseName, + Message: `they have differing arguments`, + }, + FieldsLeft: []ast.Node{ast1}, + FieldsRight: []ast.Node{ast2}, + } } } - if !sameArguments(ast1.Arguments, ast2.Arguments) { + + if type1 != nil && type2 != nil && doTypesConflict(type1, type2) { return &conflict{ Reason: conflictReason{ Name: responseName, - Message: `they have differing arguments`, + Message: fmt.Sprintf(`they return conflicting types %v and %v`, type1, type2), }, FieldsLeft: []ast.Node{ast1}, FieldsRight: []ast.Node{ast2}, } } + subFieldMap := getSubfieldMap(ast1, type1, ast2, type2) + if subFieldMap != nil { + conflicts := findConflicts(fieldsAreMutuallyExclusive, subFieldMap) + return subfieldConflicts(conflicts, responseName, ast1, ast2) + } + + return nil + } + + getSubfieldMap = func(ast1 *ast.Field, type1 Output, ast2 *ast.Field, type2 Output) map[string][]*fieldDefPair { selectionSet1 := ast1.SelectionSet selectionSet2 := ast2.SelectionSet if selectionSet1 != nil && selectionSet2 != nil { @@ -1324,32 +1416,34 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul visitedFragmentNames, subfieldMap, ) - conflicts := findConflicts(subfieldMap) - if len(conflicts) > 0 { - - conflictReasons := []conflictReason{} - conflictFieldsLeft := []ast.Node{ast1} - conflictFieldsRight := []ast.Node{ast2} - for _, c := range conflicts { - conflictReasons = append(conflictReasons, c.Reason) - conflictFieldsLeft = append(conflictFieldsLeft, c.FieldsLeft...) - conflictFieldsRight = append(conflictFieldsRight, c.FieldsRight...) - } + return subfieldMap + } + return nil + } - return &conflict{ - Reason: conflictReason{ - Name: responseName, - Message: conflictReasons, - }, - FieldsLeft: conflictFieldsLeft, - FieldsRight: conflictFieldsRight, - } + subfieldConflicts = func(conflicts []*conflict, responseName string, ast1 *ast.Field, ast2 *ast.Field) *conflict { + if len(conflicts) > 0 { + conflictReasons := []conflictReason{} + conflictFieldsLeft := []ast.Node{ast1} + conflictFieldsRight := []ast.Node{ast2} + for _, c := range conflicts { + conflictReasons = append(conflictReasons, c.Reason) + conflictFieldsLeft = append(conflictFieldsLeft, c.FieldsLeft...) + conflictFieldsRight = append(conflictFieldsRight, c.FieldsRight...) + } + + return &conflict{ + Reason: conflictReason{ + Name: responseName, + Message: conflictReasons, + }, + FieldsLeft: conflictFieldsLeft, + FieldsRight: conflictFieldsRight, } } return nil } - - findConflicts = func(fieldMap map[string][]*fieldDefPair) (conflicts []*conflict) { + findConflicts = func(parentFieldsAreMutuallyExclusive bool, fieldMap map[string][]*fieldDefPair) (conflicts []*conflict) { // ensure field traversal orderedName := sort.StringSlice{} @@ -1362,7 +1456,7 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul fields, _ := fieldMap[responseName] for _, fieldA := range fields { for _, fieldB := range fields { - c := findConflict(responseName, fieldA, fieldB) + c := findConflict(parentFieldsAreMutuallyExclusive, responseName, fieldA, fieldB) if c != nil { conflicts = append(conflicts, c) } @@ -1395,7 +1489,10 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.SelectionSet: visitor.NamedVisitFuncs{ + kinds.SelectionSet: { + // Note: we validate on the reverse traversal so deeper conflicts will be + // caught first, for correct calculation of mutual exclusivity and for + // clearer error messages. Leave: func(p visitor.VisitFuncParams) (string, interface{}) { if selectionSet, ok := p.Node.(*ast.SelectionSet); ok && selectionSet != nil { parentType, _ := context.ParentType().(Named) @@ -1406,7 +1503,7 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul nil, nil, ) - conflicts := findConflicts(fieldMap) + conflicts := findConflicts(false, fieldMap) if len(conflicts) > 0 { for _, c := range conflicts { responseName := c.Reason.Name @@ -1443,7 +1540,7 @@ func getFragmentType(context *ValidationContext, name string) Type { return ttype } -func doTypesOverlap(t1 Type, t2 Type) bool { +func doTypesOverlap(schema *Schema, t1 Type, t2 Type) bool { if t1 == t2 { return true } @@ -1452,7 +1549,7 @@ func doTypesOverlap(t1 Type, t2 Type) bool { return false } if t2, ok := t2.(Abstract); ok { - for _, ttype := range t2.PossibleTypes() { + for _, ttype := range schema.PossibleTypes(t2) { if ttype == t1 { return true } @@ -1462,7 +1559,7 @@ func doTypesOverlap(t1 Type, t2 Type) bool { } if t1, ok := t1.(Abstract); ok { if _, ok := t2.(*Object); ok { - for _, ttype := range t1.PossibleTypes() { + for _, ttype := range schema.PossibleTypes(t1) { if ttype == t2 { return true } @@ -1470,11 +1567,11 @@ func doTypesOverlap(t1 Type, t2 Type) bool { return false } t1TypeNames := map[string]bool{} - for _, ttype := range t1.PossibleTypes() { + for _, ttype := range schema.PossibleTypes(t1) { t1TypeNames[ttype.Name()] = true } if t2, ok := t2.(Abstract); ok { - for _, ttype := range t2.PossibleTypes() { + for _, ttype := range schema.PossibleTypes(t2) { if hasT1TypeName, _ := t1TypeNames[ttype.Name()]; hasT1TypeName { return true } @@ -1494,13 +1591,13 @@ func PossibleFragmentSpreadsRule(context *ValidationContext) *ValidationRuleInst visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.InlineFragment: visitor.NamedVisitFuncs{ + kinds.InlineFragment: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.InlineFragment); ok && node != nil { fragType := context.Type() parentType, _ := context.ParentType().(Type) - if fragType != nil && parentType != nil && !doTypesOverlap(fragType, parentType) { + if fragType != nil && parentType != nil && !doTypesOverlap(context.Schema(), fragType, parentType) { reportError( context, fmt.Sprintf(`Fragment cannot be spread here as objects of `+ @@ -1512,7 +1609,7 @@ func PossibleFragmentSpreadsRule(context *ValidationContext) *ValidationRuleInst return visitor.ActionNoChange, nil }, }, - kinds.FragmentSpread: visitor.NamedVisitFuncs{ + kinds.FragmentSpread: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentSpread); ok && node != nil { fragName := "" @@ -1521,7 +1618,7 @@ func PossibleFragmentSpreadsRule(context *ValidationContext) *ValidationRuleInst } fragType := getFragmentType(context, fragName) parentType, _ := context.ParentType().(Type) - if fragType != nil && parentType != nil && !doTypesOverlap(fragType, parentType) { + if fragType != nil && parentType != nil && !doTypesOverlap(context.Schema(), fragType, parentType) { reportError( context, fmt.Sprintf(`Fragment "%v" cannot be spread here as objects of `+ @@ -1548,7 +1645,7 @@ func ProvidedNonNullArgumentsRule(context *ValidationContext) *ValidationRuleIns visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Field: visitor.NamedVisitFuncs{ + kinds.Field: { Leave: func(p visitor.VisitFuncParams) (string, interface{}) { // Validate on leave to allow for deeper errors to appear first. if fieldAST, ok := p.Node.(*ast.Field); ok && fieldAST != nil { @@ -1588,7 +1685,7 @@ func ProvidedNonNullArgumentsRule(context *ValidationContext) *ValidationRuleIns return visitor.ActionNoChange, nil }, }, - kinds.Directive: visitor.NamedVisitFuncs{ + kinds.Directive: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { // Validate on leave to allow for deeper errors to appear first. @@ -1644,7 +1741,7 @@ func ScalarLeafsRule(context *ValidationContext) *ValidationRuleInstance { visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Field: visitor.NamedVisitFuncs{ + kinds.Field: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Field); ok && node != nil { nodeName := "" @@ -1689,19 +1786,19 @@ func UniqueArgumentNamesRule(context *ValidationContext) *ValidationRuleInstance visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.Field: visitor.NamedVisitFuncs{ + kinds.Field: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { knownArgNames = map[string]*ast.Name{} return visitor.ActionNoChange, nil }, }, - kinds.Directive: visitor.NamedVisitFuncs{ + kinds.Directive: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { knownArgNames = map[string]*ast.Name{} return visitor.ActionNoChange, nil }, }, - kinds.Argument: visitor.NamedVisitFuncs{ + kinds.Argument: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.Argument); ok { argName := "" @@ -1736,12 +1833,12 @@ func UniqueFragmentNamesRule(context *ValidationContext) *ValidationRuleInstance visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { return visitor.ActionSkip, nil }, }, - kinds.FragmentDefinition: visitor.NamedVisitFuncs{ + kinds.FragmentDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.FragmentDefinition); ok && node != nil { fragmentName := "" @@ -1826,7 +1923,7 @@ func UniqueOperationNamesRule(context *ValidationContext) *ValidationRuleInstanc visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.OperationDefinition); ok && node != nil { operationName := "" @@ -1911,7 +2008,7 @@ func VariablesAreInputTypesRule(context *ValidationContext) *ValidationRuleInsta visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.VariableDefinition: visitor.NamedVisitFuncs{ + kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if node, ok := p.Node.(*ast.VariableDefinition); ok && node != nil { ttype, _ := typeFromAST(*context.Schema(), node.Type) @@ -1958,7 +2055,7 @@ func VariablesInAllowedPositionRule(context *ValidationContext) *ValidationRuleI visitorOpts := &visitor.VisitorOptions{ KindFuncMap: map[string]visitor.NamedVisitFuncs{ - kinds.OperationDefinition: visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { Enter: func(p visitor.VisitFuncParams) (string, interface{}) { varDefMap = map[string]*ast.VariableDefinition{} return visitor.ActionNoChange, nil @@ -1978,7 +2075,7 @@ func VariablesInAllowedPositionRule(context *ValidationContext) *ValidationRuleI if err != nil { varType = nil } - if varType != nil && !isTypeSubTypeOf(effectiveType(varType, varDef), usage.Type) { + if varType != nil && !isTypeSubTypeOf(context.Schema(), effectiveType(varType, varDef), usage.Type) { reportError( context, fmt.Sprintf(`Variable "$%v" of type "%v" used in position `+ @@ -1993,7 +2090,7 @@ func VariablesInAllowedPositionRule(context *ValidationContext) *ValidationRuleI return visitor.ActionNoChange, nil }, }, - kinds.VariableDefinition: visitor.NamedVisitFuncs{ + kinds.VariableDefinition: { Kind: func(p visitor.VisitFuncParams) (string, interface{}) { if varDefAST, ok := p.Node.(*ast.VariableDefinition); ok { defName := "" diff --git a/rules_known_directives_rule_test.go b/rules_known_directives_rule_test.go index 1a5e7d5e..ea6c07e9 100644 --- a/rules_known_directives_rule_test.go +++ b/rules_known_directives_rule_test.go @@ -79,8 +79,8 @@ func TestValidate_KnownDirectives_WithMisplacedDirectives(t *testing.T) { ...Frag @operationOnly } `, []gqlerrors.FormattedError{ - testutil.RuleError(`Directive "include" may not be used on "operation".`, 2, 17), - testutil.RuleError(`Directive "operationOnly" may not be used on "field".`, 3, 14), - testutil.RuleError(`Directive "operationOnly" may not be used on "fragment".`, 4, 17), + testutil.RuleError(`Directive "include" may not be used on QUERY.`, 2, 17), + testutil.RuleError(`Directive "operationOnly" may not be used on FIELD.`, 3, 14), + testutil.RuleError(`Directive "operationOnly" may not be used on FRAGMENT_SPREAD.`, 4, 17), }) } diff --git a/rules_overlapping_fields_can_be_merged_test.go b/rules_overlapping_fields_can_be_merged_test.go index 903367ea..b38b13a1 100644 --- a/rules_overlapping_fields_can_be_merged_test.go +++ b/rules_overlapping_fields_can_be_merged_test.go @@ -286,51 +286,85 @@ func TestValidate_OverlappingFieldsCanBeMerged_ReportsDeepConflictToNearestCommo var someBoxInterface *graphql.Interface var stringBoxObject *graphql.Object +var intBoxObject *graphql.Object var schema graphql.Schema func init() { someBoxInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "SomeBox", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return stringBoxObject }, - Fields: graphql.Fields{ - "unrelatedField": &graphql.Field{ - Type: graphql.String, - }, - }, + Fields: graphql.FieldsThunk(func() graphql.Fields { + return graphql.Fields{ + "deepBox": &graphql.Field{ + Type: someBoxInterface, + }, + "unrelatedField": &graphql.Field{ + Type: graphql.String, + }, + } + }), }) stringBoxObject = graphql.NewObject(graphql.ObjectConfig{ Name: "StringBox", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface} }), - Fields: graphql.Fields{ - "scalar": &graphql.Field{ - Type: graphql.String, - }, - "unrelatedField": &graphql.Field{ - Type: graphql.String, - }, - }, + Fields: graphql.FieldsThunk(func() graphql.Fields { + return graphql.Fields{ + "scalar": &graphql.Field{ + Type: graphql.String, + }, + "deepBox": &graphql.Field{ + Type: stringBoxObject, + }, + "unrelatedField": &graphql.Field{ + Type: graphql.String, + }, + "listStringBox": &graphql.Field{ + Type: graphql.NewList(stringBoxObject), + }, + "stringBox": &graphql.Field{ + Type: stringBoxObject, + }, + "intBox": &graphql.Field{ + Type: intBoxObject, + }, + } + }), }) - _ = graphql.NewObject(graphql.ObjectConfig{ + intBoxObject = graphql.NewObject(graphql.ObjectConfig{ Name: "IntBox", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface} }), - Fields: graphql.Fields{ - "scalar": &graphql.Field{ - Type: graphql.Int, - }, - "unrelatedField": &graphql.Field{ - Type: graphql.String, - }, - }, + Fields: graphql.FieldsThunk(func() graphql.Fields { + return graphql.Fields{ + "scalar": &graphql.Field{ + Type: graphql.Int, + }, + "deepBox": &graphql.Field{ + Type: someBoxInterface, + }, + "unrelatedField": &graphql.Field{ + Type: graphql.String, + }, + "listStringBox": &graphql.Field{ + Type: graphql.NewList(stringBoxObject), + }, + "stringBox": &graphql.Field{ + Type: stringBoxObject, + }, + "intBox": &graphql.Field{ + Type: intBoxObject, + }, + } + }), }) var nonNullStringBox1Interface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "NonNullStringBox1", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return stringBoxObject }, Fields: graphql.Fields{ @@ -339,7 +373,7 @@ func init() { }, }, }) - _ = graphql.NewObject(graphql.ObjectConfig{ + NonNullStringBox1Impl := graphql.NewObject(graphql.ObjectConfig{ Name: "NonNullStringBox1Impl", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface, nonNullStringBox1Interface} @@ -351,11 +385,14 @@ func init() { "unrelatedField": &graphql.Field{ Type: graphql.String, }, + "deepBox": &graphql.Field{ + Type: someBoxInterface, + }, }, }) var nonNullStringBox2Interface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "NonNullStringBox2", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return stringBoxObject }, Fields: graphql.Fields{ @@ -364,7 +401,7 @@ func init() { }, }, }) - _ = graphql.NewObject(graphql.ObjectConfig{ + NonNullStringBox2Impl := graphql.NewObject(graphql.ObjectConfig{ Name: "NonNullStringBox2Impl", Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface { return []*graphql.Interface{someBoxInterface, nonNullStringBox2Interface} @@ -376,6 +413,9 @@ func init() { "unrelatedField": &graphql.Field{ Type: graphql.String, }, + "deepBox": &graphql.Field{ + Type: someBoxInterface, + }, }, }) @@ -417,6 +457,12 @@ func init() { }, }, }), + Types: []graphql.Type{ + intBoxObject, + stringBoxObject, + NonNullStringBox1Impl, + NonNullStringBox2Impl, + }, }) if err != nil { panic(err) @@ -441,20 +487,172 @@ func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_Conf } `, []gqlerrors.FormattedError{ testutil.RuleError( - `Fields "scalar" conflict because they return differing types Int and String!.`, + `Fields "scalar" conflict because they return conflicting types Int and String!.`, 5, 15, 8, 15), }) } -func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_AllowsDiffereingReturnTypesWhichCannotOverlap(t *testing.T) { - // This is valid since an object cannot be both an IntBox and a StringBox. +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_CompatibleReturnShapesOnDifferentReturnTypes(t *testing.T) { + // In this case `deepBox` returns `SomeBox` in the first usage, and + // `StringBox` in the second usage. These return types are not the same! + // however this is valid because the return *shapes* are compatible. testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on SomeBox { + deepBox { + unrelatedField + } + } + ... on StringBox { + deepBox { + unrelatedField + } + } + } + } + `) +} +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypesDespiteNoOverlap(t *testing.T) { + testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` { someBox { - ...on IntBox { + ... on IntBox { + scalar + } + ... on StringBox { + scalar + } + } + } + `, []gqlerrors.FormattedError{ + testutil.RuleError( + `Fields "scalar" conflict because they return conflicting types Int and String.`, + 5, 15, + 8, 15), + }) +} +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap(t *testing.T) { + testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on NonNullStringBox1 { + scalar + } + ... on StringBox { scalar } - ...on StringBox { + } + } + `, []gqlerrors.FormattedError{ + testutil.RuleError( + `Fields "scalar" conflict because they return conflicting types String! and String.`, + 5, 15, + 8, 15), + }) +} +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingReturnTypeListDespiteNoOverlap(t *testing.T) { + testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on IntBox { + box: listStringBox { + scalar + } + } + ... on StringBox { + box: stringBox { + scalar + } + } + } + } + `, []gqlerrors.FormattedError{ + testutil.RuleError( + `Fields "box" conflict because they return conflicting types [StringBox] and StringBox.`, + 5, 15, + 10, 15), + }) + + testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on IntBox { + box: stringBox { + scalar + } + } + ... on StringBox { + box: listStringBox { + scalar + } + } + } + } + `, []gqlerrors.FormattedError{ + testutil.RuleError( + `Fields "box" conflict because they return conflicting types StringBox and [StringBox].`, + 5, 15, + 10, 15), + }) +} +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingSubfields(t *testing.T) { + testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on IntBox { + box: stringBox { + val: scalar + val: unrelatedField + } + } + ... on StringBox { + box: stringBox { + val: scalar + } + } + } + } + `, []gqlerrors.FormattedError{ + testutil.RuleError( + `Fields "val" conflict because scalar and unrelatedField are different fields.`, + 6, 17, + 7, 17), + }) +} +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_DisallowsDifferingDeepReturnTypesDespiteNoOverlap(t *testing.T) { + testutil.ExpectFailsRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on IntBox { + box: stringBox { + scalar + } + } + ... on StringBox { + box: intBox { + scalar + } + } + } + } + `, []gqlerrors.FormattedError{ + testutil.RuleError( + `Fields "box" conflict because subfields "scalar" conflict because they return conflicting types String and Int.`, + 5, 15, + 6, 17, + 10, 15, + 11, 17), + }) +} +func TestValidate_OverlappingFieldsCanBeMerged_ReturnTypesMustBeUnambiguous_AllowsNonConflictingOverlappingTypes(t *testing.T) { + testutil.ExpectPassesRuleWithSchema(t, &schema, graphql.OverlappingFieldsCanBeMergedRule, ` + { + someBox { + ... on IntBox { + scalar: unrelatedField + } + ... on StringBox { scalar } } diff --git a/schema-kitchen-sink.graphql b/schema-kitchen-sink.graphql index 052dfbe4..efc1b469 100644 --- a/schema-kitchen-sink.graphql +++ b/schema-kitchen-sink.graphql @@ -1,5 +1,10 @@ # Filename: schema-kitchen-sink.graphql +schema { + query: QueryType + mutation: MutationType +} + type Foo implements Bar { one: Type two(argument: InputType!): Type @@ -31,3 +36,10 @@ input InputType { extend type Foo { seven(argument: [String]): Type } + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include(if: Boolean!) + on FIELD + | FRAGMENT_SPREAD + | INLINE_FRAGMENT diff --git a/schema.go b/schema.go index 108cdbac..f3a4eaff 100644 --- a/schema.go +++ b/schema.go @@ -8,6 +8,7 @@ type SchemaConfig struct { Query *Object Mutation *Object Subscription *Object + Types []Type Directives []*Directive } @@ -30,6 +31,8 @@ type Schema struct { queryType *Object mutationType *Object subscriptionType *Object + implementations map[string][]*Object + possibleTypeMap map[string]map[string]bool } func NewSchema(config SchemaConfig) (Schema, error) { @@ -62,35 +65,68 @@ func NewSchema(config SchemaConfig) (Schema, error) { SkipDirective, } } + // Ensure directive definitions are error-free + for _, dir := range schema.directives { + if dir.err != nil { + return schema, dir.err + } + } // Build type map now to detect any errors within this schema. typeMap := TypeMap{} - objectTypes := []*Object{ - schema.QueryType(), - schema.MutationType(), - schema.SubscriptionType(), - typeType, - schemaType, + initialTypes := []Type{} + if schema.QueryType() != nil { + initialTypes = append(initialTypes, schema.QueryType()) } - for _, objectType := range objectTypes { - if objectType == nil { - continue - } - if objectType.err != nil { - return schema, objectType.err + if schema.MutationType() != nil { + initialTypes = append(initialTypes, schema.MutationType()) + } + if schema.SubscriptionType() != nil { + initialTypes = append(initialTypes, schema.SubscriptionType()) + } + if schemaType != nil { + initialTypes = append(initialTypes, schemaType) + } + + for _, ttype := range config.Types { + // assume that user will never add a nil object to config + initialTypes = append(initialTypes, ttype) + } + + for _, ttype := range initialTypes { + if ttype.Error() != nil { + return schema, ttype.Error() } - typeMap, err = typeMapReducer(typeMap, objectType) + typeMap, err = typeMapReducer(&schema, typeMap, ttype) if err != nil { return schema, err } } + schema.typeMap = typeMap + + // Keep track of all implementations by interface name. + if schema.implementations == nil { + schema.implementations = map[string][]*Object{} + } + for _, ttype := range schema.typeMap { + if ttype, ok := ttype.(*Object); ok { + for _, iface := range ttype.Interfaces() { + impls, ok := schema.implementations[iface.Name()] + if impls == nil || !ok { + impls = []*Object{} + } + impls = append(impls, ttype) + schema.implementations[iface.Name()] = impls + } + } + } + // Enforce correct interface implementations - for _, ttype := range typeMap { - switch ttype := ttype.(type) { - case *Object: + for _, ttype := range schema.typeMap { + if ttype, ok := ttype.(*Object); ok { for _, iface := range ttype.Interfaces() { - err := assertObjectImplementsInterface(ttype, iface) + err := assertObjectImplementsInterface(&schema, ttype, iface) if err != nil { return schema, err } @@ -134,7 +170,39 @@ func (gq *Schema) Type(name string) Type { return gq.TypeMap()[name] } -func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { +func (gq *Schema) PossibleTypes(abstractType Abstract) []*Object { + if abstractType, ok := abstractType.(*Union); ok { + return abstractType.Types() + } + if abstractType, ok := abstractType.(*Interface); ok { + if impls, ok := gq.implementations[abstractType.Name()]; ok { + return impls + } + } + return []*Object{} +} +func (gq *Schema) IsPossibleType(abstractType Abstract, possibleType *Object) bool { + possibleTypeMap := gq.possibleTypeMap + if possibleTypeMap == nil { + possibleTypeMap = map[string]map[string]bool{} + } + + if typeMap, ok := possibleTypeMap[abstractType.Name()]; !ok { + typeMap = map[string]bool{} + for _, possibleType := range gq.PossibleTypes(abstractType) { + typeMap[possibleType.Name()] = true + } + possibleTypeMap[abstractType.Name()] = typeMap + } + + gq.possibleTypeMap = possibleTypeMap + if typeMap, ok := possibleTypeMap[abstractType.Name()]; ok { + isPossible, _ := typeMap[possibleType.Name()] + return isPossible + } + return false +} +func typeMapReducer(schema *Schema, typeMap TypeMap, objectType Type) (TypeMap, error) { var err error if objectType == nil || objectType.Name() == "" { return typeMap, nil @@ -143,11 +211,11 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { switch objectType := objectType.(type) { case *List: if objectType.OfType != nil { - return typeMapReducer(typeMap, objectType.OfType) + return typeMapReducer(schema, typeMap, objectType.OfType) } case *NonNull: if objectType.OfType != nil { - return typeMapReducer(typeMap, objectType.OfType) + return typeMapReducer(schema, typeMap, objectType.OfType) } case *Object: if objectType.err != nil { @@ -173,7 +241,7 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { switch objectType := objectType.(type) { case *Union: - types := objectType.PossibleTypes() + types := schema.PossibleTypes(objectType) if objectType.err != nil { return typeMap, objectType.err } @@ -181,13 +249,13 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { if innerObjectType.err != nil { return typeMap, innerObjectType.err } - typeMap, err = typeMapReducer(typeMap, innerObjectType) + typeMap, err = typeMapReducer(schema, typeMap, innerObjectType) if err != nil { return typeMap, err } } case *Interface: - types := objectType.PossibleTypes() + types := schema.PossibleTypes(objectType) if objectType.err != nil { return typeMap, objectType.err } @@ -195,7 +263,7 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { if innerObjectType.err != nil { return typeMap, innerObjectType.err } - typeMap, err = typeMapReducer(typeMap, innerObjectType) + typeMap, err = typeMapReducer(schema, typeMap, innerObjectType) if err != nil { return typeMap, err } @@ -209,7 +277,7 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { if innerObjectType.err != nil { return typeMap, innerObjectType.err } - typeMap, err = typeMapReducer(typeMap, innerObjectType) + typeMap, err = typeMapReducer(schema, typeMap, innerObjectType) if err != nil { return typeMap, err } @@ -224,12 +292,12 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { } for _, field := range fieldMap { for _, arg := range field.Args { - typeMap, err = typeMapReducer(typeMap, arg.Type) + typeMap, err = typeMapReducer(schema, typeMap, arg.Type) if err != nil { return typeMap, err } } - typeMap, err = typeMapReducer(typeMap, field.Type) + typeMap, err = typeMapReducer(schema, typeMap, field.Type) if err != nil { return typeMap, err } @@ -241,12 +309,12 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { } for _, field := range fieldMap { for _, arg := range field.Args { - typeMap, err = typeMapReducer(typeMap, arg.Type) + typeMap, err = typeMapReducer(schema, typeMap, arg.Type) if err != nil { return typeMap, err } } - typeMap, err = typeMapReducer(typeMap, field.Type) + typeMap, err = typeMapReducer(schema, typeMap, field.Type) if err != nil { return typeMap, err } @@ -257,7 +325,7 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { return typeMap, objectType.err } for _, field := range fieldMap { - typeMap, err = typeMapReducer(typeMap, field.Type) + typeMap, err = typeMapReducer(schema, typeMap, field.Type) if err != nil { return typeMap, err } @@ -266,7 +334,7 @@ func typeMapReducer(typeMap TypeMap, objectType Type) (TypeMap, error) { return typeMap, nil } -func assertObjectImplementsInterface(object *Object, iface *Interface) error { +func assertObjectImplementsInterface(schema *Schema, object *Object, iface *Interface) error { objectFieldMap := object.Fields() ifaceFieldMap := iface.Fields() @@ -288,7 +356,7 @@ func assertObjectImplementsInterface(object *Object, iface *Interface) error { // Assert interface field type is satisfied by object field type, by being // a valid subtype. (covariant) err = invariant( - isTypeSubTypeOf(objectField.Type, ifaceField.Type), + isTypeSubTypeOf(schema, objectField.Type, ifaceField.Type), fmt.Sprintf(`%v.%v expects type "%v" but `+ `%v.%v provides type "%v".`, iface, fieldName, ifaceField.Type, @@ -389,7 +457,7 @@ func isEqualType(typeA Type, typeB Type) bool { * Provided a type and a super type, return true if the first type is either * equal or a subset of the second super type (covariant). */ -func isTypeSubTypeOf(maybeSubType Type, superType Type) bool { +func isTypeSubTypeOf(schema *Schema, maybeSubType Type, superType Type) bool { // Equivalent type is a valid subtype if maybeSubType == superType { return true @@ -398,19 +466,19 @@ func isTypeSubTypeOf(maybeSubType Type, superType Type) bool { // If superType is non-null, maybeSubType must also be nullable. if superType, ok := superType.(*NonNull); ok { if maybeSubType, ok := maybeSubType.(*NonNull); ok { - return isTypeSubTypeOf(maybeSubType.OfType, superType.OfType) + return isTypeSubTypeOf(schema, maybeSubType.OfType, superType.OfType) } return false } if maybeSubType, ok := maybeSubType.(*NonNull); ok { // If superType is nullable, maybeSubType may be non-null. - return isTypeSubTypeOf(maybeSubType.OfType, superType) + return isTypeSubTypeOf(schema, maybeSubType.OfType, superType) } // If superType type is a list, maybeSubType type must also be a list. if superType, ok := superType.(*List); ok { if maybeSubType, ok := maybeSubType.(*List); ok { - return isTypeSubTypeOf(maybeSubType.OfType, superType.OfType) + return isTypeSubTypeOf(schema, maybeSubType.OfType, superType.OfType) } return false } else if _, ok := maybeSubType.(*List); ok { @@ -421,12 +489,12 @@ func isTypeSubTypeOf(maybeSubType Type, superType Type) bool { // If superType type is an abstract type, maybeSubType type may be a currently // possible object type. if superType, ok := superType.(*Interface); ok { - if maybeSubType, ok := maybeSubType.(*Object); ok && superType.IsPossibleType(maybeSubType) { + if maybeSubType, ok := maybeSubType.(*Object); ok && schema.IsPossibleType(superType, maybeSubType) { return true } } if superType, ok := superType.(*Union); ok { - if maybeSubType, ok := maybeSubType.(*Object); ok && superType.IsPossibleType(maybeSubType) { + if maybeSubType, ok := maybeSubType.(*Object); ok && schema.IsPossibleType(superType, maybeSubType) { return true } } diff --git a/testutil/introspection_query.go b/testutil/introspection_query.go index 555ad9df..d92b81e2 100644 --- a/testutil/introspection_query.go +++ b/testutil/introspection_query.go @@ -12,10 +12,12 @@ var IntrospectionQuery = ` directives { name description + locations args { ...InputValue } - onOperation + # deprecated, but included for coverage till removed + onOperation onFragment onField } diff --git a/testutil/rules_test_harness.go b/testutil/rules_test_harness.go index 3691f6c4..035d8a61 100644 --- a/testutil/rules_test_harness.go +++ b/testutil/rules_test_harness.go @@ -70,7 +70,7 @@ func init() { }) var dogType = graphql.NewObject(graphql.ObjectConfig{ Name: "Dog", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ @@ -146,7 +146,7 @@ func init() { var catType = graphql.NewObject(graphql.ObjectConfig{ Name: "Cat", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ @@ -182,7 +182,7 @@ func init() { dogType, catType, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { // not used for validation return nil }, @@ -198,7 +198,7 @@ func init() { var humanType = graphql.NewObject(graphql.ObjectConfig{ Name: "Human", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Interfaces: []*graphql.Interface{ @@ -229,7 +229,7 @@ func init() { var alienType = graphql.NewObject(graphql.ObjectConfig{ Name: "Alien", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Interfaces: []*graphql.Interface{ @@ -259,7 +259,7 @@ func init() { dogType, humanType, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { // not used for validation return nil }, @@ -270,7 +270,7 @@ func init() { alienType, humanType, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { // not used for validation return nil }, @@ -459,13 +459,19 @@ func init() { schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryRoot, Directives: []*graphql.Directive{ - graphql.NewDirective(&graphql.Directive{ - Name: "operationOnly", - OnOperation: true, + graphql.NewDirective(graphql.DirectiveConfig{ + Name: "operationOnly", + Locations: []string{graphql.DirectiveLocationQuery}, }), graphql.IncludeDirective, graphql.SkipDirective, }, + Types: []graphql.Type{ + catType, + dogType, + humanType, + alienType, + }, }) if err != nil { panic(err) diff --git a/testutil/testutil.go b/testutil/testutil.go index 9951d8f3..0a5ccc87 100644 --- a/testutil/testutil.go +++ b/testutil/testutil.go @@ -133,8 +133,8 @@ func init() { Description: "Which movies they appear in.", }, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { - if character, ok := value.(StarWarsChar); ok { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + if character, ok := p.Value.(StarWarsChar); ok { id, _ := strconv.Atoi(character.ID) human := GetHuman(id) if human.ID != "" { diff --git a/type_comparators_test.go b/type_comparators_test.go new file mode 100644 index 00000000..995166ef --- /dev/null +++ b/type_comparators_test.go @@ -0,0 +1,149 @@ +package graphql + +import ( + "testing" +) + +func TestIsEqualType_SameReferenceAreEqual(t *testing.T) { + if !isEqualType(String, String) { + t.Fatalf("Expected same reference to be equal") + } +} + +func TestIsEqualType_IntAndFloatAreNotEqual(t *testing.T) { + if isEqualType(Int, Float) { + t.Fatalf("Expected GraphQLInt and GraphQLFloat to not equal") + } +} + +func TestIsEqualType_ListsOfSameTypeAreEqual(t *testing.T) { + if !isEqualType(NewList(Int), NewList(Int)) { + t.Fatalf("Expected lists of same type are equal") + } +} + +func TestIsEqualType_ListsAreNotEqualToItem(t *testing.T) { + if isEqualType(NewList(Int), Int) { + t.Fatalf("Expected lists are not equal to item") + } +} + +func TestIsEqualType_NonNullOfSameTypeAreEqual(t *testing.T) { + if !isEqualType(NewNonNull(Int), NewNonNull(Int)) { + t.Fatalf("Expected non-null of same type are equal") + } +} +func TestIsEqualType_NonNullIsNotEqualToNullable(t *testing.T) { + if isEqualType(NewNonNull(Int), Int) { + t.Fatalf("Expected non-null is not equal to nullable") + } +} + +func testSchemaForIsTypeSubTypeOfTest(t *testing.T, fields Fields) *Schema { + schema, err := NewSchema(SchemaConfig{ + Query: NewObject(ObjectConfig{ + Name: "Query", + Fields: fields, + }), + }) + if err != nil { + t.Fatalf("Invalid schema: %v", err) + } + return &schema +} + +func TestIsTypeSubTypeOf_SameReferenceIsSubtype(t *testing.T) { + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: String}, + }) + if !isTypeSubTypeOf(schema, String, String) { + t.Fatalf("Expected same reference is subtype") + } +} +func TestIsTypeSubTypeOf_IntIsNotSubtypeOfFloat(t *testing.T) { + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: String}, + }) + if isTypeSubTypeOf(schema, Int, Float) { + t.Fatalf("Expected int is not subtype of float") + } +} +func TestIsTypeSubTypeOf_NonNullIsSubtypeOfNullable(t *testing.T) { + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: String}, + }) + if !isTypeSubTypeOf(schema, NewNonNull(Int), Int) { + t.Fatalf("Expected non-null is subtype of nullable") + } +} +func TestIsTypeSubTypeOf_NullableIsNotSubtypeOfNonNull(t *testing.T) { + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: String}, + }) + if isTypeSubTypeOf(schema, Int, NewNonNull(Int)) { + t.Fatalf("Expected nullable is not subtype of non-null") + } +} +func TestIsTypeSubTypeOf_ItemIsNotSubTypeOfList(t *testing.T) { + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: String}, + }) + if isTypeSubTypeOf(schema, Int, NewList(Int)) { + t.Fatalf("Expected item is not subtype of list") + } +} +func TestIsTypeSubTypeOf_ListIsNotSubtypeOfItem(t *testing.T) { + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: String}, + }) + if isTypeSubTypeOf(schema, NewList(Int), Int) { + t.Fatalf("Expected list is not subtype of item") + } +} + +func TestIsTypeSubTypeOf_MemberIsSubtypeOfUnion(t *testing.T) { + memberType := NewObject(ObjectConfig{ + Name: "Object", + IsTypeOf: func(p IsTypeOfParams) bool { + return true + }, + Fields: Fields{ + "field": &Field{Type: String}, + }, + }) + unionType := NewUnion(UnionConfig{ + Name: "Union", + Types: []*Object{memberType}, + }) + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: unionType}, + }) + if !isTypeSubTypeOf(schema, memberType, unionType) { + t.Fatalf("Expected member is subtype of union") + } +} + +func TestIsTypeSubTypeOf_ImplementationIsSubtypeOfInterface(t *testing.T) { + ifaceType := NewInterface(InterfaceConfig{ + Name: "Interface", + Fields: Fields{ + "field": &Field{Type: String}, + }, + }) + implType := NewObject(ObjectConfig{ + Name: "Object", + IsTypeOf: func(p IsTypeOfParams) bool { + return true + }, + Interfaces: []*Interface{ifaceType}, + Fields: Fields{ + "field": &Field{Type: String}, + }, + }) + schema := testSchemaForIsTypeSubTypeOfTest(t, Fields{ + "field": &Field{Type: implType}, + }) + if !isTypeSubTypeOf(schema, implType, ifaceType) { + t.Fatalf("Expected implementation is subtype of interface") + } +} diff --git a/type_info.go b/type_info.go index 3c06c29b..39838295 100644 --- a/type_info.go +++ b/type_info.go @@ -110,11 +110,11 @@ func (ti *TypeInfo) Enter(node ast.Node) { } ti.directive = schema.Directive(nameVal) case *ast.OperationDefinition: - if node.Operation == "query" { + if node.Operation == ast.OperationTypeQuery { ttype = schema.QueryType() - } else if node.Operation == "mutation" { + } else if node.Operation == ast.OperationTypeMutation { ttype = schema.MutationType() - } else if node.Operation == "subscription" { + } else if node.Operation == ast.OperationTypeSubscription { ttype = schema.SubscriptionType() } ti.typeStack = append(ti.typeStack, ttype) diff --git a/union_interface_test.go b/union_interface_test.go index 2da90f40..d4cc151b 100644 --- a/union_interface_test.go +++ b/union_interface_test.go @@ -6,6 +6,7 @@ import ( "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/testutil" + "golang.org/x/net/context" ) type testNamedType interface { @@ -49,8 +50,8 @@ var dogType = graphql.NewObject(graphql.ObjectConfig{ Type: graphql.Boolean, }, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testDog2) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testDog2) return ok }, }) @@ -67,8 +68,8 @@ var catType = graphql.NewObject(graphql.ObjectConfig{ Type: graphql.Boolean, }, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testCat2) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testCat2) return ok }, }) @@ -77,11 +78,11 @@ var petType = graphql.NewUnion(graphql.UnionConfig{ Types: []*graphql.Object{ dogType, catType, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { - if _, ok := value.(*testCat2); ok { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + if _, ok := p.Value.(*testCat2); ok { return catType } - if _, ok := value.(*testDog2); ok { + if _, ok := p.Value.(*testDog2); ok { return dogType } return nil @@ -103,14 +104,15 @@ var personType = graphql.NewObject(graphql.ObjectConfig{ Type: graphql.NewList(namedType), }, }, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { - _, ok := value.(*testPerson) + IsTypeOf: func(p graphql.IsTypeOfParams) bool { + _, ok := p.Value.(*testPerson) return ok }, }) var unionInterfaceTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ Query: personType, + Types: []graphql.Type{petType}, }) var garfield = &testCat2{"Garfield", false} @@ -206,8 +208,8 @@ func TestUnionIntersectionTypes_CanIntrospectOnUnionAndIntersectionTypes(t *test if len(result.Errors) != len(expected.Errors) { t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) } - if !reflect.DeepEqual(expected, result) { - t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) + if !testutil.ContainSubset(expected.Data.(map[string]interface{}), result.Data.(map[string]interface{})) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected.Data, result.Data)) } } func TestUnionIntersectionTypes_ExecutesUsingUnionTypes(t *testing.T) { @@ -497,6 +499,9 @@ func TestUnionIntersectionTypes_AllowsFragmentConditionsToBeAbstractTypes(t *tes } func TestUnionIntersectionTypes_GetsExecutionInfoInResolver(t *testing.T) { + var encounteredContextValue string + var encounteredSchema graphql.Schema + var encounteredRootValue string var personType2 *graphql.Object namedType2 := graphql.NewInterface(graphql.InterfaceConfig{ @@ -506,7 +511,10 @@ func TestUnionIntersectionTypes_GetsExecutionInfoInResolver(t *testing.T) { Type: graphql.String, }, }, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { + encounteredSchema = p.Info.Schema + encounteredContextValue, _ = p.Context.Value("authToken").(string) + encounteredRootValue = p.Info.RootValue.(*testPerson).Name return personType2 }, }) @@ -551,17 +559,29 @@ func TestUnionIntersectionTypes_GetsExecutionInfoInResolver(t *testing.T) { // parse query ast := testutil.TestParse(t, doc) + // create context + ctx := context.Background() + ctx = context.WithValue(ctx, "authToken", "contextStringValue123") + // execute ep := graphql.ExecuteParams{ - Schema: schema2, - AST: ast, - Root: john2, + Schema: schema2, + AST: ast, + Root: john2, + Context: ctx, } result := testutil.TestExecute(t, ep) - if len(result.Errors) != len(expected.Errors) { - t.Fatalf("Unexpected errors, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) - } + if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } + if !reflect.DeepEqual("contextStringValue123", encounteredContextValue) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff("contextStringValue123", encounteredContextValue)) + } + if !reflect.DeepEqual("John", encounteredRootValue) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff("John", encounteredRootValue)) + } + if !reflect.DeepEqual(schema2, encounteredSchema) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(schema2, encounteredSchema)) + } } diff --git a/validation_test.go b/validation_test.go index 85d2c98d..6c8fc521 100644 --- a/validation_test.go +++ b/validation_test.go @@ -29,7 +29,7 @@ var someObjectType = graphql.NewObject(graphql.ObjectConfig{ }) var objectWithIsTypeOf = graphql.NewObject(graphql.ObjectConfig{ Name: "ObjectWithIsTypeOf", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ @@ -40,7 +40,7 @@ var objectWithIsTypeOf = graphql.NewObject(graphql.ObjectConfig{ }) var someUnionType = graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{ @@ -49,7 +49,7 @@ var someUnionType = graphql.NewUnion(graphql.UnionConfig{ }) var someInterfaceType = graphql.NewInterface(graphql.InterfaceConfig{ Name: "SomeInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -113,6 +113,7 @@ func schemaWithFieldType(ttype graphql.Output) (graphql.Schema, error) { }, }, }), + Types: []graphql.Type{ttype}, }) } func schemaWithInputObject(ttype graphql.Input) (graphql.Schema, error) { @@ -173,13 +174,14 @@ func schemaWithObjectImplementingType(implementedType *graphql.Interface) (graph }, }, }), + Types: []graphql.Type{badObjectType}, }) } func schemaWithUnionOfType(ttype *graphql.Object) (graphql.Schema, error) { badObjectType := graphql.NewUnion(graphql.UnionConfig{ Name: "BadUnion", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{ttype}, @@ -387,7 +389,7 @@ func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichHaveS anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -396,7 +398,7 @@ func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichHaveS }, }, }) - _ = graphql.NewObject(graphql.ObjectConfig{ + FirstBadObject := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Interfaces: []*graphql.Interface{ anotherInterface, @@ -407,7 +409,7 @@ func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichHaveS }, }, }) - _ = graphql.NewObject(graphql.ObjectConfig{ + SecondBadObject := graphql.NewObject(graphql.ObjectConfig{ Name: "BadObject", Interfaces: []*graphql.Interface{ anotherInterface, @@ -428,6 +430,7 @@ func TestTypeSystem_SchemaMustContainUniquelyNamedTypes_RejectsASchemaWhichHaveS }) _, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, + Types: []graphql.Type{FirstBadObject, SecondBadObject}, }) expectedError := `Schema must contain unique named types but contains multiple types named "BadObject".` if err == nil || err.Error() != expectedError { @@ -545,7 +548,7 @@ func TestTypeSystem_FieldsArgsMustBeObjects_AcceptsAnObjectTypeWithFieldArgs(t * func TestTypeSystem_ObjectInterfacesMustBeArray_AcceptsAnObjectTypeWithArrayInterfaces(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -573,7 +576,7 @@ func TestTypeSystem_ObjectInterfacesMustBeArray_AcceptsAnObjectTypeWithArrayInte func TestTypeSystem_ObjectInterfacesMustBeArray_AcceptsAnObjectTypeWithInterfacesAsFunctionReturningAnArray(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -599,7 +602,7 @@ func TestTypeSystem_ObjectInterfacesMustBeArray_AcceptsAnObjectTypeWithInterface func TestTypeSystem_UnionTypesMustBeArray_AcceptsAUnionTypeWithArrayTypes(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{ @@ -613,7 +616,7 @@ func TestTypeSystem_UnionTypesMustBeArray_AcceptsAUnionTypeWithArrayTypes(t *tes func TestTypeSystem_UnionTypesMustBeArray_RejectsAUnionTypeWithoutTypes(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, })) @@ -625,7 +628,7 @@ func TestTypeSystem_UnionTypesMustBeArray_RejectsAUnionTypeWithoutTypes(t *testi func TestTypeSystem_UnionTypesMustBeArray_RejectsAUnionTypeWithEmptyTypes(t *testing.T) { _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Types: []*graphql.Object{}, @@ -689,7 +692,7 @@ func TestTypeSystem_InputObjectsMustHaveFields_RejectsAnInputObjectTypeWithEmpty func TestTypeSystem_ObjectTypesMustBeAssertable_AcceptsAnObjectTypeWithAnIsTypeOfFunction(t *testing.T) { _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "AnotherObject", - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ @@ -707,7 +710,7 @@ func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceTypeDefinin anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -742,7 +745,7 @@ func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceWithImpleme _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: []*graphql.Interface{anotherInterfaceType}, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ @@ -760,7 +763,7 @@ func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceTypeDefinin anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -772,7 +775,7 @@ func TestTypeSystem_InterfaceTypesMustBeResolvable_AcceptsAnInterfaceTypeDefinin _, err := schemaWithFieldType(graphql.NewObject(graphql.ObjectConfig{ Name: "SomeObject", Interfaces: []*graphql.Interface{anotherInterfaceType}, - IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool { + IsTypeOf: func(p graphql.IsTypeOfParams) bool { return true }, Fields: graphql.Fields{ @@ -791,7 +794,7 @@ func TestTypeSystem_UnionTypesMustBeResolvable_AcceptsAUnionTypeDefiningResolveT _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: []*graphql.Object{someObjectType}, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, })) @@ -814,7 +817,7 @@ func TestTypeSystem_UnionTypesMustBeResolvable_AcceptsAUnionTypeDefiningResolveT _, err := schemaWithFieldType(graphql.NewUnion(graphql.UnionConfig{ Name: "SomeUnion", Types: []*graphql.Object{objectWithIsTypeOf}, - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, })) @@ -982,7 +985,7 @@ func TestTypeSystem_ObjectFieldsMustHaveOutputTypes_RejectsAnEmptyObjectFieldTyp func TestTypeSystem_ObjectsCanOnlyImplementInterfaces_AcceptsAnObjectImplementingAnInterface(t *testing.T) { anotherInterfaceType := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1120,7 +1123,7 @@ func TestTypeSystem_NonNullMustAcceptGraphQLTypes_RejectsNilAsNonNullableType(t func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhichImplementsAnInterface(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1156,7 +1159,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhi func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhichImplementsAnInterfaceAlongWithMoreFields(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1195,7 +1198,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhi func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhichImpementsAnInterfaceFieldAlongWithAdditionalOptionalArguments(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1234,7 +1237,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWhi func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWhichImplementsAnInterfaceFieldAlongWithAdditionalRequiredArguments(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1274,7 +1277,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWhi func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectMissingAnInterfaceField(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1307,7 +1310,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectMis func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithAnIncorrectlyTypedInterfaceField(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1353,7 +1356,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWit anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1382,7 +1385,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWit var anotherInterface *graphql.Interface anotherInterface = graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: (graphql.FieldsThunk)(func() graphql.Fields { @@ -1413,7 +1416,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithASubtypedInterfaceField_Union(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1439,7 +1442,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectMissingAnInterfaceArgument(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1471,7 +1474,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectMis func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithAnIncorrectlyTypedInterfaceArgument(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1508,7 +1511,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithAnEquivalentlyModifiedInterfaceField(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1534,7 +1537,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithANonListInterfaceFieldListType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1562,7 +1565,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithAListInterfaceFieldNonListType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1590,7 +1593,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWithSubsetNonNullInterfaceFieldType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ @@ -1617,7 +1620,7 @@ func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_AcceptsAnObjectWit func TestTypeSystem_ObjectsMustAdhereToInterfaceTheyImplement_RejectsAnObjectWithASupersetNullableInterfaceFieldType(t *testing.T) { anotherInterface := graphql.NewInterface(graphql.InterfaceConfig{ Name: "AnotherInterface", - ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { + ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { return nil }, Fields: graphql.Fields{ diff --git a/variables_test.go b/variables_test.go index adfe823c..545f3c7d 100644 --- a/variables_test.go +++ b/variables_test.go @@ -251,6 +251,33 @@ func TestVariables_ObjectsAndNullability_UsingInlineStructs_DoesNotUseIncorrectV t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) } } +func TestVariables_ObjectsAndNullability_UsingInlineStructs_ProperlyRunsParseLiteralOnComplexScalarTypes(t *testing.T) { + doc := ` + { + fieldWithObjectInput(input: {a: "foo", d: "SerializedValue"}) + } + ` + expected := &graphql.Result{ + Data: map[string]interface{}{ + "fieldWithObjectInput": `{"a":"foo","d":"DeserializedValue"}`, + }, + } + // parse query + ast := testutil.TestParse(t, doc) + + // execute + ep := graphql.ExecuteParams{ + Schema: variablesTestSchema, + AST: ast, + } + result := testutil.TestExecute(t, ep) + if len(result.Errors) > 0 { + t.Fatalf("wrong result, unexpected errors: %v", result.Errors) + } + if !reflect.DeepEqual(expected, result) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) + } +} func testVariables_ObjectsAndNullability_UsingVariables_GetAST(t *testing.T) *ast.Document { doc := ` @@ -390,11 +417,11 @@ func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnNullForNestedNon expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" got invalid value {"a":"foo","b":"bar","c":null}.` + "\nIn field \"c\": Expected \"String!\", found null.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -425,10 +452,10 @@ func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnIncorrectType(t expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: "Variable \"$input\" got invalid value \"foo bar\".\nExpected \"TestInputObject\", found not an object.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -462,11 +489,11 @@ func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnOmissionOfNested expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" got invalid value {"a":"foo","b":"bar"}.` + "\nIn field \"c\": Expected \"String!\", found null.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -501,7 +528,7 @@ func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnDeepNestedErrors expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" got invalid value {"na":{"a":"foo"}}.` + "\nIn field \"na\": In field \"c\": Expected \"String!\", found null." + "\nIn field \"nb\": Expected \"String!\", found null.", @@ -547,11 +574,11 @@ func TestVariables_ObjectsAndNullability_UsingVariables_ErrorsOnAdditionOfUnknow expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" got invalid value {"a":"foo","b":"bar","c":"baz","extra":"dog"}.` + "\nIn field \"extra\": Unknown field.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -758,10 +785,10 @@ func TestVariables_NonNullableScalars_DoesNotAllowNonNullableInputsToBeOmittedIn expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$value" of required type "String!" was not provided.`, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 31, }, }, @@ -797,10 +824,10 @@ func TestVariables_NonNullableScalars_DoesNotAllowNonNullableInputsToBeSetToNull expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$value" of required type "String!" was not provided.`, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 31, }, }, @@ -1025,10 +1052,10 @@ func TestVariables_ListsAndNullability_DoesNotAllowNonNullListsToBeNull(t *testi expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" of required type "[String]!" was not provided.`, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -1182,12 +1209,12 @@ func TestVariables_ListsAndNullability_DoesNotAllowListOfNonNullsToContainNull(t expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" got invalid value ` + `["A",null,"B"].` + "\nIn element #1: Expected \"String!\", found null.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -1222,10 +1249,10 @@ func TestVariables_ListsAndNullability_DoesNotAllowNonNullListOfNonNullsToBeNull expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" of required type "[String!]!" was not provided.`, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -1290,12 +1317,12 @@ func TestVariables_ListsAndNullability_DoesNotAllowNonNullListOfNonNullsToContai expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" got invalid value ` + `["A",null,"B"].` + "\nIn element #1: Expected \"String!\", found null.", Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -1332,10 +1359,10 @@ func TestVariables_ListsAndNullability_DoesNotAllowInvalidTypesToBeUsedAsValues( expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" expected value of type "TestType!" which cannot be used as an input type.`, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, }, @@ -1370,10 +1397,10 @@ func TestVariables_ListsAndNullability_DoesNotAllowUnknownTypesToBeUsedAsValues( expected := &graphql.Result{ Data: nil, Errors: []gqlerrors.FormattedError{ - gqlerrors.FormattedError{ + { Message: `Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type.`, Locations: []location.SourceLocation{ - location.SourceLocation{ + { Line: 2, Column: 17, }, },