diff --git a/codegen/build.go b/codegen/build.go index c4cfcb4655..d24149a954 100644 --- a/codegen/build.go +++ b/codegen/build.go @@ -41,7 +41,7 @@ func Models(schema *schema.Schema, userTypes map[string]string, destDir string) bindTypes(imports, namedTypes, prog) - models := buildModels(namedTypes, schema) + models := buildModels(namedTypes, schema, prog) return &ModelBuild{ PackageName: filepath.Base(destDir), Models: models, @@ -67,7 +67,7 @@ func Bind(schema *schema.Schema, userTypes map[string]string, destDir string) (* b := &Build{ PackageName: filepath.Base(destDir), Objects: objects, - Interfaces: buildInterfaces(namedTypes, schema), + Interfaces: buildInterfaces(namedTypes, schema, prog), Inputs: inputs, Imports: imports, } diff --git a/codegen/interface.go b/codegen/interface.go index 98c9bc1775..2de0c88a9b 100644 --- a/codegen/interface.go +++ b/codegen/interface.go @@ -3,5 +3,11 @@ package codegen type Interface struct { *NamedType - Implementors []*NamedType + Implementors []InterfaceImplementor +} + +type InterfaceImplementor struct { + ValueReceiver bool + + *NamedType } diff --git a/codegen/interface_build.go b/codegen/interface_build.go index b45d6ba7d1..672a9e82bf 100644 --- a/codegen/interface_build.go +++ b/codegen/interface_build.go @@ -2,18 +2,21 @@ package codegen import ( "fmt" + "go/types" + "os" "sort" "strings" "github.com/vektah/gqlgen/neelance/schema" + "golang.org/x/tools/go/loader" ) -func buildInterfaces(types NamedTypes, s *schema.Schema) []*Interface { +func buildInterfaces(types NamedTypes, s *schema.Schema, prog *loader.Program) []*Interface { var interfaces []*Interface for _, typ := range s.Types { switch typ := typ.(type) { case *schema.Union, *schema.Interface: - interfaces = append(interfaces, buildInterface(types, typ)) + interfaces = append(interfaces, buildInterface(types, typ, prog)) default: continue } @@ -26,14 +29,15 @@ func buildInterfaces(types NamedTypes, s *schema.Schema) []*Interface { return interfaces } -func buildInterface(types NamedTypes, typ schema.NamedType) *Interface { +func buildInterface(types NamedTypes, typ schema.NamedType, prog *loader.Program) *Interface { switch typ := typ.(type) { case *schema.Union: i := &Interface{NamedType: types[typ.TypeName()]} for _, implementor := range typ.PossibleTypes { - i.Implementors = append(i.Implementors, types[implementor.TypeName()]) + t := types[implementor.TypeName()] + i.Implementors = append(i.Implementors, InterfaceImplementor{NamedType: t, ValueReceiver: true}) } return i @@ -42,7 +46,12 @@ func buildInterface(types NamedTypes, typ schema.NamedType) *Interface { i := &Interface{NamedType: types[typ.TypeName()]} for _, implementor := range typ.PossibleTypes { - i.Implementors = append(i.Implementors, types[implementor.TypeName()]) + t := types[implementor.TypeName()] + + i.Implementors = append(i.Implementors, InterfaceImplementor{ + NamedType: t, + ValueReceiver: isValueReceiver(types[typ.Name], t, prog), + }) } return i @@ -50,3 +59,29 @@ func buildInterface(types NamedTypes, typ schema.NamedType) *Interface { panic(fmt.Errorf("unknown interface %#v", typ)) } } + +func isValueReceiver(intf *NamedType, implementor *NamedType, prog *loader.Program) bool { + interfaceType := findGoInterface(prog, intf.Package, intf.GoType) + implementorType := findGoNamedType(prog, implementor.Package, implementor.GoType) + + if interfaceType == nil || implementorType == nil { + return true + } + + for i := 0; i < interfaceType.NumMethods(); i++ { + intfMethod := interfaceType.Method(i) + + implMethod := findMethod(implementorType, intfMethod.Name()) + if implMethod == nil { + fmt.Fprintf(os.Stderr, "missing method %s on %s\n", intfMethod.Name(), implementor.GoType) + return false + } + + sig := implMethod.Type().(*types.Signature) + if _, isPtr := sig.Recv().Type().(*types.Pointer); isPtr { + return false + } + } + + return true +} diff --git a/codegen/models_build.go b/codegen/models_build.go index 139020372f..317a4e7360 100644 --- a/codegen/models_build.go +++ b/codegen/models_build.go @@ -5,9 +5,10 @@ import ( "strings" "github.com/vektah/gqlgen/neelance/schema" + "golang.org/x/tools/go/loader" ) -func buildModels(types NamedTypes, s *schema.Schema) []Model { +func buildModels(types NamedTypes, s *schema.Schema, prog *loader.Program) []Model { var models []Model for _, typ := range s.Types { @@ -26,7 +27,7 @@ func buildModels(types NamedTypes, s *schema.Schema) []Model { } model = obj2Model(obj) case *schema.Interface, *schema.Union: - intf := buildInterface(types, typ) + intf := buildInterface(types, typ, prog) if intf.GoType != "" { continue } diff --git a/codegen/templates/data.go b/codegen/templates/data.go index dafbe2855e..3041b73a62 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -5,7 +5,7 @@ var data = map[string]string{ "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(field graphql.CollectedField) func() graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\t\tresults, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\tif err != nil {\n\t\t\tec.Error(err)\n\t\t\treturn nil\n\t\t}\n\t\treturn func() graphql.Marshaler {\n\t\t\tres, ok := <-results\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvar out graphql.OrderedMap\n\t\t\tout.Add(field.Alias, func() graphql.Marshaler { {{ $field.WriteJson }} }())\n\t\t\treturn &out\n\t\t}\n\t}\n{{ else }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(field graphql.CollectedField, {{if not $object.Root}}obj *{{$object.FullName}}{{end}}) graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\n\t\t{{- if $field.IsConcurrent }}\n\t\t\treturn graphql.Defer(func() (ret graphql.Marshaler) {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\tuserErr := ec.recover(r)\n\t\t\t\t\t\tec.Error(userErr)\n\t\t\t\t\t\tret = graphql.Null\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t{{- end }}\n\n\t\t\t{{- if $field.GoVarName }}\n\t\t\t\tres := obj.{{$field.GoVarName}}\n\t\t\t{{- else if $field.GoMethodName }}\n\t\t\t\t{{- if $field.NoErr }}\n\t\t\t\t\tres := {{$field.GoMethodName}}({{ $field.CallArgs }})\n\t\t\t\t{{- else }}\n\t\t\t\t\tres, err := {{$field.GoMethodName}}({{ $field.CallArgs }})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tec.Error(err)\n\t\t\t\t\t\treturn graphql.Null\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t{{- else }}\n\t\t\t\tres, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\t\t\tif err != nil {\n\t\t\t\t\tec.Error(err)\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t{{ $field.WriteJson }}\n\t\t{{- if $field.IsConcurrent }}\n\t\t\t})\n\t\t{{- end }}\n\t}\n{{ end }}\n", "generated.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\nfunc MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema {\n\treturn &executableSchema{resolvers}\n}\n\ntype Resolvers interface {\n{{- range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{ $field.ResolverDeclaration }}\n\t{{ end }}\n{{- end }}\n}\n\ntype executableSchema struct {\n\tresolvers Resolvers\n}\n\nfunc (e *executableSchema) Schema() *schema.Schema {\n\treturn parsedSchema\n}\n\nfunc (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover}\n\n\t\tdata := ec._{{.QueryRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"queries are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover}\n\n\t\tdata := ec._{{.MutationRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"mutations are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(op.Selections)\n\t\tif ec.Errors != nil {\n\t\t\treturn graphql.OneShot(&graphql.Response{Data: []byte(\"null\"), Errors: ec.Errors})\n\t\t}\n\n\t\tvar buf bytes.Buffer\n\t\treturn func() *graphql.Response {\n\t\t\tbuf.Reset()\n\t\t\tdata := next()\n\t\t\tif data == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdata.MarshalGQL(&buf)\n\n\t\t\terrs := ec.Errors\n\t\t\tec.Errors = nil\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf.Bytes(),\n\t\t\t\tErrors: errs,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{ {Message: \"subscriptions are not supported\"} }})\n\t{{- end }}\n}\n\ntype executionContext struct {\n\terrors.Builder\n\tresolvers Resolvers\n\tvariables map[string]interface{}\n\tdoc *query.Document\n\tctx context.Context\n\trecover graphql.RecoverFunc\n}\n\n{{- range $object := .Objects }}\n\t{{ template \"object.gotpl\" $object }}\n\n\t{{- range $field := $object.Fields }}\n\t\t{{ template \"field.gotpl\" $field }}\n\t{{ end }}\n{{- end}}\n\n{{- range $interface := .Interfaces }}\n\t{{ template \"interface.gotpl\" $interface }}\n{{- end }}\n\n{{- range $input := .Inputs }}\n\t{{ template \"input.gotpl\" $input }}\n{{- end }}\n\nvar parsedSchema = schema.MustParse({{.SchemaRaw|quote}})\n\nfunc (ec *executionContext) introspectSchema() *introspection.Schema {\n\treturn introspection.WrapSchema(parsedSchema)\n}\n\nfunc (ec *executionContext) introspectType(name string) *introspection.Type {\n\tt := parsedSchema.Resolve(name)\n\tif t == nil {\n\t\treturn nil\n\t}\n\treturn introspection.WrapType(t)\n}\n", "input.gotpl": "\t{{- if .IsMarshaled }}\n\tfunc Unmarshal{{ .GQLType }}(v interface{}) ({{.FullName}}, error) {\n\t\tvar it {{.FullName}}\n\n\t\tfor k, v := range v.(map[string]interface{}) {\n\t\t\tswitch k {\n\t\t\t{{- range $field := .Fields }}\n\t\t\tcase {{$field.GQLName|quote}}:\n\t\t\t\tvar err error\n\t\t\t\t{{ $field.Unmarshal (print \"it.\" $field.GoVarName) \"v\" }}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn it, err\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\n\t\treturn it, nil\n\t}\n\t{{- end }}\n", - "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.GQLType}}(sel []query.Selection, obj *{{$interface.FullName}}) graphql.Marshaler {\n\tswitch obj := (*obj).(type) {\n\tcase nil:\n\t\treturn graphql.Null\n\t{{- range $implementor := $interface.Implementors }}\n\tcase {{$implementor.FullName}}:\n\t\treturn ec._{{$implementor.GQLType}}(sel, &obj)\n\n\tcase *{{$implementor.FullName}}:\n\t\treturn ec._{{$implementor.GQLType}}(sel, obj)\n\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", + "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.GQLType}}(sel []query.Selection, obj *{{$interface.FullName}}) graphql.Marshaler {\n\tswitch obj := (*obj).(type) {\n\tcase nil:\n\t\treturn graphql.Null\n\t{{- range $implementor := $interface.Implementors }}\n\t\t{{- if $implementor.ValueReceiver }}\n\t\t\tcase {{$implementor.FullName}}:\n\t\t\t\treturn ec._{{$implementor.GQLType}}(sel, &obj)\n\t\t{{- end}}\n\t\tcase *{{$implementor.FullName}}:\n\t\t\treturn ec._{{$implementor.GQLType}}(sel, obj)\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", "models.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\n{{ range $model := .Models }}\n\t{{- if .IsInterface }}\n\t\ttype {{.GoType}} interface {}\n\t{{- else }}\n\t\ttype {{.GoType}} struct {\n\t\t\t{{- range $field := .Fields }}\n\t\t\t\t{{- if $field.GoVarName }}\n\t\t\t\t\t{{ $field.GoVarName }} {{$field.Signature}}\n\t\t\t\t{{- else }}\n\t\t\t\t\t{{ $field.GoFKName }} {{$field.GoFKType}}\n\t\t\t\t{{- end }}\n\t\t\t{{- end }}\n\t\t}\n\t{{- end }}\n{{- end}}\n", "object.gotpl": "{{ $object := . }}\n\nvar {{ $object.GQLType|lcFirst}}Implementors = {{$object.Implementors}}\n\n// nolint: gocyclo, errcheck, gas, goconst\n{{- if .Stream }}\nfunc (ec *executionContext) _{{$object.GQLType}}(sel []query.Selection) func() graphql.Marshaler {\n\tfields := graphql.CollectFields(ec.doc, sel, {{$object.GQLType|lcFirst}}Implementors, ec.variables)\n\n\tif len(fields) != 1 {\n\t\tec.Errorf(\"must subscribe to exactly one stream\")\n\t\treturn nil\n\t}\n\n\tswitch fields[0].Name {\n\t{{- range $field := $object.Fields }}\n\tcase \"{{$field.GQLName}}\":\n\t\treturn ec._{{$object.GQLType}}_{{$field.GQLName}}(fields[0])\n\t{{- end }}\n\tdefault:\n\t\tpanic(\"unknown field \" + strconv.Quote(fields[0].Name))\n\t}\n}\n{{- else }}\nfunc (ec *executionContext) _{{$object.GQLType}}(sel []query.Selection{{if not $object.Root}}, obj *{{$object.FullName}} {{end}}) graphql.Marshaler {\n\tfields := graphql.CollectFields(ec.doc, sel, {{$object.GQLType|lcFirst}}Implementors, ec.variables)\n\tout := graphql.NewOrderedMap(len(fields))\n\tfor i, field := range fields {\n\t\tout.Keys[i] = field.Alias\n\n\t\tswitch field.Name {\n\t\tcase \"__typename\":\n\t\t\tout.Values[i] = graphql.MarshalString({{$object.GQLType|quote}})\n\t\t{{- range $field := $object.Fields }}\n\t\tcase \"{{$field.GQLName}}\":\n\t\t\tout.Values[i] = ec._{{$object.GQLType}}_{{$field.GQLName}}(field{{if not $object.Root}}, obj{{end}})\n\t\t{{- end }}\n\t\tdefault:\n\t\t\tpanic(\"unknown field \" + strconv.Quote(field.Name))\n\t\t}\n\t}\n\n\treturn out\n}\n{{- end }}\n", } diff --git a/codegen/templates/interface.gotpl b/codegen/templates/interface.gotpl index 58cd1d3a19..0392a98b69 100644 --- a/codegen/templates/interface.gotpl +++ b/codegen/templates/interface.gotpl @@ -5,12 +5,12 @@ func (ec *executionContext) _{{$interface.GQLType}}(sel []query.Selection, obj * case nil: return graphql.Null {{- range $implementor := $interface.Implementors }} - case {{$implementor.FullName}}: - return ec._{{$implementor.GQLType}}(sel, &obj) - - case *{{$implementor.FullName}}: - return ec._{{$implementor.GQLType}}(sel, obj) - + {{- if $implementor.ValueReceiver }} + case {{$implementor.FullName}}: + return ec._{{$implementor.GQLType}}(sel, &obj) + {{- end}} + case *{{$implementor.FullName}}: + return ec._{{$implementor.GQLType}}(sel, obj) {{- end }} default: panic(fmt.Errorf("unexpected type %T", obj)) diff --git a/codegen/util.go b/codegen/util.go index 81bb069ded..5f1b732831 100644 --- a/codegen/util.go +++ b/codegen/util.go @@ -38,6 +38,39 @@ func findGoType(prog *loader.Program, pkgName string, typeName string) (types.Ob return nil, fmt.Errorf("unable to find type %s\n", fullName) } +func findGoNamedType(prog *loader.Program, pkgName string, typeName string) *types.Named { + def, err := findGoType(prog, pkgName, typeName) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } + if def == nil { + return nil + } + + namedType, ok := def.Type().(*types.Named) + if !ok { + fmt.Fprintf(os.Stderr, "expected %s to be a named type, instead found %T\n", typeName, def.Type()) + return nil + } + + return namedType +} + +func findGoInterface(prog *loader.Program, pkgName string, typeName string) *types.Interface { + namedType := findGoNamedType(prog, pkgName, typeName) + if namedType == nil { + return nil + } + + underlying, ok := namedType.Underlying().(*types.Interface) + if !ok { + fmt.Fprintf(os.Stderr, "expected %s to be a named interface, instead found %s", typeName, namedType.String()) + return nil + } + + return underlying +} + func findMethod(typ *types.Named, name string) *types.Func { for i := 0; i < typ.NumMethods(); i++ { method := typ.Method(i) diff --git a/example/starwars/generated.go b/example/starwars/generated.go index f88d61e518..4e3d389ea2 100644 --- a/example/starwars/generated.go +++ b/example/starwars/generated.go @@ -1512,12 +1512,10 @@ func (ec *executionContext) _Character(sel []query.Selection, obj *Character) gr return graphql.Null case Human: return ec._Human(sel, &obj) - case *Human: return ec._Human(sel, obj) case Droid: return ec._Droid(sel, &obj) - case *Droid: return ec._Droid(sel, obj) default: @@ -1531,17 +1529,14 @@ func (ec *executionContext) _SearchResult(sel []query.Selection, obj *SearchResu return graphql.Null case Human: return ec._Human(sel, &obj) - case *Human: return ec._Human(sel, obj) case Droid: return ec._Droid(sel, &obj) - case *Droid: return ec._Droid(sel, obj) case Starship: return ec._Starship(sel, &obj) - case *Starship: return ec._Starship(sel, obj) default: diff --git a/test/generated.go b/test/generated.go index 5922ce6cd6..1f61592891 100644 --- a/test/generated.go +++ b/test/generated.go @@ -5,6 +5,7 @@ package test import ( "bytes" context "context" + fmt "fmt" strconv "strconv" graphql "github.com/vektah/gqlgen/graphql" @@ -22,6 +23,7 @@ type Resolvers interface { OuterObject_inner(ctx context.Context, obj *OuterObject) (InnerObject, error) Query_nestedInputs(ctx context.Context, input [][]OuterInput) (*bool, error) Query_nestedOutputs(ctx context.Context) ([][]OuterObject, error) + Query_shapes(ctx context.Context) ([]Shape, error) } type executableSchema struct { @@ -62,6 +64,40 @@ type executionContext struct { recover graphql.RecoverFunc } +var circleImplementors = []string{"Circle", "Shape"} + +// nolint: gocyclo, errcheck, gas, goconst +func (ec *executionContext) _Circle(sel []query.Selection, obj *Circle) graphql.Marshaler { + fields := graphql.CollectFields(ec.doc, sel, circleImplementors, ec.variables) + out := graphql.NewOrderedMap(len(fields)) + for i, field := range fields { + out.Keys[i] = field.Alias + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Circle") + case "radius": + out.Values[i] = ec._Circle_radius(field, obj) + case "area": + out.Values[i] = ec._Circle_area(field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + + return out +} + +func (ec *executionContext) _Circle_radius(field graphql.CollectedField, obj *Circle) graphql.Marshaler { + res := obj.Radius + return graphql.MarshalFloat(res) +} + +func (ec *executionContext) _Circle_area(field graphql.CollectedField, obj *Circle) graphql.Marshaler { + res := obj.Area() + return graphql.MarshalFloat(res) +} + var innerObjectImplementors = []string{"InnerObject"} // nolint: gocyclo, errcheck, gas, goconst @@ -145,6 +181,8 @@ func (ec *executionContext) _Query(sel []query.Selection) graphql.Marshaler { out.Values[i] = ec._Query_nestedInputs(field) case "nestedOutputs": out.Values[i] = ec._Query_nestedOutputs(field) + case "shapes": + out.Values[i] = ec._Query_shapes(field) case "__schema": out.Values[i] = ec._Query___schema(field) case "__type": @@ -242,6 +280,28 @@ func (ec *executionContext) _Query_nestedOutputs(field graphql.CollectedField) g }) } +func (ec *executionContext) _Query_shapes(field graphql.CollectedField) graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() + res, err := ec.resolvers.Query_shapes(ec.ctx) + if err != nil { + ec.Error(err) + return graphql.Null + } + arr1 := graphql.Array{} + for idx1 := range res { + arr1 = append(arr1, func() graphql.Marshaler { return ec._Shape(field.Selections, &res[idx1]) }()) + } + return arr1 + }) +} + func (ec *executionContext) _Query___schema(field graphql.CollectedField) graphql.Marshaler { res := ec.introspectSchema() if res == nil { @@ -268,6 +328,47 @@ func (ec *executionContext) _Query___type(field graphql.CollectedField) graphql. return ec.___Type(field.Selections, res) } +var rectangleImplementors = []string{"Rectangle", "Shape"} + +// nolint: gocyclo, errcheck, gas, goconst +func (ec *executionContext) _Rectangle(sel []query.Selection, obj *Rectangle) graphql.Marshaler { + fields := graphql.CollectFields(ec.doc, sel, rectangleImplementors, ec.variables) + out := graphql.NewOrderedMap(len(fields)) + for i, field := range fields { + out.Keys[i] = field.Alias + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Rectangle") + case "length": + out.Values[i] = ec._Rectangle_length(field, obj) + case "width": + out.Values[i] = ec._Rectangle_width(field, obj) + case "area": + out.Values[i] = ec._Rectangle_area(field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + + return out +} + +func (ec *executionContext) _Rectangle_length(field graphql.CollectedField, obj *Rectangle) graphql.Marshaler { + res := obj.Length + return graphql.MarshalFloat(res) +} + +func (ec *executionContext) _Rectangle_width(field graphql.CollectedField, obj *Rectangle) graphql.Marshaler { + res := obj.Width + return graphql.MarshalFloat(res) +} + +func (ec *executionContext) _Rectangle_area(field graphql.CollectedField, obj *Rectangle) graphql.Marshaler { + res := obj.Area() + return graphql.MarshalFloat(res) +} + var __DirectiveImplementors = []string{"__Directive"} // nolint: gocyclo, errcheck, gas, goconst @@ -762,6 +863,19 @@ func (ec *executionContext) ___Type_ofType(field graphql.CollectedField, obj *in return ec.___Type(field.Selections, res) } +func (ec *executionContext) _Shape(sel []query.Selection, obj *Shape) graphql.Marshaler { + switch obj := (*obj).(type) { + case nil: + return graphql.Null + case *Circle: + return ec._Circle(sel, obj) + case *Rectangle: + return ec._Rectangle(sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + func UnmarshalInnerInput(v interface{}) (InnerInput, error) { var it InnerInput @@ -798,7 +912,7 @@ func UnmarshalOuterInput(v interface{}) (OuterInput, error) { return it, nil } -var parsedSchema = schema.MustParse("input InnerInput {\n id:Int!\n}\n\ninput OuterInput {\n inner: InnerInput!\n}\n\ntype OuterObject {\n inner: InnerObject!\n}\n\ntype InnerObject {\n id: Int!\n}\n\ntype Query {\n nestedInputs(input: [[OuterInput]] = [[{inner: {id: 1}}]]): Boolean\n nestedOutputs: [[OuterObject]]\n}\n") +var parsedSchema = schema.MustParse("input InnerInput {\n id:Int!\n}\n\ninput OuterInput {\n inner: InnerInput!\n}\n\ntype OuterObject {\n inner: InnerObject!\n}\n\ntype InnerObject {\n id: Int!\n}\n\ninterface Shape {\n area: Float\n}\n\ntype Circle implements Shape {\n radius: Float\n area: Float\n}\n\ntype Rectangle implements Shape {\n length: Float\n width: Float\n area: Float\n}\n\ntype Query {\n nestedInputs(input: [[OuterInput]] = [[{inner: {id: 1}}]]): Boolean\n nestedOutputs: [[OuterObject]]\n shapes: [Shape]\n}\n") func (ec *executionContext) introspectSchema() *introspection.Schema { return introspection.WrapSchema(parsedSchema) diff --git a/test/models.go b/test/models.go new file mode 100644 index 0000000000..9777fa5e0a --- /dev/null +++ b/test/models.go @@ -0,0 +1,23 @@ +package test + +import "math" + +type Shape interface { + Area() float64 +} + +type Circle struct { + Radius float64 +} + +func (c *Circle) Area() float64 { + return c.Radius * math.Pi * math.Pi +} + +type Rectangle struct { + Length, Width float64 +} + +func (r *Rectangle) Area() float64 { + return r.Length * r.Width +} diff --git a/test/resolvers_test.go b/test/resolvers_test.go index 54b5f7588c..a79eb96fb8 100644 --- a/test/resolvers_test.go +++ b/test/resolvers_test.go @@ -1,4 +1,4 @@ -//go:generate gorunpkg github.com/vektah/gqlgen -out generated.go +//go:generate gorunpkg github.com/vektah/gqlgen -out generated.go -typemap types.json package test diff --git a/test/schema.graphql b/test/schema.graphql index 143a76cc01..2e8a4d0f36 100644 --- a/test/schema.graphql +++ b/test/schema.graphql @@ -14,7 +14,23 @@ type InnerObject { id: Int! } +interface Shape { + area: Float +} + +type Circle implements Shape { + radius: Float + area: Float +} + +type Rectangle implements Shape { + length: Float + width: Float + area: Float +} + type Query { nestedInputs(input: [[OuterInput]] = [[{inner: {id: 1}}]]): Boolean nestedOutputs: [[OuterObject]] + shapes: [Shape] } diff --git a/test/types.json b/test/types.json new file mode 100644 index 0000000000..251fa29d8b --- /dev/null +++ b/test/types.json @@ -0,0 +1,5 @@ +{ + "Shape": "github.com/vektah/gqlgen/test.Shape", + "Circle": "github.com/vektah/gqlgen/test.Circle", + "Rectangle": "github.com/vektah/gqlgen/test.Rectangle" +}