diff --git a/codegen/object.go b/codegen/object.go index 06291517817..266f3475368 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -50,6 +50,15 @@ func (o *Object) Implementors() string { return "[]string{" + satisfiedBy + "}" } +func (o *Object) HasResolvers() bool { + for _, f := range o.Fields { + if f.IsResolver() { + return true + } + } + return false +} + func (f *Field) IsResolver() bool { return f.GoMethodName == "" && f.GoVarName == "" } @@ -57,6 +66,28 @@ func (f *Field) IsResolver() bool { func (f *Field) IsConcurrent() bool { return f.IsResolver() && !f.Object.DisableConcurrency } +func (f *Field) ShortInvocation() string { + if !f.IsResolver() { + return "" + } + shortName := strings.ToUpper(f.GQLName[:1]) + f.GQLName[1:] + res := fmt.Sprintf("%s().%s(ctx", f.Object.GQLType, shortName) + if !f.Object.Root { + res += fmt.Sprintf(", obj") + } + for _, arg := range f.Args { + res += fmt.Sprintf(", %s", arg.GoVarName) + } + res += ")" + return res +} +func (f *Field) ShortResolverDeclaration() string { + if !f.IsResolver() { + return "" + } + decl := strings.TrimPrefix(f.ResolverDeclaration(), f.Object.GQLType+"_") + return strings.ToUpper(decl[:1]) + decl[1:] +} func (f *Field) ResolverDeclaration() string { if !f.IsResolver() { diff --git a/codegen/templates/data.go b/codegen/templates/data.go index c948256e292..c024d322106 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -3,7 +3,7 @@ package templates var data = map[string]string{ "args.gotpl": "\t{{- if . }}args := map[string]interface{}{} {{end}}\n\t{{- range $i, $arg := . }}\n\t\tvar arg{{$i}} {{$arg.Signature }}\n\t\tif tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {\n\t\t\tvar err error\n\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\tif err != nil {\n\t\t\t\tec.Error(ctx, err)\n\t\t\t\t{{- if $arg.Object.Stream }}\n\t\t\t\t\treturn nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t{{- end }}\n\t\t\t}\n\t\t} {{ if $arg.Default }} else {\n\t\t\tvar tmp interface{} = {{ $arg.Default | dump }}\n\t\t\tvar err error\n\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\tif err != nil {\n\t\t\t\tec.Error(ctx, err)\n\t\t\t\t{{- if $arg.Object.Stream }}\n\t\t\t\t\treturn nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\t\t{{end }}\n\t\targs[{{$arg.GQLName|quote}}] = arg{{$i}}\n\t{{- end -}}\n", "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(ctx context.Context, field graphql.CollectedField) func() graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{Field: field})\n\t\tresults, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\tif err != nil {\n\t\t\tec.Error(ctx, 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}}(ctx context.Context, 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\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\t\t\tObject: {{$object.GQLType|quote}},\n\t\t\t\tArgs: {{if $field.Args }}args{{else}}nil{{end}},\n\t\t\t\tField: field,\n\t\t\t})\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(ctx, r)\n\t\t\t\t\t\tec.Error(ctx, userErr)\n\t\t\t\t\t\tret = graphql.Null\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t{{ else }}\n\t\t\trctx := graphql.GetResolverContext(ctx)\n\t\t\trctx.Object = {{$object.GQLType|quote}}\n\t\t\trctx.Args = {{if $field.Args }}args{{else}}nil{{end}}\n\t\t\trctx.Field = field\n\t\t\trctx.PushField(field.Alias)\n\t\t\tdefer rctx.Pop()\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(ctx, 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\n\t\t\t\tresTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {\n\t\t\t\t\treturn ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tec.Error(ctx, err)\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\n\t\t\t\tif resTmp == nil {\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\n\t\t\t\tres := resTmp.({{$field.Signature}})\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": "// Code 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: 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, op *query.Operation) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.QueryRoot.GQLType}}(ctx, op.Selections)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"queries are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, op *query.Operation) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.MutationRoot.GQLType}}(ctx, op.Selections)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"mutations are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, op *query.Operation) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(ctx, 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 := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\t\tbuf.Reset()\n\t\t\t\tdata := next()\n\n\t\t\t\tif data == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tdata.MarshalGQL(&buf)\n\t\t\t\treturn buf.Bytes()\n\t\t\t})\n\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf,\n\t\t\t\tErrors: ec.Errors,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(graphql.ErrorResponse(ctx, \"subscriptions are not supported\"))\n\t{{- end }}\n}\n\ntype executionContext struct {\n\t*graphql.RequestContext\n\n\tresolvers Resolvers\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\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\nvar parsedSchema = schema.MustParse({{.SchemaRaw|rawQuote}})\n", + "generated.gotpl": "// Code 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// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface.\nfunc MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema {\n\treturn &executableSchema{resolvers: resolvers}\n}\n\n// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.\nfunc NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema {\n\treturn MakeExecutableSchema(shortMapper{r: 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 ResolverRoot interface {\n{{- range $object := .Objects -}}\n\t{{ if $object.HasResolvers -}}\n\t\t{{$object.GQLType}}() {{$object.GQLType}}Resolver\n\t{{ end }}\n{{- end }}\n}\n\n{{- range $object := .Objects -}}\n\t{{ if $object.HasResolvers }}\n\t\ttype {{$object.GQLType}}Resolver interface {\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{ $field.ShortResolverDeclaration }}\n\t\t{{ end }}\n\t\t}\n\t{{- end }}\n{{- end }}\n\ntype shortMapper struct {\n\tr ResolverRoot\n}\n\n{{- range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{- if $field.IsResolver }}\n\t\t\tfunc (s shortMapper) {{ $field.ResolverDeclaration }} {\n\t\t\t\treturn s.r.{{$field.ShortInvocation}}\n\t\t\t}\n\t\t{{- end }}\n\t{{ end }}\n{{- end }}\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, op *query.Operation) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.QueryRoot.GQLType}}(ctx, op.Selections)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"queries are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, op *query.Operation) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.MutationRoot.GQLType}}(ctx, op.Selections)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"mutations are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, op *query.Operation) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(ctx, 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 := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\t\tbuf.Reset()\n\t\t\t\tdata := next()\n\n\t\t\t\tif data == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tdata.MarshalGQL(&buf)\n\t\t\t\treturn buf.Bytes()\n\t\t\t})\n\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf,\n\t\t\t\tErrors: ec.Errors,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(graphql.ErrorResponse(ctx, \"subscriptions are not supported\"))\n\t{{- end }}\n}\n\ntype executionContext struct {\n\t*graphql.RequestContext\n\n\tresolvers Resolvers\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\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\nvar parsedSchema = schema.MustParse({{.SchemaRaw|rawQuote}})\n", "input.gotpl": "\t{{- if .IsMarshaled }}\n\tfunc Unmarshal{{ .GQLType }}(v interface{}) ({{.FullName}}, error) {\n\t\tvar it {{.FullName}}\n\t\tvar asMap = v.(map[string]interface{})\n\t\t{{ range $field := .Fields}}\n\t\t\t{{- if $field.Default}}\n\t\t\t\tif _, present := asMap[{{$field.GQLName|quote}}] ; !present {\n\t\t\t\t\tasMap[{{$field.GQLName|quote}}] = {{ $field.Default | dump }}\n\t\t\t\t}\n\t\t\t{{- end}}\n\t\t{{- end }}\n\n\t\tfor k, v := range asMap {\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}}(ctx context.Context, 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}}(ctx, sel, &obj)\n\t\t{{- end}}\n\t\tcase *{{$implementor.FullName}}:\n\t\t\treturn ec._{{$implementor.GQLType}}(ctx, sel, obj)\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", "models.gotpl": "// Code 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}} `json:\"{{$field.GQLName}}\"`\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\n{{ range $enum := .Enums }}\n\ttype {{.GoType}} string\n\tconst (\n\t{{ range $value := .Values }}\n\t\t{{$enum.GoType}}{{ .Name|toCamel }} {{$enum.GoType}} = {{.Name|quote}} {{with .Description}} // {{.}} {{end}}\n\t{{- end }}\n\t)\n\n\tfunc (e {{.GoType}}) IsValid() bool {\n\t\tswitch e {\n\t\tcase {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.GoType }}{{ $element.Name|toCamel }}{{end}}:\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tfunc (e {{.GoType}}) String() string {\n\t\treturn string(e)\n\t}\n\n\tfunc (e *{{.GoType}}) UnmarshalGQL(v interface{}) error {\n\t\tstr, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"enums must be strings\")\n\t\t}\n\n\t\t*e = {{.GoType}}(str)\n\t\tif !e.IsValid() {\n\t\t\treturn fmt.Errorf(\"%s is not a valid {{.GQLType}}\", str)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfunc (e {{.GoType}}) MarshalGQL(w io.Writer) {\n\t\tfmt.Fprint(w, strconv.Quote(e.String()))\n\t}\n\n{{- end }}\n", diff --git a/codegen/templates/generated.gotpl b/codegen/templates/generated.gotpl index 72a3cac4abf..cc1dc459b07 100644 --- a/codegen/templates/generated.gotpl +++ b/codegen/templates/generated.gotpl @@ -8,10 +8,16 @@ import ( {{ end }} ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { {{- range $object := .Objects -}} {{ range $field := $object.Fields -}} @@ -20,6 +26,38 @@ type Resolvers interface { {{- end }} } +type ResolverRoot interface { +{{- range $object := .Objects -}} + {{ if $object.HasResolvers -}} + {{$object.GQLType}}() {{$object.GQLType}}Resolver + {{ end }} +{{- end }} +} + +{{- range $object := .Objects -}} + {{ if $object.HasResolvers }} + type {{$object.GQLType}}Resolver interface { + {{ range $field := $object.Fields -}} + {{ $field.ShortResolverDeclaration }} + {{ end }} + } + {{- end }} +{{- end }} + +type shortMapper struct { + r ResolverRoot +} + +{{- range $object := .Objects -}} + {{ range $field := $object.Fields -}} + {{- if $field.IsResolver }} + func (s shortMapper) {{ $field.ResolverDeclaration }} { + return s.r.{{$field.ShortInvocation}} + } + {{- end }} + {{ end }} +{{- end }} + type executableSchema struct { resolvers Resolvers } diff --git a/example/chat/chat_test.go b/example/chat/chat_test.go index 44734c2e969..4b63fd0526c 100644 --- a/example/chat/chat_test.go +++ b/example/chat/chat_test.go @@ -2,7 +2,6 @@ package chat import ( "net/http/httptest" - "sync" "testing" "github.com/stretchr/testify/require" @@ -13,8 +12,6 @@ import ( func TestChat(t *testing.T) { srv := httptest.NewServer(handler.GraphQL(MakeExecutableSchema(New()))) c := client.New(srv.URL) - var wg sync.WaitGroup - wg.Add(1) t.Run("subscribe to chat events", func(t *testing.T) { t.Parallel() @@ -22,31 +19,63 @@ func TestChat(t *testing.T) { sub := c.Websocket(`subscription { messageAdded(roomName:"#gophers") { text createdBy } }`) defer sub.Close() - wg.Done() - var resp struct { + postErrCh := make(chan error) + go func() { + var resp interface{} + // can't call t.Fatal from separate goroutine, so we return to the err chan for later + err := c.Post(`mutation { + a:post(text:"Hello!", roomName:"#gophers", username:"vektah") { id } + b:post(text:"Whats up?", roomName:"#gophers", username:"vektah") { id } + }`, &resp) + if err != nil { + // only push this error if non-nil + postErrCh <- err + } + }() + + type resp struct { MessageAdded struct { Text string CreatedBy string } } - require.NoError(t, sub.Next(&resp)) - require.Equal(t, "Hello!", resp.MessageAdded.Text) - require.Equal(t, "vektah", resp.MessageAdded.CreatedBy) - require.NoError(t, sub.Next(&resp)) - require.Equal(t, "Whats up?", resp.MessageAdded.Text) - require.Equal(t, "vektah", resp.MessageAdded.CreatedBy) - }) + // Contains the result of a `sub.Next` call + type subMsg struct { + resp + err error + } - t.Run("post two messages", func(t *testing.T) { - t.Parallel() + subCh := make(chan subMsg) + go func() { + var msg subMsg + + msg.err = sub.Next(&msg.resp) + subCh <- msg + + msg.err = sub.Next(&msg.resp) + subCh <- msg + }() + + var m subMsg + // Either can fail, and results in a failed test. + // + // Using a select prevents us from hanging the test + // in the event of a failure and instead reports + // back immediately. + select { + case m = <-subCh: + case err := <-postErrCh: + require.NoError(t, err, "post 2 messages") + } + require.NoError(t, m.err, "sub.Next") + require.Equal(t, "Hello!", m.resp.MessageAdded.Text) + require.Equal(t, "vektah", m.resp.MessageAdded.CreatedBy) - wg.Wait() - var resp interface{} - c.MustPost(`mutation { - a:post(text:"Hello!", roomName:"#gophers", username:"vektah") { id } - b:post(text:"Whats up?", roomName:"#gophers", username:"vektah") { id } - }`, &resp) + m = <-subCh + require.NoError(t, m.err, "sub.Next") + require.Equal(t, "Whats up?", m.resp.MessageAdded.Text) + require.Equal(t, "vektah", m.resp.MessageAdded.CreatedBy) }) } diff --git a/example/chat/generated.go b/example/chat/generated.go index f45e86db74e..2dc437e2c1e 100644 --- a/example/chat/generated.go +++ b/example/chat/generated.go @@ -13,10 +13,16 @@ import ( schema "github.com/vektah/gqlgen/neelance/schema" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { Mutation_post(ctx context.Context, text string, username string, roomName string) (Message, error) Query_room(ctx context.Context, name string) (*Chatroom, error) @@ -24,6 +30,37 @@ type Resolvers interface { Subscription_messageAdded(ctx context.Context, roomName string) (<-chan Message, error) } +type ResolverRoot interface { + Mutation() MutationResolver + Query() QueryResolver + Subscription() SubscriptionResolver +} +type MutationResolver interface { + Post(ctx context.Context, text string, username string, roomName string) (Message, error) +} +type QueryResolver interface { + Room(ctx context.Context, name string) (*Chatroom, error) +} +type SubscriptionResolver interface { + MessageAdded(ctx context.Context, roomName string) (<-chan Message, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) Mutation_post(ctx context.Context, text string, username string, roomName string) (Message, error) { + return s.r.Mutation().Post(ctx, text, username, roomName) +} + +func (s shortMapper) Query_room(ctx context.Context, name string) (*Chatroom, error) { + return s.r.Query().Room(ctx, name) +} + +func (s shortMapper) Subscription_messageAdded(ctx context.Context, roomName string) (<-chan Message, error) { + return s.r.Subscription().MessageAdded(ctx, roomName) +} + type executableSchema struct { resolvers Resolvers } diff --git a/example/dataloader/generated.go b/example/dataloader/generated.go index 6a5bdcf3a8e..eb3c3c01bbe 100644 --- a/example/dataloader/generated.go +++ b/example/dataloader/generated.go @@ -13,10 +13,16 @@ import ( schema "github.com/vektah/gqlgen/neelance/schema" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { Customer_address(ctx context.Context, obj *Customer) (*Address, error) Customer_orders(ctx context.Context, obj *Customer) ([]Order, error) @@ -26,6 +32,47 @@ type Resolvers interface { Query_torture(ctx context.Context, customerIds [][]int) ([][]Customer, error) } +type ResolverRoot interface { + Customer() CustomerResolver + Order() OrderResolver + Query() QueryResolver +} +type CustomerResolver interface { + Address(ctx context.Context, obj *Customer) (*Address, error) + Orders(ctx context.Context, obj *Customer) ([]Order, error) +} +type OrderResolver interface { + Items(ctx context.Context, obj *Order) ([]Item, error) +} +type QueryResolver interface { + Customers(ctx context.Context) ([]Customer, error) + Torture(ctx context.Context, customerIds [][]int) ([][]Customer, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) Customer_address(ctx context.Context, obj *Customer) (*Address, error) { + return s.r.Customer().Address(ctx, obj) +} + +func (s shortMapper) Customer_orders(ctx context.Context, obj *Customer) ([]Order, error) { + return s.r.Customer().Orders(ctx, obj) +} + +func (s shortMapper) Order_items(ctx context.Context, obj *Order) ([]Item, error) { + return s.r.Order().Items(ctx, obj) +} + +func (s shortMapper) Query_customers(ctx context.Context) ([]Customer, error) { + return s.r.Query().Customers(ctx) +} + +func (s shortMapper) Query_torture(ctx context.Context, customerIds [][]int) ([][]Customer, error) { + return s.r.Query().Torture(ctx, customerIds) +} + type executableSchema struct { resolvers Resolvers } diff --git a/example/scalars/generated.go b/example/scalars/generated.go index 5df780fa2af..732e3922db3 100644 --- a/example/scalars/generated.go +++ b/example/scalars/generated.go @@ -15,10 +15,16 @@ import ( schema "github.com/vektah/gqlgen/neelance/schema" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { Query_user(ctx context.Context, id external.ObjectID) (*User, error) Query_search(ctx context.Context, input SearchArgs) ([]User, error) @@ -27,6 +33,39 @@ type Resolvers interface { User_customResolver(ctx context.Context, obj *User) (Point, error) } +type ResolverRoot interface { + Query() QueryResolver + User() UserResolver +} +type QueryResolver interface { + User(ctx context.Context, id external.ObjectID) (*User, error) + Search(ctx context.Context, input SearchArgs) ([]User, error) +} +type UserResolver interface { + PrimitiveResolver(ctx context.Context, obj *User) (string, error) + CustomResolver(ctx context.Context, obj *User) (Point, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) Query_user(ctx context.Context, id external.ObjectID) (*User, error) { + return s.r.Query().User(ctx, id) +} + +func (s shortMapper) Query_search(ctx context.Context, input SearchArgs) ([]User, error) { + return s.r.Query().Search(ctx, input) +} + +func (s shortMapper) User_primitiveResolver(ctx context.Context, obj *User) (string, error) { + return s.r.User().PrimitiveResolver(ctx, obj) +} + +func (s shortMapper) User_customResolver(ctx context.Context, obj *User) (Point, error) { + return s.r.User().CustomResolver(ctx, obj) +} + type executableSchema struct { resolvers Resolvers } diff --git a/example/scalars/scalar_test.go b/example/scalars/scalar_test.go index 34416ad92cf..226c0ed1b3d 100644 --- a/example/scalars/scalar_test.go +++ b/example/scalars/scalar_test.go @@ -41,7 +41,9 @@ func TestScalars(t *testing.T) { fragment UserData on User { id name created tier address { location } }`, &resp) require.Equal(t, "1,2", resp.User.Address.Location) - require.Equal(t, time.Now().Unix(), resp.User.Created) + // There can be a delay between creation and test assertion, so we + // give some leeway to eliminate false positives. + require.WithinDuration(t, time.Now(), time.Unix(resp.User.Created, 0), 5*time.Second) require.Equal(t, "6,66", resp.Search[0].Address.Location) require.Equal(t, int64(666), resp.Search[0].Created) require.Equal(t, "A", resp.Search[0].Tier) diff --git a/example/selection/generated.go b/example/selection/generated.go index 0b3209117a8..7a36fa9bb28 100644 --- a/example/selection/generated.go +++ b/example/selection/generated.go @@ -14,14 +14,35 @@ import ( schema "github.com/vektah/gqlgen/neelance/schema" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { Query_events(ctx context.Context) ([]Event, error) } +type ResolverRoot interface { + Query() QueryResolver +} +type QueryResolver interface { + Events(ctx context.Context) ([]Event, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) Query_events(ctx context.Context) ([]Event, error) { + return s.r.Query().Events(ctx) +} + type executableSchema struct { resolvers Resolvers } diff --git a/example/starwars/generated.go b/example/starwars/generated.go index 81cad006e8a..e5c04858143 100644 --- a/example/starwars/generated.go +++ b/example/starwars/generated.go @@ -15,10 +15,16 @@ import ( schema "github.com/vektah/gqlgen/neelance/schema" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { Droid_friends(ctx context.Context, obj *Droid) ([]Character, error) Droid_friendsConnection(ctx context.Context, obj *Droid, first *int, after *string) (FriendsConnection, error) @@ -41,6 +47,104 @@ type Resolvers interface { Query_starship(ctx context.Context, id string) (*Starship, error) } +type ResolverRoot interface { + Droid() DroidResolver + FriendsConnection() FriendsConnectionResolver + Human() HumanResolver + Mutation() MutationResolver + Query() QueryResolver +} +type DroidResolver interface { + Friends(ctx context.Context, obj *Droid) ([]Character, error) + FriendsConnection(ctx context.Context, obj *Droid, first *int, after *string) (FriendsConnection, error) +} +type FriendsConnectionResolver interface { + Edges(ctx context.Context, obj *FriendsConnection) ([]FriendsEdge, error) + Friends(ctx context.Context, obj *FriendsConnection) ([]Character, error) +} +type HumanResolver interface { + Friends(ctx context.Context, obj *Human) ([]Character, error) + FriendsConnection(ctx context.Context, obj *Human, first *int, after *string) (FriendsConnection, error) + + Starships(ctx context.Context, obj *Human) ([]Starship, error) +} +type MutationResolver interface { + CreateReview(ctx context.Context, episode Episode, review Review) (*Review, error) +} +type QueryResolver interface { + Hero(ctx context.Context, episode Episode) (Character, error) + Reviews(ctx context.Context, episode Episode, since *time.Time) ([]Review, error) + Search(ctx context.Context, text string) ([]SearchResult, error) + Character(ctx context.Context, id string) (Character, error) + Droid(ctx context.Context, id string) (*Droid, error) + Human(ctx context.Context, id string) (*Human, error) + Starship(ctx context.Context, id string) (*Starship, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) Droid_friends(ctx context.Context, obj *Droid) ([]Character, error) { + return s.r.Droid().Friends(ctx, obj) +} + +func (s shortMapper) Droid_friendsConnection(ctx context.Context, obj *Droid, first *int, after *string) (FriendsConnection, error) { + return s.r.Droid().FriendsConnection(ctx, obj, first, after) +} + +func (s shortMapper) FriendsConnection_edges(ctx context.Context, obj *FriendsConnection) ([]FriendsEdge, error) { + return s.r.FriendsConnection().Edges(ctx, obj) +} + +func (s shortMapper) FriendsConnection_friends(ctx context.Context, obj *FriendsConnection) ([]Character, error) { + return s.r.FriendsConnection().Friends(ctx, obj) +} + +func (s shortMapper) Human_friends(ctx context.Context, obj *Human) ([]Character, error) { + return s.r.Human().Friends(ctx, obj) +} + +func (s shortMapper) Human_friendsConnection(ctx context.Context, obj *Human, first *int, after *string) (FriendsConnection, error) { + return s.r.Human().FriendsConnection(ctx, obj, first, after) +} + +func (s shortMapper) Human_starships(ctx context.Context, obj *Human) ([]Starship, error) { + return s.r.Human().Starships(ctx, obj) +} + +func (s shortMapper) Mutation_createReview(ctx context.Context, episode Episode, review Review) (*Review, error) { + return s.r.Mutation().CreateReview(ctx, episode, review) +} + +func (s shortMapper) Query_hero(ctx context.Context, episode Episode) (Character, error) { + return s.r.Query().Hero(ctx, episode) +} + +func (s shortMapper) Query_reviews(ctx context.Context, episode Episode, since *time.Time) ([]Review, error) { + return s.r.Query().Reviews(ctx, episode, since) +} + +func (s shortMapper) Query_search(ctx context.Context, text string) ([]SearchResult, error) { + return s.r.Query().Search(ctx, text) +} + +func (s shortMapper) Query_character(ctx context.Context, id string) (Character, error) { + return s.r.Query().Character(ctx, id) +} + +func (s shortMapper) Query_droid(ctx context.Context, id string) (*Droid, error) { + return s.r.Query().Droid(ctx, id) +} + +func (s shortMapper) Query_human(ctx context.Context, id string) (*Human, error) { + return s.r.Query().Human(ctx, id) +} + +func (s shortMapper) Query_starship(ctx context.Context, id string) (*Starship, error) { + return s.r.Query().Starship(ctx, id) +} + type executableSchema struct { resolvers Resolvers } diff --git a/example/todo/generated.go b/example/todo/generated.go index 2320ba64073..20498008159 100644 --- a/example/todo/generated.go +++ b/example/todo/generated.go @@ -13,10 +13,16 @@ import ( schema "github.com/vektah/gqlgen/neelance/schema" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { MyMutation_createTodo(ctx context.Context, todo TodoInput) (Todo, error) MyMutation_updateTodo(ctx context.Context, id int, changes map[string]interface{}) (*Todo, error) @@ -25,6 +31,44 @@ type Resolvers interface { MyQuery_todos(ctx context.Context) ([]Todo, error) } +type ResolverRoot interface { + MyMutation() MyMutationResolver + MyQuery() MyQueryResolver +} +type MyMutationResolver interface { + CreateTodo(ctx context.Context, todo TodoInput) (Todo, error) + UpdateTodo(ctx context.Context, id int, changes map[string]interface{}) (*Todo, error) +} +type MyQueryResolver interface { + Todo(ctx context.Context, id int) (*Todo, error) + LastTodo(ctx context.Context) (*Todo, error) + Todos(ctx context.Context) ([]Todo, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) MyMutation_createTodo(ctx context.Context, todo TodoInput) (Todo, error) { + return s.r.MyMutation().CreateTodo(ctx, todo) +} + +func (s shortMapper) MyMutation_updateTodo(ctx context.Context, id int, changes map[string]interface{}) (*Todo, error) { + return s.r.MyMutation().UpdateTodo(ctx, id, changes) +} + +func (s shortMapper) MyQuery_todo(ctx context.Context, id int) (*Todo, error) { + return s.r.MyQuery().Todo(ctx, id) +} + +func (s shortMapper) MyQuery_lastTodo(ctx context.Context) (*Todo, error) { + return s.r.MyQuery().LastTodo(ctx) +} + +func (s shortMapper) MyQuery_todos(ctx context.Context) ([]Todo, error) { + return s.r.MyQuery().Todos(ctx) +} + type executableSchema struct { resolvers Resolvers } diff --git a/test/generated.go b/test/generated.go index 2336ac610c7..57cbba167fc 100644 --- a/test/generated.go +++ b/test/generated.go @@ -15,10 +15,16 @@ import ( models "github.com/vektah/gqlgen/test/models" ) +// MakeExecutableSchema creates an ExecutableSchema from the Resolvers interface. func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { return &executableSchema{resolvers: resolvers} } +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(resolvers ResolverRoot) graphql.ExecutableSchema { + return MakeExecutableSchema(shortMapper{r: resolvers}) +} + type Resolvers interface { Element_child(ctx context.Context, obj *Element) (Element, error) Element_error(ctx context.Context, obj *Element) (bool, error) @@ -28,6 +34,49 @@ type Resolvers interface { Query_jsonEncoding(ctx context.Context) (string, error) } +type ResolverRoot interface { + Element() ElementResolver + Query() QueryResolver +} +type ElementResolver interface { + Child(ctx context.Context, obj *Element) (Element, error) + Error(ctx context.Context, obj *Element) (bool, error) +} +type QueryResolver interface { + Path(ctx context.Context) ([]Element, error) + Date(ctx context.Context, filter models.DateFilter) (bool, error) + Viewer(ctx context.Context) (*Viewer, error) + JsonEncoding(ctx context.Context) (string, error) +} + +type shortMapper struct { + r ResolverRoot +} + +func (s shortMapper) Element_child(ctx context.Context, obj *Element) (Element, error) { + return s.r.Element().Child(ctx, obj) +} + +func (s shortMapper) Element_error(ctx context.Context, obj *Element) (bool, error) { + return s.r.Element().Error(ctx, obj) +} + +func (s shortMapper) Query_path(ctx context.Context) ([]Element, error) { + return s.r.Query().Path(ctx) +} + +func (s shortMapper) Query_date(ctx context.Context, filter models.DateFilter) (bool, error) { + return s.r.Query().Date(ctx, filter) +} + +func (s shortMapper) Query_viewer(ctx context.Context) (*Viewer, error) { + return s.r.Query().Viewer(ctx) +} + +func (s shortMapper) Query_jsonEncoding(ctx context.Context) (string, error) { + return s.r.Query().JsonEncoding(ctx) +} + type executableSchema struct { resolvers Resolvers }