diff --git a/codegen/field.go b/codegen/field.go index 0849900e04c..8f6e9d52bbe 100644 --- a/codegen/field.go +++ b/codegen/field.go @@ -196,25 +196,12 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) { } } -func (b *builder) findBindTarget(in types.Type, name string) (types.Object, error) { - switch t := in.(type) { - case *types.Named: - if _, ok := t.Underlying().(*types.Interface); ok { - return nil, errors.New("can't bind to an interface at root") - } - case *types.Interface: - return nil, errors.New("can't bind to an interface at root") - } - - return b.findBindTargetRecur(in, name) -} - -// findBindTargetRecur attempts to match the name to a field or method on a Type +// findBindTarget attempts to match the name to a field or method on a Type // with the following priorites: // 1. Any Fields with a struct tag (see config.StructTag). Errors if more than one match is found // 2. Any method or field with a matching name. Errors if more than one match is found // 3. Same logic again for embedded fields -func (b *builder) findBindTargetRecur(t types.Type, name string) (types.Object, error) { +func (b *builder) findBindTarget(t types.Type, name string) (types.Object, error) { // NOTE: a struct tag will override both methods and fields // Bind to struct tag found, err := b.findBindStructTagTarget(t, name) @@ -366,7 +353,7 @@ func (b *builder) findBindStructEmbedsTarget(strukt *types.Struct, name string) fieldType = ptr.Elem() } - f, err := b.findBindTargetRecur(fieldType, name) + f, err := b.findBindTarget(fieldType, name) if err != nil { return nil, err } @@ -388,7 +375,7 @@ func (b *builder) findBindInterfaceEmbedsTarget(iface *types.Interface, name str for i := 0; i < iface.NumEmbeddeds(); i++ { embeddedType := iface.EmbeddedType(i) - f, err := b.findBindTargetRecur(embeddedType, name) + f, err := b.findBindTarget(embeddedType, name) if err != nil { return nil, err } @@ -481,7 +468,7 @@ func (f *Field) ShortResolverDeclaration() string { res := "(ctx context.Context" if !f.Object.Root { - res += fmt.Sprintf(", obj *%s", templates.CurrentImports.LookupType(f.Object.Type)) + res += fmt.Sprintf(", obj %s", templates.CurrentImports.LookupType(f.Object.Reference())) } for _, arg := range f.Args { res += fmt.Sprintf(", %s %s", arg.VarName, templates.CurrentImports.LookupType(arg.TypeReference.GO)) diff --git a/codegen/object.go b/codegen/object.go index 08ae09aa423..18cfd29bed6 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -82,7 +82,12 @@ func (b *builder) buildObject(typ *ast.Definition) (*Object, error) { } func (o *Object) Reference() types.Type { - switch o.Type.(type) { + switch v := o.Type.(type) { + case *types.Named: + _, isInterface := v.Underlying().(*types.Interface) + if isInterface { + return o.Type + } case *types.Pointer, *types.Slice, *types.Map: return o.Type } diff --git a/codegen/testserver/generated.go b/codegen/testserver/generated.go index 99b4c328ec8..04d448f1d86 100644 --- a/codegen/testserver/generated.go +++ b/codegen/testserver/generated.go @@ -39,6 +39,7 @@ type Config struct { } type ResolverRoot interface { + BackedByInterface() BackedByInterfaceResolver Errors() ErrorsResolver ForcedResolver() ForcedResolverResolver ModelMethods() ModelMethodsResolver @@ -89,6 +90,12 @@ type ComplexityRoot struct { ID func(childComplexity int) int } + BackedByInterface struct { + ID func(childComplexity int) int + ThisShouldBind func(childComplexity int) int + ThisShouldBindWithError func(childComplexity int) int + } + Cat struct { CatBreed func(childComplexity int) int Species func(childComplexity int) int @@ -103,6 +110,12 @@ type ComplexityRoot struct { Radius func(childComplexity int) int } + ConcreteNodeA struct { + Child func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + } + ContentPost struct { Foo func(childComplexity int) int } @@ -262,6 +275,8 @@ type ComplexityRoot struct { NestedOutputs func(childComplexity int) int NoShape func(childComplexity int) int NoShapeTypedNil func(childComplexity int) int + Node func(childComplexity int) int + NotAnInterface func(childComplexity int) int NullableArg func(childComplexity int, arg *int) int OptionalUnion func(childComplexity int) int Overlapping func(childComplexity int) int @@ -338,6 +353,9 @@ type ComplexityRoot struct { } } +type BackedByInterfaceResolver interface { + ID(ctx context.Context, obj BackedByInterface) (string, error) +} type ErrorsResolver interface { A(ctx context.Context, obj *Errors) (*Error, error) B(ctx context.Context, obj *Errors) (*Error, error) @@ -399,8 +417,10 @@ type QueryResolver interface { EnumInInput(ctx context.Context, input *InputWithEnumValue) (EnumTest, error) Shapes(ctx context.Context) ([]Shape, error) NoShape(ctx context.Context) (Shape, error) + Node(ctx context.Context) (Node, error) NoShapeTypedNil(ctx context.Context) (Shape, error) Animal(ctx context.Context) (Animal, error) + NotAnInterface(ctx context.Context) (BackedByInterface, error) Issue896a(ctx context.Context) ([]*CheckIssue896, error) MapStringInterface(ctx context.Context, in map[string]interface{}) (map[string]interface{}, error) MapNestedStringInterface(ctx context.Context, in *NestedMapInput) (map[string]interface{}, error) @@ -510,6 +530,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.B.ID(childComplexity), true + case "BackedByInterface.id": + if e.complexity.BackedByInterface.ID == nil { + break + } + + return e.complexity.BackedByInterface.ID(childComplexity), true + + case "BackedByInterface.thisShouldBind": + if e.complexity.BackedByInterface.ThisShouldBind == nil { + break + } + + return e.complexity.BackedByInterface.ThisShouldBind(childComplexity), true + + case "BackedByInterface.thisShouldBindWithError": + if e.complexity.BackedByInterface.ThisShouldBindWithError == nil { + break + } + + return e.complexity.BackedByInterface.ThisShouldBindWithError(childComplexity), true + case "Cat.catBreed": if e.complexity.Cat.CatBreed == nil { break @@ -545,6 +586,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Circle.Radius(childComplexity), true + case "ConcreteNodeA.child": + if e.complexity.ConcreteNodeA.Child == nil { + break + } + + return e.complexity.ConcreteNodeA.Child(childComplexity), true + + case "ConcreteNodeA.id": + if e.complexity.ConcreteNodeA.ID == nil { + break + } + + return e.complexity.ConcreteNodeA.ID(childComplexity), true + + case "ConcreteNodeA.name": + if e.complexity.ConcreteNodeA.Name == nil { + break + } + + return e.complexity.ConcreteNodeA.Name(childComplexity), true + case "Content_Post.foo": if e.complexity.ContentPost.Foo == nil { break @@ -1185,6 +1247,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.NoShapeTypedNil(childComplexity), true + case "Query.node": + if e.complexity.Query.Node == nil { + break + } + + return e.complexity.Query.Node(childComplexity), true + + case "Query.notAnInterface": + if e.complexity.Query.NotAnInterface == nil { + break + } + + return e.complexity.Query.NotAnInterface(childComplexity), true + case "Query.nullableArg": if e.complexity.Query.NullableArg == nil { break @@ -1622,6 +1698,11 @@ type Autobind { type B { id: ID! } +type BackedByInterface { + id: String! + thisShouldBind: String! + thisShouldBindWithError: String! +} scalar Bytes type Cat implements Animal { species: String! @@ -1638,6 +1719,11 @@ type Circle implements Shape { radius: Float area: Float } +type ConcreteNodeA implements Node { + id: ID! + child: Node! + name: String! +} union Content_Child = Content_User | Content_Post type Content_Post { foo: String @@ -1749,6 +1835,10 @@ type ModelMethods { input NestedMapInput { map: MapStringInterfaceInput } +interface Node { + id: ID! + child: Node! +} type ObjectDirectives { text: String! @length(min: 0, max: 7, message: "not valid") nullableText: String @toNull @@ -1815,8 +1905,10 @@ type Query { enumInInput(input: InputWithEnumValue): EnumTest! shapes: [Shape] noShape: Shape @makeNil + node: Node! noShapeTypedNil: Shape @makeTypedNil animal: Animal @makeTypedNil + notAnInterface: BackedByInterface issue896a: [CheckIssue896!] mapStringInterface(in: MapStringInterfaceInput): MapStringInterfaceType mapNestedStringInterface(in: NestedMapInput): MapStringInterfaceType @@ -3084,6 +3176,99 @@ func (ec *executionContext) _B_id(ctx context.Context, field graphql.CollectedFi return ec.marshalNID2string(ctx, field.Selections, res) } +func (ec *executionContext) _BackedByInterface_id(ctx context.Context, field graphql.CollectedField, obj BackedByInterface) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "BackedByInterface", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.BackedByInterface().ID(rctx, obj) + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _BackedByInterface_thisShouldBind(ctx context.Context, field graphql.CollectedField, obj BackedByInterface) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "BackedByInterface", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ThisShouldBind(), nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _BackedByInterface_thisShouldBindWithError(ctx context.Context, field graphql.CollectedField, obj BackedByInterface) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "BackedByInterface", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ThisShouldBindWithError() + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Cat_species(ctx context.Context, field graphql.CollectedField, obj *Cat) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3230,6 +3415,99 @@ func (ec *executionContext) _Circle_area(ctx context.Context, field graphql.Coll return ec.marshalOFloat2float64(ctx, field.Selections, res) } +func (ec *executionContext) _ConcreteNodeA_id(ctx context.Context, field graphql.CollectedField, obj *ConcreteNodeA) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ConcreteNodeA", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ConcreteNodeA_child(ctx context.Context, field graphql.CollectedField, obj *ConcreteNodeA) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ConcreteNodeA", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Child() + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(Node) + fc.Result = res + return ec.marshalNNode2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐNode(ctx, field.Selections, res) +} + +func (ec *executionContext) _ConcreteNodeA_name(ctx context.Context, field graphql.CollectedField, obj *ConcreteNodeA) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ConcreteNodeA", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Content_Post_foo(ctx context.Context, field graphql.CollectedField, obj *ContentPost) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5860,6 +6138,37 @@ func (ec *executionContext) _Query_noShape(ctx context.Context, field graphql.Co return ec.marshalOShape2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐShape(ctx, field.Selections, res) } +func (ec *executionContext) _Query_node(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Node(rctx) + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(Node) + fc.Result = res + return ec.marshalNNode2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐNode(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_noShapeTypedNil(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5956,6 +6265,34 @@ func (ec *executionContext) _Query_animal(ctx context.Context, field graphql.Col return ec.marshalOAnimal2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐAnimal(ctx, field.Selections, res) } +func (ec *executionContext) _Query_notAnInterface(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().NotAnInterface(rctx) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(BackedByInterface) + fc.Result = res + return ec.marshalOBackedByInterface2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐBackedByInterface(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_issue896a(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -8908,6 +9245,20 @@ func (ec *executionContext) _Content_Child(ctx context.Context, sel ast.Selectio } } +func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj Node) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case *ConcreteNodeA: + if obj == nil { + return graphql.Null + } + return ec._ConcreteNodeA(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + func (ec *executionContext) _Shape(ctx context.Context, sel ast.SelectionSet, obj Shape) graphql.Marshaler { switch obj := (obj).(type) { case nil: @@ -9128,6 +9479,52 @@ func (ec *executionContext) _B(ctx context.Context, sel ast.SelectionSet, obj *B return out } +var backedByInterfaceImplementors = []string{"BackedByInterface"} + +func (ec *executionContext) _BackedByInterface(ctx context.Context, sel ast.SelectionSet, obj BackedByInterface) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, backedByInterfaceImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("BackedByInterface") + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._BackedByInterface_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "thisShouldBind": + out.Values[i] = ec._BackedByInterface_thisShouldBind(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "thisShouldBindWithError": + out.Values[i] = ec._BackedByInterface_thisShouldBindWithError(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var catImplementors = []string{"Cat", "Animal"} func (ec *executionContext) _Cat(ctx context.Context, sel ast.SelectionSet, obj *Cat) graphql.Marshaler { @@ -9210,6 +9607,43 @@ func (ec *executionContext) _Circle(ctx context.Context, sel ast.SelectionSet, o return out } +var concreteNodeAImplementors = []string{"ConcreteNodeA", "Node"} + +func (ec *executionContext) _ConcreteNodeA(ctx context.Context, sel ast.SelectionSet, obj *ConcreteNodeA) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, concreteNodeAImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ConcreteNodeA") + case "id": + out.Values[i] = ec._ConcreteNodeA_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "child": + out.Values[i] = ec._ConcreteNodeA_child(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "name": + out.Values[i] = ec._ConcreteNodeA_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var content_PostImplementors = []string{"Content_Post", "Content_Child"} func (ec *executionContext) _Content_Post(ctx context.Context, sel ast.SelectionSet, obj *ContentPost) graphql.Marshaler { @@ -10498,6 +10932,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_noShape(ctx, field) return res }) + case "node": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_node(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "noShapeTypedNil": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -10520,6 +10968,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_animal(ctx, field) return res }) + case "notAnInterface": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_notAnInterface(ctx, field) + return res + }) case "issue896a": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -11584,6 +12043,16 @@ func (ec *executionContext) marshalNMarshalPanic2ᚕgithubᚗcomᚋ99designsᚋg return ret } +func (ec *executionContext) marshalNNode2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐNode(ctx context.Context, sel ast.SelectionSet, v Node) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._Node(ctx, sel, v) +} + func (ec *executionContext) marshalNPrimitive2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐPrimitive(ctx context.Context, sel ast.SelectionSet, v Primitive) graphql.Marshaler { return ec._Primitive(ctx, sel, &v) } @@ -12122,6 +12591,13 @@ func (ec *executionContext) marshalOAutobind2ᚖgithubᚗcomᚋ99designsᚋgqlge return ec._Autobind(ctx, sel, v) } +func (ec *executionContext) marshalOBackedByInterface2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐBackedByInterface(ctx context.Context, sel ast.SelectionSet, v BackedByInterface) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._BackedByInterface(ctx, sel, v) +} + func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { return graphql.UnmarshalBoolean(v) } diff --git a/codegen/testserver/interfaces.go b/codegen/testserver/interfaces.go index 18488d8153f..41eb7e24008 100644 --- a/codegen/testserver/interfaces.go +++ b/codegen/testserver/interfaces.go @@ -32,3 +32,34 @@ func (r *Rectangle) Area() float64 { } func (r *Rectangle) isShapeUnion() {} func (r *Rectangle) isShape() {} + +type Node interface { + Child() (Node, error) +} + +type ConcreteNodeA struct { + ID string + Name string + child Node +} + +func (n *ConcreteNodeA) Child() (Node, error) { + return n.child, nil +} + +type BackedByInterface interface { + ThisShouldBind() string + ThisShouldBindWithError() (string, error) +} + +type BackedByInterfaceImpl struct { + Value string + Error error +} + +func (b *BackedByInterfaceImpl) ThisShouldBind() string { + return b.Value +} +func (b *BackedByInterfaceImpl) ThisShouldBindWithError() (string, error) { + return b.Value, b.Error +} diff --git a/codegen/testserver/interfaces.graphql b/codegen/testserver/interfaces.graphql index 177e2a096c9..08eb42e8e8c 100644 --- a/codegen/testserver/interfaces.graphql +++ b/codegen/testserver/interfaces.graphql @@ -1,14 +1,22 @@ extend type Query { shapes: [Shape] noShape: Shape @makeNil + node: Node! noShapeTypedNil: Shape @makeTypedNil animal: Animal @makeTypedNil + notAnInterface: BackedByInterface } interface Animal { species: String! } +type BackedByInterface { + id: String! + thisShouldBind: String! + thisShouldBindWithError: String! +} + type Dog implements Animal { species: String! dogBreed: String! @@ -35,3 +43,14 @@ union ShapeUnion @goModel(model:"testserver.ShapeUnion") = Circle | Rectangle directive @makeNil on FIELD_DEFINITION directive @makeTypedNil on FIELD_DEFINITION + +interface Node { + id: ID! + child: Node! +} + +type ConcreteNodeA implements Node { + id: ID! + child: Node! + name: String! +} diff --git a/codegen/testserver/interfaces_test.go b/codegen/testserver/interfaces_test.go index 4cb29ec91a6..1e44bbfc5ad 100644 --- a/codegen/testserver/interfaces_test.go +++ b/codegen/testserver/interfaces_test.go @@ -2,6 +2,7 @@ package testserver import ( "context" + "fmt" "reflect" "testing" @@ -18,6 +19,41 @@ func TestInterfaces(t *testing.T) { require.Equal(t, "[]testserver.Shape", field.Type.Out(0).String()) }) + t.Run("models returning interfaces", func(t *testing.T) { + resolvers := &Stub{} + resolvers.QueryResolver.Node = func(ctx context.Context) (node Node, err error) { + return &ConcreteNodeA{ + ID: "1234", + Name: "asdf", + child: &ConcreteNodeA{ + ID: "5678", + Name: "hjkl", + child: nil, + }, + }, nil + } + + srv := handler.NewDefaultServer( + NewExecutableSchema(Config{ + Resolvers: resolvers, + }), + ) + + c := client.New(srv) + + var resp struct { + Node struct { + ID string + Child struct { + ID string + } + } + } + c.MustPost(`{ node { id, child { id } } }`, &resp) + require.Equal(t, "1234", resp.Node.ID) + require.Equal(t, "5678", resp.Node.Child.ID) + }) + t.Run("interfaces can be nil", func(t *testing.T) { resolvers := &Stub{} resolvers.QueryResolver.NoShape = func(ctx context.Context) (shapes Shape, e error) { @@ -88,4 +124,56 @@ func TestInterfaces(t *testing.T) { var resp interface{} c.MustPost(`{ animal { species } }`, &resp) }) + + t.Run("can bind to interfaces even when the graphql is not", func(t *testing.T) { + resolvers := &Stub{} + resolvers.BackedByInterfaceResolver.ID = func(ctx context.Context, obj BackedByInterface) (s string, err error) { + return "ID:" + obj.ThisShouldBind(), nil + } + resolvers.QueryResolver.NotAnInterface = func(ctx context.Context) (byInterface BackedByInterface, err error) { + return &BackedByInterfaceImpl{ + Value: "A", + Error: nil, + }, nil + } + + c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) + + var resp struct { + NotAnInterface struct { + ID string + ThisShouldBind string + ThisShouldBindWithError string + } + } + c.MustPost(`{ notAnInterface { id, thisShouldBind, thisShouldBindWithError } }`, &resp) + require.Equal(t, "ID:A", resp.NotAnInterface.ID) + require.Equal(t, "A", resp.NotAnInterface.ThisShouldBind) + require.Equal(t, "A", resp.NotAnInterface.ThisShouldBindWithError) + }) + + t.Run("can return errors from interface funcs", func(t *testing.T) { + resolvers := &Stub{} + resolvers.BackedByInterfaceResolver.ID = func(ctx context.Context, obj BackedByInterface) (s string, err error) { + return "ID:" + obj.ThisShouldBind(), nil + } + resolvers.QueryResolver.NotAnInterface = func(ctx context.Context) (byInterface BackedByInterface, err error) { + return &BackedByInterfaceImpl{ + Value: "A", + Error: fmt.Errorf("boom"), + }, nil + } + + c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) + + var resp struct { + NotAnInterface struct { + ID string + ThisShouldBind string + ThisShouldBindWithError string + } + } + err := c.Post(`{ notAnInterface { id, thisShouldBind, thisShouldBindWithError } }`, &resp) + require.EqualError(t, err, `[{"message":"boom","path":["notAnInterface","thisShouldBindWithError"]}]`) + }) } diff --git a/codegen/testserver/resolver.go b/codegen/testserver/resolver.go index f8092248102..e74442fdbc3 100644 --- a/codegen/testserver/resolver.go +++ b/codegen/testserver/resolver.go @@ -10,6 +10,10 @@ import ( type Resolver struct{} +func (r *backedByInterfaceResolver) ID(ctx context.Context, obj BackedByInterface) (string, error) { + panic("not implemented") +} + func (r *errorsResolver) A(ctx context.Context, obj *Errors) (*Error, error) { panic("not implemented") } @@ -186,6 +190,10 @@ func (r *queryResolver) NoShape(ctx context.Context) (Shape, error) { panic("not implemented") } +func (r *queryResolver) Node(ctx context.Context) (Node, error) { + panic("not implemented") +} + func (r *queryResolver) NoShapeTypedNil(ctx context.Context) (Shape, error) { panic("not implemented") } @@ -194,6 +202,10 @@ func (r *queryResolver) Animal(ctx context.Context) (Animal, error) { panic("not implemented") } +func (r *queryResolver) NotAnInterface(ctx context.Context) (BackedByInterface, error) { + panic("not implemented") +} + func (r *queryResolver) Issue896a(ctx context.Context) ([]*CheckIssue896, error) { panic("not implemented") } @@ -294,6 +306,7 @@ func (r *userResolver) Friends(ctx context.Context, obj *User) ([]*User, error) panic("not implemented") } +func (r *Resolver) BackedByInterface() BackedByInterfaceResolver { return &backedByInterfaceResolver{r} } func (r *Resolver) Errors() ErrorsResolver { return &errorsResolver{r} } func (r *Resolver) ForcedResolver() ForcedResolverResolver { return &forcedResolverResolver{r} } func (r *Resolver) ModelMethods() ModelMethodsResolver { return &modelMethodsResolver{r} } @@ -305,6 +318,7 @@ func (r *Resolver) Query() QueryResolver { return &query func (r *Resolver) Subscription() SubscriptionResolver { return &subscriptionResolver{r} } func (r *Resolver) User() UserResolver { return &userResolver{r} } +type backedByInterfaceResolver struct{ *Resolver } type errorsResolver struct{ *Resolver } type forcedResolverResolver struct{ *Resolver } type modelMethodsResolver struct{ *Resolver } diff --git a/codegen/testserver/stub.go b/codegen/testserver/stub.go index eeeaeb20974..6e4608287f9 100644 --- a/codegen/testserver/stub.go +++ b/codegen/testserver/stub.go @@ -10,6 +10,9 @@ import ( ) type Stub struct { + BackedByInterfaceResolver struct { + ID func(ctx context.Context, obj BackedByInterface) (string, error) + } ErrorsResolver struct { A func(ctx context.Context, obj *Errors) (*Error, error) B func(ctx context.Context, obj *Errors) (*Error, error) @@ -69,8 +72,10 @@ type Stub struct { EnumInInput func(ctx context.Context, input *InputWithEnumValue) (EnumTest, error) Shapes func(ctx context.Context) ([]Shape, error) NoShape func(ctx context.Context) (Shape, error) + Node func(ctx context.Context) (Node, error) NoShapeTypedNil func(ctx context.Context) (Shape, error) Animal func(ctx context.Context) (Animal, error) + NotAnInterface func(ctx context.Context) (BackedByInterface, error) Issue896a func(ctx context.Context) ([]*CheckIssue896, error) MapStringInterface func(ctx context.Context, in map[string]interface{}) (map[string]interface{}, error) MapNestedStringInterface func(ctx context.Context, in *NestedMapInput) (map[string]interface{}, error) @@ -103,6 +108,9 @@ type Stub struct { } } +func (r *Stub) BackedByInterface() BackedByInterfaceResolver { + return &stubBackedByInterface{r} +} func (r *Stub) Errors() ErrorsResolver { return &stubErrors{r} } @@ -134,6 +142,12 @@ func (r *Stub) User() UserResolver { return &stubUser{r} } +type stubBackedByInterface struct{ *Stub } + +func (r *stubBackedByInterface) ID(ctx context.Context, obj BackedByInterface) (string, error) { + return r.BackedByInterfaceResolver.ID(ctx, obj) +} + type stubErrors struct{ *Stub } func (r *stubErrors) A(ctx context.Context, obj *Errors) (*Error, error) { @@ -289,12 +303,18 @@ func (r *stubQuery) Shapes(ctx context.Context) ([]Shape, error) { func (r *stubQuery) NoShape(ctx context.Context) (Shape, error) { return r.QueryResolver.NoShape(ctx) } +func (r *stubQuery) Node(ctx context.Context) (Node, error) { + return r.QueryResolver.Node(ctx) +} func (r *stubQuery) NoShapeTypedNil(ctx context.Context) (Shape, error) { return r.QueryResolver.NoShapeTypedNil(ctx) } func (r *stubQuery) Animal(ctx context.Context) (Animal, error) { return r.QueryResolver.Animal(ctx) } +func (r *stubQuery) NotAnInterface(ctx context.Context) (BackedByInterface, error) { + return r.QueryResolver.NotAnInterface(ctx) +} func (r *stubQuery) Issue896a(ctx context.Context) ([]*CheckIssue896, error) { return r.QueryResolver.Issue896a(ctx) }