diff --git a/CHANGELOG.md b/CHANGELOG.md index 662377f1b8..d977ea4063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,178 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.35...HEAD) +## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.37...HEAD) + +## [v0.17.37](https://github.com/99designs/gqlgen/compare/v0.17.36...v0.17.37) - 2023-09-08 +- ccae370e release v0.17.37 + +- 6505f8be Update gqlparser (#2785) + +
153ec470 add uuid type (#2751) (closes #2749) + +* add uuid type + +* add uuid example + +* add uuid scalar doc + +* strconv.Quote + +* Apply suggestions from code review + +* fix + + + +--------- + +
+ +
fa471180 ForceGenerate parameter to [@goModel](https://github.com/goModel) added. (#2780) + +* forceGenerate to docs added + +--------- + +
+ +
11bb9b18 codegen: add support for `go_build_tags` option in gqlgen.yaml (#2784) + +* codegen: support go_build_tags option in gqlgen.yaml + +* chore: added test + +* docs/content: update config example + +* chore: more comment + +
+ +
bee47dcf fix flaky test TestSubscriptions (#2779) + +* fix flaky test TestSubscriptions + +* update other copy of the test + +
+ +- a41f4daa docs: short-lived loader (#2778) + +- cc4e0ba2 ensure HasOperationContext checks for nil (#2776) + +
a1ca2204 fix typo in TESTING.md server path (#2774) + +following TESTING.md instructions, I got an error: +"stat ./server/server.go: no such file or directory" + +server.go path is: integration/server/cmd/integration/server.go + +
+ +
1cde8c3f return internal types in schema introspection (#2773) + +according to graphql spec: +``` +types: return the set of all named types contained within this schema. +Any named type which can be found through a field of any introspection type must be included in this set. +``` +source: https://github.com/graphql/graphql-spec/blob/main/spec/Section%204%20--%20Introspection.md#the-__schema-type + +some clients libs (like HotChocolate for C#) depends on this behavior. + +
+ +- 065aea3e Fix gqlgen truncates tag value with colon (#2759) + +- d6270e4f Update subsciptions documentation to correctly close channel (#2753) + +- 2d8673a6 Add Model references to Interface (#2738) + +- 790d7a75 Allow GraphiQL headers to be set when creating the playground handler (#2740) (closes #2739) + +- 0eb95dc4 v0.17.36 postrelease bump + + + + + + +## [v0.17.36](https://github.com/99designs/gqlgen/compare/v0.17.35...v0.17.36) - 2023-07-27 +- bd6cfd31 release v0.17.36 + +
60ec0d86 Fix plugin template resolution (#2733) (closes #2262) + +- According to the documentation comment for [templates.Options], if the + `Template` and `TemplateFS` fields are empty, it `Render` should find + the `.gotpl` files from the calling plugin. However, it looks like + helper function. This results in broken behavior in consumers such as + [infiotinc/gqlgenc](https://github.com/infiotinc/gqlgenc) when they + use the latest version of `gqlgen` as instead of finding the template + from the plugin, the test template from this package is used which + outputs only: `this is my test package`. +- The cause for this is that `runtime.Caller` was still only skipping + one stack level which means that it was finding the `Render` function + instead of its caller. + +
+ +- 76d444c1 Make models configurable via template (#2730) + +- abe3ffde Don't set the package variable for the new Resolver Template (#2725) + +
febf9566 Make the resolver implementation configurable via a new template resolver.gotpl (#2720) + +* Make an optional resolver.gotpl ResolverTemplate to implement a custom resolver + +* Add test + +* Add documetation for the new resolver option + +* Change the tab to spaces + +* remove unecessary test assertion :/ + +
+ +
bda30260 Fixed Data Loader docs (#2723) + +Also updated to v7 + +
+ +
16c9eb64 Fix docs (#2722) + +* docs: fix variable names in dataloader sample + +* fix: request-scoped middleware + +
+ +
b233a01b docs: update dataloader docs (#2719) + +* docs: update example + +* docs: update example + +* fix: import + +
+ +- cccc7389 Added go mod tidy to quick start guide (#2718) (closes #2717, #2651, #2641, #2614, #2576) + +- 9adc7b81 Update gqlparser to v2.5.8 (#2716) + +- b442fbf4 Post v0.17.35 changelog update + +- 57c12199 v0.17.35 postrelease bump + + + + + ## [v0.17.35](https://github.com/99designs/gqlgen/compare/v0.17.34...v0.17.35) - 2023-07-15 - 05006bf1 release v0.17.35 diff --git a/README.md b/README.md index e35db1c123..b11ab55392 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Still not convinced enough to use **gqlgen**? Compare **gqlgen** with other Go g go run github.com/99designs/gqlgen init + go mod tidy + 4. Start the graphql server go run server.go diff --git a/TESTING.md b/TESTING.md index b8adadd649..2677b33e48 100644 --- a/TESTING.md +++ b/TESTING.md @@ -25,7 +25,7 @@ Setting up the integration environment is a little tricky: ```bash cd integration go generate ./... -go run ./server/server.go +go run ./server/cmd/integration/server.go ``` in another terminal ```bash diff --git a/_examples/chat/chat_test.go b/_examples/chat/chat_test.go index 8a4081bdb8..c3dd59abee 100644 --- a/_examples/chat/chat_test.go +++ b/_examples/chat/chat_test.go @@ -6,10 +6,11 @@ import ( "sync" "testing" - "github.com/99designs/gqlgen/client" - "github.com/99designs/gqlgen/graphql/handler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql/handler" ) func TestChatSubscriptions(t *testing.T) { diff --git a/_examples/chat/generated.go b/_examples/chat/generated.go index 58f7545a7b..2ee09e11a2 100644 --- a/_examples/chat/generated.go +++ b/_examples/chat/generated.go @@ -25,6 +25,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -32,6 +33,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -84,12 +86,16 @@ type SubscriptionResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -288,14 +294,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" diff --git a/_examples/chat/server/server.go b/_examples/chat/server/server.go index 19e938f340..5627f4d2a0 100644 --- a/_examples/chat/server/server.go +++ b/_examples/chat/server/server.go @@ -6,11 +6,6 @@ import ( "net/url" "time" - "github.com/99designs/gqlgen/graphql/handler/extension" - "github.com/99designs/gqlgen/graphql/handler/transport" - - "github.com/99designs/gqlgen/graphql/playground" - "github.com/gorilla/websocket" "github.com/opentracing/opentracing-go" "github.com/rs/cors" @@ -20,6 +15,9 @@ import ( "github.com/99designs/gqlgen/_examples/chat" "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/99designs/gqlgen/graphql/playground" ) func main() { diff --git a/_examples/config/generated.go b/_examples/config/generated.go index 9d2a01c55a..b377298385 100644 --- a/_examples/config/generated.go +++ b/_examples/config/generated.go @@ -23,6 +23,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -30,6 +31,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -87,12 +89,16 @@ type RoleResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -278,14 +284,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" "todo.graphql" "user.graphql" diff --git a/_examples/config/schema.resolvers.go b/_examples/config/schema.resolvers.go index 39649ff57f..c783ab751e 100644 --- a/_examples/config/schema.resolvers.go +++ b/_examples/config/schema.resolvers.go @@ -2,7 +2,7 @@ package config // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/config/todo.resolvers.go b/_examples/config/todo.resolvers.go index 04fff1f298..57ca050c8e 100644 --- a/_examples/config/todo.resolvers.go +++ b/_examples/config/todo.resolvers.go @@ -2,7 +2,7 @@ package config // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/config/user.resolvers.go b/_examples/config/user.resolvers.go index 06a9c54377..05a4deb48c 100644 --- a/_examples/config/user.resolvers.go +++ b/_examples/config/user.resolvers.go @@ -2,7 +2,7 @@ package config // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/dataloader/dataloader_test.go b/_examples/dataloader/dataloader_test.go index c70b45b95a..34dc9e427d 100644 --- a/_examples/dataloader/dataloader_test.go +++ b/_examples/dataloader/dataloader_test.go @@ -3,10 +3,11 @@ package dataloader import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/stretchr/testify/require" ) func TestTodo(t *testing.T) { diff --git a/_examples/dataloader/generated.go b/_examples/dataloader/generated.go index e205815208..b882fbc52c 100644 --- a/_examples/dataloader/generated.go +++ b/_examples/dataloader/generated.go @@ -24,6 +24,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -31,6 +32,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -91,12 +93,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -298,14 +304,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" diff --git a/_examples/embedding/subdir/embedding_test.go b/_examples/embedding/subdir/embedding_test.go index d779455236..3f2e97d24c 100644 --- a/_examples/embedding/subdir/embedding_test.go +++ b/_examples/embedding/subdir/embedding_test.go @@ -3,10 +3,11 @@ package subdir import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/_examples/embedding/subdir/gendir" "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestEmbeddingWorks(t *testing.T) { diff --git a/_examples/embedding/subdir/gendir/generated.go b/_examples/embedding/subdir/gendir/generated.go index edb6ace6ba..eaf200d2dd 100644 --- a/_examples/embedding/subdir/gendir/generated.go +++ b/_examples/embedding/subdir/gendir/generated.go @@ -23,6 +23,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -30,6 +31,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -62,12 +64,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -189,14 +195,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } var sources = []*ast.Source{ diff --git a/_examples/embedding/subdir/root_.generated.go b/_examples/embedding/subdir/root_.generated.go index 03c8e18e5a..d2dc848a05 100644 --- a/_examples/embedding/subdir/root_.generated.go +++ b/_examples/embedding/subdir/root_.generated.go @@ -19,6 +19,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -26,6 +27,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -52,12 +54,16 @@ type ComplexityRoot struct { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -179,14 +185,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schemadir/root.graphqls" "subdir.graphqls" diff --git a/_examples/federation/accounts/graph/entity.resolvers.go b/_examples/federation/accounts/graph/entity.resolvers.go index 6daa854284..31b2cad602 100644 --- a/_examples/federation/accounts/graph/entity.resolvers.go +++ b/_examples/federation/accounts/graph/entity.resolvers.go @@ -2,7 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/federation/accounts/graph/generated.go b/_examples/federation/accounts/graph/generated.go index 398daeacd9..791d5fa047 100644 --- a/_examples/federation/accounts/graph/generated.go +++ b/_examples/federation/accounts/graph/generated.go @@ -25,6 +25,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -32,6 +33,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -83,12 +85,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -274,14 +280,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphqls" diff --git a/_examples/federation/accounts/graph/schema.resolvers.go b/_examples/federation/accounts/graph/schema.resolvers.go index acc71de4fc..a3364c1207 100644 --- a/_examples/federation/accounts/graph/schema.resolvers.go +++ b/_examples/federation/accounts/graph/schema.resolvers.go @@ -2,7 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/federation/products/graph/entity.resolvers.go b/_examples/federation/products/graph/entity.resolvers.go index 2d66bf9e19..840d32321a 100644 --- a/_examples/federation/products/graph/entity.resolvers.go +++ b/_examples/federation/products/graph/entity.resolvers.go @@ -2,7 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/federation/products/graph/generated.go b/_examples/federation/products/graph/generated.go index 15a66fb2cc..0f13b18d8f 100644 --- a/_examples/federation/products/graph/generated.go +++ b/_examples/federation/products/graph/generated.go @@ -25,6 +25,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -32,6 +33,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -86,12 +88,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -301,14 +307,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphqls" diff --git a/_examples/federation/products/graph/schema.resolvers.go b/_examples/federation/products/graph/schema.resolvers.go index 4255804608..87d62e225c 100644 --- a/_examples/federation/products/graph/schema.resolvers.go +++ b/_examples/federation/products/graph/schema.resolvers.go @@ -2,7 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/federation/reviews/graph/entity.resolvers.go b/_examples/federation/reviews/graph/entity.resolvers.go index 265b2cdc8d..08102f8a30 100644 --- a/_examples/federation/reviews/graph/entity.resolvers.go +++ b/_examples/federation/reviews/graph/entity.resolvers.go @@ -2,7 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/federation/reviews/graph/generated.go b/_examples/federation/reviews/graph/generated.go index ac7f09beab..79060f78c2 100644 --- a/_examples/federation/reviews/graph/generated.go +++ b/_examples/federation/reviews/graph/generated.go @@ -25,6 +25,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -32,6 +33,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -97,12 +99,16 @@ type UserResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -323,14 +329,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphqls" diff --git a/_examples/federation/reviews/graph/schema.resolvers.go b/_examples/federation/reviews/graph/schema.resolvers.go index 1df3e2f031..4900e4d8e2 100644 --- a/_examples/federation/reviews/graph/schema.resolvers.go +++ b/_examples/federation/reviews/graph/schema.resolvers.go @@ -2,7 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/_examples/fileupload/fileupload_test.go b/_examples/fileupload/fileupload_test.go index 29ef9dc3fb..c4138c14c9 100644 --- a/_examples/fileupload/fileupload_test.go +++ b/_examples/fileupload/fileupload_test.go @@ -8,12 +8,13 @@ import ( "os" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/_examples/fileupload/model" gqlclient "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/require" ) func TestFileUpload(t *testing.T) { diff --git a/_examples/fileupload/generated.go b/_examples/fileupload/generated.go index f5730632e9..160ab84faa 100644 --- a/_examples/fileupload/generated.go +++ b/_examples/fileupload/generated.go @@ -24,6 +24,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -31,6 +32,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -75,12 +77,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -267,14 +273,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" diff --git a/_examples/fileupload/server/server.go b/_examples/fileupload/server/server.go index 75a47b73b6..55dc08d883 100644 --- a/_examples/fileupload/server/server.go +++ b/_examples/fileupload/server/server.go @@ -7,15 +7,13 @@ import ( "log" "net/http" - "github.com/99designs/gqlgen/graphql/handler/extension" - "github.com/99designs/gqlgen/graphql/handler/transport" - - "github.com/99designs/gqlgen/graphql/playground" - "github.com/99designs/gqlgen/_examples/fileupload" "github.com/99designs/gqlgen/_examples/fileupload/model" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/99designs/gqlgen/graphql/playground" ) func main() { diff --git a/_examples/go.mod b/_examples/go.mod index 1da124d769..b6dfdaa00b 100644 --- a/_examples/go.mod +++ b/_examples/go.mod @@ -5,14 +5,15 @@ go 1.18 replace github.com/99designs/gqlgen => ../ require ( - github.com/99designs/gqlgen v0.17.32 + github.com/99designs/gqlgen v0.17.36 + github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/opentracing/opentracing-go v1.2.0 github.com/rs/cors v1.9.0 github.com/stretchr/testify v1.8.2 github.com/vektah/dataloaden v0.3.0 - github.com/vektah/gqlparser/v2 v2.5.7 + github.com/vektah/gqlparser/v2 v2.5.10 sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 ) @@ -24,6 +25,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sosodev/duration v1.1.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/tools v0.9.3 // indirect diff --git a/_examples/go.sum b/_examples/go.sum index 5272749751..98f0ac96a2 100644 --- a/_examples/go.sum +++ b/_examples/go.sum @@ -1,7 +1,6 @@ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -12,6 +11,8 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -21,11 +22,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.3/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -48,17 +44,17 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sosodev/duration v1.1.0 h1:kQcaiGbJaIsRqgQy7VGlZrVw1giWO+lDoX3MCPnpVO4= +github.com/sosodev/duration v1.1.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -66,8 +62,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84= github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= -github.com/vektah/gqlparser/v2 v2.5.7 h1:QnW4lWFSaycZ1jqvVaQ/tDXGGzQfqAuWdyC4S9g/KVM= -github.com/vektah/gqlparser/v2 v2.5.7/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU= +github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -114,12 +110,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/_examples/scalars/generated.go b/_examples/scalars/generated.go index 94e57c0fa9..549d3a4fa5 100644 --- a/_examples/scalars/generated.go +++ b/_examples/scalars/generated.go @@ -26,6 +26,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -33,6 +34,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -84,12 +86,16 @@ type UserResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -305,14 +311,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" diff --git a/_examples/scalars/scalar_test.go b/_examples/scalars/scalar_test.go index f9ca2b1dd1..0d3228b141 100644 --- a/_examples/scalars/scalar_test.go +++ b/_examples/scalars/scalar_test.go @@ -4,10 +4,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/stretchr/testify/require" ) type RawUser struct { diff --git a/_examples/selection/generated.go b/_examples/selection/generated.go index b0f567eefd..a583e9edab 100644 --- a/_examples/selection/generated.go +++ b/_examples/selection/generated.go @@ -24,6 +24,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -31,6 +32,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -68,12 +70,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -223,14 +229,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" diff --git a/_examples/selection/selection.go b/_examples/selection/selection.go index 5890d14ab9..6a6b0f9bec 100644 --- a/_examples/selection/selection.go +++ b/_examples/selection/selection.go @@ -7,8 +7,9 @@ import ( "fmt" "time" - "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" ) type Resolver struct{} diff --git a/_examples/selection/selection_test.go b/_examples/selection/selection_test.go index c687403322..72495e927b 100644 --- a/_examples/selection/selection_test.go +++ b/_examples/selection/selection_test.go @@ -3,9 +3,10 @@ package selection import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestSelection(t *testing.T) { diff --git a/_examples/starwars/generated/exec.go b/_examples/starwars/generated/exec.go index b7a934bdab..1465c2bb43 100644 --- a/_examples/starwars/generated/exec.go +++ b/_examples/starwars/generated/exec.go @@ -24,6 +24,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -31,6 +32,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -146,12 +148,16 @@ type StarshipResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -581,14 +587,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } var sources = []*ast.Source{ diff --git a/_examples/starwars/resolvers.go b/_examples/starwars/resolvers.go index e7b9629d12..6c94a2ea0f 100644 --- a/_examples/starwars/resolvers.go +++ b/_examples/starwars/resolvers.go @@ -12,7 +12,6 @@ import ( "time" "github.com/99designs/gqlgen/_examples/starwars/generated" - "github.com/99designs/gqlgen/_examples/starwars/models" ) diff --git a/_examples/starwars/starwars_test.go b/_examples/starwars/starwars_test.go index 8cbe861b0c..a01f9fb7b5 100644 --- a/_examples/starwars/starwars_test.go +++ b/_examples/starwars/starwars_test.go @@ -3,11 +3,12 @@ package starwars import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/_examples/starwars/generated" "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/stretchr/testify/require" ) func TestStarwars(t *testing.T) { diff --git a/_examples/todo/generated.go b/_examples/todo/generated.go index 8317645e00..0aa9e6c197 100644 --- a/_examples/todo/generated.go +++ b/_examples/todo/generated.go @@ -23,6 +23,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -30,6 +31,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -75,12 +77,16 @@ type MyQueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -259,14 +265,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphql" diff --git a/_examples/todo/todo.go b/_examples/todo/todo.go index ad3da2c392..f8a710722e 100644 --- a/_examples/todo/todo.go +++ b/_examples/todo/todo.go @@ -8,8 +8,9 @@ import ( "fmt" "time" - "github.com/99designs/gqlgen/graphql" "github.com/mitchellh/mapstructure" + + "github.com/99designs/gqlgen/graphql" ) var ( diff --git a/_examples/todo/todo_test.go b/_examples/todo/todo_test.go index a6cef4ac05..348ecc7023 100644 --- a/_examples/todo/todo_test.go +++ b/_examples/todo/todo_test.go @@ -3,10 +3,11 @@ package todo import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/stretchr/testify/require" ) func TestTodo(t *testing.T) { diff --git a/_examples/type-system-extension/generated.go b/_examples/type-system-extension/generated.go index 1fa0b781b2..71fb282a51 100644 --- a/_examples/type-system-extension/generated.go +++ b/_examples/type-system-extension/generated.go @@ -23,6 +23,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -30,6 +31,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -77,12 +79,16 @@ type MyQueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -245,14 +251,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schemas/enum-extension.graphql" "schemas/input-object-extension.graphql" "schemas/interface-extension.graphql" "schemas/object-extension.graphql" "schemas/scalar-extension.graphql" "schemas/schema-extension.graphql" "schemas/schema.graphql" "schemas/type-extension.graphql" "schemas/union-extension.graphql" diff --git a/_examples/type-system-extension/server/server.go b/_examples/type-system-extension/server/server.go index 2ddca5f7b9..6fec1d2403 100644 --- a/_examples/type-system-extension/server/server.go +++ b/_examples/type-system-extension/server/server.go @@ -5,10 +5,9 @@ import ( "net/http" "os" - "github.com/99designs/gqlgen/graphql/playground" - extension "github.com/99designs/gqlgen/_examples/type-system-extension" "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/playground" ) const defaultPort = "8080" diff --git a/_examples/uuid/gqlgen.yml b/_examples/uuid/gqlgen.yml new file mode 100644 index 0000000000..33c3d09ddd --- /dev/null +++ b/_examples/uuid/gqlgen.yml @@ -0,0 +1,90 @@ +# Where are all the schema files located? globs are supported eg src/**/*.graphqls +schema: + - graph/*.graphqls + +# Where should the generated server code go? +exec: + filename: graph/generated.go + package: graph + +# Uncomment to enable federation +# federation: +# filename: graph/federation.go +# package: graph + +# Where should any generated models go? +model: + filename: graph/model/models_gen.go + package: model + +# Where should the resolver implementations go? +resolver: + layout: follow-schema + dir: graph + package: graph + filename_template: "{name}.resolvers.go" + # Optional: turn on to not generate template comments above resolvers + # omit_template_comment: false + +# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models +# struct_tag: json + +# Optional: turn on to use []Thing instead of []*Thing +# omit_slice_element_pointers: false + +# Optional: turn on to omit Is() methods to interface and unions +# omit_interface_checks : true + +# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function +# omit_complexity: false + +# Optional: turn on to not generate any file notice comments in generated files +# omit_gqlgen_file_notice: false + +# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true. +# omit_gqlgen_version_in_file_notice: false + +# Optional: turn off to make struct-type struct fields not use pointers +# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } +# struct_fields_always_pointers: true + +# Optional: turn off to make resolvers return values instead of pointers for structs +# resolvers_always_return_pointers: true + +# Optional: turn on to return pointers instead of values in unmarshalInput +# return_pointers_in_unmarshalinput: false + +# Optional: wrap nullable input fields with Omittable +# nullable_input_omittable: true + +# Optional: set to speed up generation time by not performing a final validation pass. +# skip_validation: true + +# Optional: set to skip running `go mod tidy` when generating server code +# skip_mod_tidy: true + +# gqlgen will search for any type names in the schema in these go packages +# if they match it will use them, otherwise it will generate them. +autobind: +# - "github.com/99designs/gqlgen/_examples/uuid/graph/model" + +# This section declares type mapping between the GraphQL and go type systems +# +# The first line in each type will be used as defaults for resolver arguments and +# modelgen, the others will be allowed when binding to fields. Configure them to +# your liking +models: + ID: + model: + - github.com/99designs/gqlgen/graphql.ID + - github.com/99designs/gqlgen/graphql.Int + - github.com/99designs/gqlgen/graphql.Int64 + - github.com/99designs/gqlgen/graphql.Int32 + Int: + model: + - github.com/99designs/gqlgen/graphql.Int + - github.com/99designs/gqlgen/graphql.Int64 + - github.com/99designs/gqlgen/graphql.Int32 + UUID: + model: + - github.com/99designs/gqlgen/graphql.UUID diff --git a/_examples/uuid/graph/generated.go b/_examples/uuid/graph/generated.go new file mode 100644 index 0000000000..57b2c363a8 --- /dev/null +++ b/_examples/uuid/graph/generated.go @@ -0,0 +1,3700 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package graph + +import ( + "bytes" + "context" + "embed" + "errors" + "fmt" + "strconv" + "sync" + "sync/atomic" + + "github.com/99designs/gqlgen/_examples/uuid/graph/model" + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/introspection" + "github.com/google/uuid" + gqlparser "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" +) + +// region ************************** generated!.gotpl ************************** + +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { + return &executableSchema{ + resolvers: cfg.Resolvers, + directives: cfg.Directives, + complexity: cfg.Complexity, + } +} + +type Config struct { + Resolvers ResolverRoot + Directives DirectiveRoot + Complexity ComplexityRoot +} + +type ResolverRoot interface { + Mutation() MutationResolver + Query() QueryResolver +} + +type DirectiveRoot struct { +} + +type ComplexityRoot struct { + Mutation struct { + CreateTodo func(childComplexity int, input model.NewTodo) int + } + + Query struct { + Todos func(childComplexity int) int + } + + Todo struct { + Done func(childComplexity int) int + ID func(childComplexity int) int + Text func(childComplexity int) int + UID func(childComplexity int) int + } +} + +type MutationResolver interface { + CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) +} +type QueryResolver interface { + Todos(ctx context.Context) ([]*model.Todo, error) +} + +type executableSchema struct { + resolvers ResolverRoot + directives DirectiveRoot + complexity ComplexityRoot +} + +func (e *executableSchema) Schema() *ast.Schema { + return parsedSchema +} + +func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) { + ec := executionContext{nil, e, 0, 0, nil} + _ = ec + switch typeName + "." + field { + + case "Mutation.createTodo": + if e.complexity.Mutation.CreateTodo == nil { + break + } + + args, err := ec.field_Mutation_createTodo_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateTodo(childComplexity, args["input"].(model.NewTodo)), true + + case "Query.todos": + if e.complexity.Query.Todos == nil { + break + } + + return e.complexity.Query.Todos(childComplexity), true + + case "Todo.done": + if e.complexity.Todo.Done == nil { + break + } + + return e.complexity.Todo.Done(childComplexity), true + + case "Todo.id": + if e.complexity.Todo.ID == nil { + break + } + + return e.complexity.Todo.ID(childComplexity), true + + case "Todo.text": + if e.complexity.Todo.Text == nil { + break + } + + return e.complexity.Todo.Text(childComplexity), true + + case "Todo.uid": + if e.complexity.Todo.UID == nil { + break + } + + return e.complexity.Todo.UID(childComplexity), true + + } + return 0, false +} + +func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { + rc := graphql.GetOperationContext(ctx) + ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)} + inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputNewTodo, + ) + first := true + + switch rc.Operation.Operation { + case ast.Query: + return func(ctx context.Context) *graphql.Response { + var response graphql.Response + var data graphql.Marshaler + if first { + first = false + ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap) + data = ec._Query(ctx, rc.Operation.SelectionSet) + } else { + if atomic.LoadInt32(&ec.pendingDeferred) > 0 { + result := <-ec.deferredResults + atomic.AddInt32(&ec.pendingDeferred, -1) + data = result.Result + response.Path = result.Path + response.Label = result.Label + response.Errors = result.Errors + } else { + return nil + } + } + var buf bytes.Buffer + data.MarshalGQL(&buf) + response.Data = buf.Bytes() + if atomic.LoadInt32(&ec.deferred) > 0 { + hasNext := atomic.LoadInt32(&ec.pendingDeferred) > 0 + response.HasNext = &hasNext + } + + return &response + } + case ast.Mutation: + return func(ctx context.Context) *graphql.Response { + if !first { + return nil + } + first = false + ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap) + data := ec._Mutation(ctx, rc.Operation.SelectionSet) + var buf bytes.Buffer + data.MarshalGQL(&buf) + + return &graphql.Response{ + Data: buf.Bytes(), + } + } + + default: + return graphql.OneShot(graphql.ErrorResponse(ctx, "unsupported GraphQL operation")) + } +} + +type executionContext struct { + *graphql.OperationContext + *executableSchema + deferred int32 + pendingDeferred int32 + deferredResults chan graphql.DeferredResult +} + +func (ec *executionContext) processDeferredGroup(dg graphql.DeferredGroup) { + atomic.AddInt32(&ec.pendingDeferred, 1) + go func() { + ctx := graphql.WithFreshResponseContext(dg.Context) + dg.FieldSet.Dispatch(ctx) + ds := graphql.DeferredResult{ + Path: dg.Path, + Label: dg.Label, + Result: dg.FieldSet, + Errors: graphql.GetErrors(ctx), + } + // null fields should bubble up + if dg.FieldSet.Invalids > 0 { + ds.Result = graphql.Null + } + ec.deferredResults <- ds + }() +} + +func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { + if ec.DisableIntrospection { + return nil, errors.New("introspection disabled") + } + return introspection.WrapSchema(parsedSchema), nil +} + +func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { + if ec.DisableIntrospection { + return nil, errors.New("introspection disabled") + } + return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil +} + +//go:embed "schema.graphqls" +var sourcesFS embed.FS + +func sourceData(filename string) string { + data, err := sourcesFS.ReadFile(filename) + if err != nil { + panic(fmt.Sprintf("codegen problem: %s not available", filename)) + } + return string(data) +} + +var sources = []*ast.Source{ + {Name: "schema.graphqls", Input: sourceData("schema.graphqls"), BuiltIn: false}, +} +var parsedSchema = gqlparser.MustLoadSchema(sources...) + +// endregion ************************** generated!.gotpl ************************** + +// region ***************************** args.gotpl ***************************** + +func (ec *executionContext) field_Mutation_createTodo_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.NewTodo + if tmp, ok := rawArgs["input"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + arg0, err = ec.unmarshalNNewTodo2githubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐNewTodo(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["name"] = arg0 + return args, nil +} + +func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 bool + if tmp, ok := rawArgs["includeDeprecated"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includeDeprecated")) + arg0, err = ec.unmarshalOBoolean2bool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["includeDeprecated"] = arg0 + return args, nil +} + +func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 bool + if tmp, ok := rawArgs["includeDeprecated"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includeDeprecated")) + arg0, err = ec.unmarshalOBoolean2bool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["includeDeprecated"] = arg0 + return args, nil +} + +// endregion ***************************** args.gotpl ***************************** + +// region ************************** directives.gotpl ************************** + +// endregion ************************** directives.gotpl ************************** + +// region **************************** field.gotpl ***************************** + +func (ec *executionContext) _Mutation_createTodo(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createTodo(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateTodo(rctx, fc.Args["input"].(model.NewTodo)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Todo) + fc.Result = res + return ec.marshalNTodo2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐTodo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createTodo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Todo_id(ctx, field) + case "text": + return ec.fieldContext_Todo_text(ctx, field) + case "done": + return ec.fieldContext_Todo_done(ctx, field) + case "uid": + return ec.fieldContext_Todo_uid(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Todo", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createTodo_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_todos(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_todos(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Todos(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Todo) + fc.Result = res + return ec.marshalNTodo2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐTodoᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_todos(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Todo_id(ctx, field) + case "text": + return ec.fieldContext_Todo_text(ctx, field) + case "done": + return ec.fieldContext_Todo_done(ctx, field) + case "uid": + return ec.fieldContext_Todo_uid(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Todo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query___type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.introspectType(fc.Args["name"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query___type(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query___type_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query___schema(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.introspectSchema() + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Schema) + fc.Result = res + return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query___schema(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "description": + return ec.fieldContext___Schema_description(ctx, field) + case "types": + return ec.fieldContext___Schema_types(ctx, field) + case "queryType": + return ec.fieldContext___Schema_queryType(ctx, field) + case "mutationType": + return ec.fieldContext___Schema_mutationType(ctx, field) + case "subscriptionType": + return ec.fieldContext___Schema_subscriptionType(ctx, field) + case "directives": + return ec.fieldContext___Schema_directives(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Schema", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Todo_id(ctx context.Context, field graphql.CollectedField, obj *model.Todo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Todo_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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) fieldContext_Todo_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Todo", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Todo_text(ctx context.Context, field graphql.CollectedField, obj *model.Todo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Todo_text(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Text, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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) fieldContext_Todo_text(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Todo", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Todo_done(ctx context.Context, field graphql.CollectedField, obj *model.Todo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Todo_done(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Done, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Todo_done(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Todo", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Todo_uid(ctx context.Context, field graphql.CollectedField, obj *model.Todo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Todo_uid(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.UID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(uuid.UUID) + fc.Result = res + return ec.marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Todo_uid(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Todo", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type UUID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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) fieldContext___Directive_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_locations(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Locations, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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.marshalN__DirectiveLocation2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_locations(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type __DirectiveLocation does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_args(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Args, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_args(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___InputValue_name(ctx, field) + case "description": + return ec.fieldContext___InputValue_description(ctx, field) + case "type": + return ec.fieldContext___InputValue_type(ctx, field) + case "defaultValue": + return ec.fieldContext___InputValue_defaultValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __InputValue", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_isRepeatable(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_isRepeatable(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsRepeatable, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_isRepeatable(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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) fieldContext___EnumValue_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_isDeprecated(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsDeprecated(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_deprecationReason(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeprecationReason(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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) fieldContext___Field_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_args(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Args, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_args(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___InputValue_name(ctx, field) + case "description": + return ec.fieldContext___InputValue_description(ctx, field) + case "type": + return ec.fieldContext___InputValue_type(ctx, field) + case "defaultValue": + return ec.fieldContext___InputValue_defaultValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __InputValue", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_type(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_isDeprecated(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsDeprecated(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_isDeprecated(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_deprecationReason(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeprecationReason(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_deprecationReason(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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) fieldContext___InputValue_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_type(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_defaultValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DefaultValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_types(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Types(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_types(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_queryType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.QueryType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_queryType(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_mutationType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.MutationType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_mutationType(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_subscriptionType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SubscriptionType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_directives(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_directives(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Directives(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.Directive) + fc.Result = res + return ec.marshalN__Directive2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirectiveᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_directives(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___Directive_name(ctx, field) + case "description": + return ec.fieldContext___Directive_description(ctx, field) + case "locations": + return ec.fieldContext___Directive_locations(ctx, field) + case "args": + return ec.fieldContext___Directive_args(ctx, field) + case "isRepeatable": + return ec.fieldContext___Directive_isRepeatable(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Directive", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_kind(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Kind(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + 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.marshalN__TypeKind2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_kind(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type __TypeKind does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_fields(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Fields(fc.Args["includeDeprecated"].(bool)), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.Field) + fc.Result = res + return ec.marshalO__Field2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐFieldᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_fields(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___Field_name(ctx, field) + case "description": + return ec.fieldContext___Field_description(ctx, field) + case "args": + return ec.fieldContext___Field_args(ctx, field) + case "type": + return ec.fieldContext___Field_type(ctx, field) + case "isDeprecated": + return ec.fieldContext___Field_isDeprecated(ctx, field) + case "deprecationReason": + return ec.fieldContext___Field_deprecationReason(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Field", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field___Type_fields_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_interfaces(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Interfaces(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_interfaces(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_possibleTypes(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.PossibleTypes(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_possibleTypes(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_enumValues(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EnumValues(fc.Args["includeDeprecated"].(bool)), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.EnumValue) + fc.Result = res + return ec.marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_enumValues(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___EnumValue_name(ctx, field) + case "description": + return ec.fieldContext___EnumValue_description(ctx, field) + case "isDeprecated": + return ec.fieldContext___EnumValue_isDeprecated(ctx, field) + case "deprecationReason": + return ec.fieldContext___EnumValue_deprecationReason(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __EnumValue", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field___Type_enumValues_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_inputFields(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.InputFields(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalO__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_inputFields(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___InputValue_name(ctx, field) + case "description": + return ec.fieldContext___InputValue_description(ctx, field) + case "type": + return ec.fieldContext___InputValue_type(ctx, field) + case "defaultValue": + return ec.fieldContext___InputValue_defaultValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __InputValue", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_ofType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.OfType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_ofType(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_specifiedByURL(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_specifiedByURL(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SpecifiedByURL(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_specifiedByURL(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +// endregion **************************** field.gotpl ***************************** + +// region **************************** input.gotpl ***************************** + +func (ec *executionContext) unmarshalInputNewTodo(ctx context.Context, obj interface{}) (model.NewTodo, error) { + var it model.NewTodo + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"text", "userId", "uid"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "text": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("text")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Text = data + case "userId": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.UserID = data + case "uid": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("uid")) + data, err := ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + it.UID = data + } + } + + return it, nil +} + +// endregion **************************** input.gotpl ***************************** + +// region ************************** interface.gotpl *************************** + +// endregion ************************** interface.gotpl *************************** + +// region **************************** object.gotpl **************************** + +var mutationImplementors = []string{"Mutation"} + +func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mutationImplementors) + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ + Object: "Mutation", + }) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + innerCtx := graphql.WithRootFieldContext(ctx, &graphql.RootFieldContext{ + Object: field.Name, + Field: field, + }) + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Mutation") + case "createTodo": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createTodo(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var queryImplementors = []string{"Query"} + +func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, queryImplementors) + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ + Object: "Query", + }) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + innerCtx := graphql.WithRootFieldContext(ctx, &graphql.RootFieldContext{ + Object: field.Name, + Field: field, + }) + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Query") + case "todos": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_todos(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "__type": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Query___type(ctx, field) + }) + case "__schema": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Query___schema(ctx, field) + }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var todoImplementors = []string{"Todo"} + +func (ec *executionContext) _Todo(ctx context.Context, sel ast.SelectionSet, obj *model.Todo) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, todoImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Todo") + case "id": + out.Values[i] = ec._Todo_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "text": + out.Values[i] = ec._Todo_text(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "done": + out.Values[i] = ec._Todo_done(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "uid": + out.Values[i] = ec._Todo_uid(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __DirectiveImplementors = []string{"__Directive"} + +func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __DirectiveImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Directive") + case "name": + out.Values[i] = ec.___Directive_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___Directive_description(ctx, field, obj) + case "locations": + out.Values[i] = ec.___Directive_locations(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "args": + out.Values[i] = ec.___Directive_args(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "isRepeatable": + out.Values[i] = ec.___Directive_isRepeatable(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __EnumValueImplementors = []string{"__EnumValue"} + +func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.EnumValue) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __EnumValueImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__EnumValue") + case "name": + out.Values[i] = ec.___EnumValue_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___EnumValue_description(ctx, field, obj) + case "isDeprecated": + out.Values[i] = ec.___EnumValue_isDeprecated(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deprecationReason": + out.Values[i] = ec.___EnumValue_deprecationReason(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __FieldImplementors = []string{"__Field"} + +func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, obj *introspection.Field) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __FieldImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Field") + case "name": + out.Values[i] = ec.___Field_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___Field_description(ctx, field, obj) + case "args": + out.Values[i] = ec.___Field_args(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "type": + out.Values[i] = ec.___Field_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "isDeprecated": + out.Values[i] = ec.___Field_isDeprecated(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deprecationReason": + out.Values[i] = ec.___Field_deprecationReason(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __InputValueImplementors = []string{"__InputValue"} + +func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.InputValue) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __InputValueImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__InputValue") + case "name": + out.Values[i] = ec.___InputValue_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___InputValue_description(ctx, field, obj) + case "type": + out.Values[i] = ec.___InputValue_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "defaultValue": + out.Values[i] = ec.___InputValue_defaultValue(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __SchemaImplementors = []string{"__Schema"} + +func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, obj *introspection.Schema) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __SchemaImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Schema") + case "description": + out.Values[i] = ec.___Schema_description(ctx, field, obj) + case "types": + out.Values[i] = ec.___Schema_types(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "queryType": + out.Values[i] = ec.___Schema_queryType(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "mutationType": + out.Values[i] = ec.___Schema_mutationType(ctx, field, obj) + case "subscriptionType": + out.Values[i] = ec.___Schema_subscriptionType(ctx, field, obj) + case "directives": + out.Values[i] = ec.___Schema_directives(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __TypeImplementors = []string{"__Type"} + +func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, obj *introspection.Type) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __TypeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Type") + case "kind": + out.Values[i] = ec.___Type_kind(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "name": + out.Values[i] = ec.___Type_name(ctx, field, obj) + case "description": + out.Values[i] = ec.___Type_description(ctx, field, obj) + case "fields": + out.Values[i] = ec.___Type_fields(ctx, field, obj) + case "interfaces": + out.Values[i] = ec.___Type_interfaces(ctx, field, obj) + case "possibleTypes": + out.Values[i] = ec.___Type_possibleTypes(ctx, field, obj) + case "enumValues": + out.Values[i] = ec.___Type_enumValues(ctx, field, obj) + case "inputFields": + out.Values[i] = ec.___Type_inputFields(ctx, field, obj) + case "ofType": + out.Values[i] = ec.___Type_ofType(ctx, field, obj) + case "specifiedByURL": + out.Values[i] = ec.___Type_specifiedByURL(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +// endregion **************************** object.gotpl **************************** + +// region ***************************** type.gotpl ***************************** + +func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v interface{}) (bool, error) { + res, err := graphql.UnmarshalBoolean(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { + res := graphql.MarshalBoolean(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalID(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalID(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNNewTodo2githubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐNewTodo(ctx context.Context, v interface{}) (model.NewTodo, error) { + res, err := ec.unmarshalInputNewTodo(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) marshalNTodo2githubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐTodo(ctx context.Context, sel ast.SelectionSet, v model.Todo) graphql.Marshaler { + return ec._Todo(ctx, sel, &v) +} + +func (ec *executionContext) marshalNTodo2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐTodoᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Todo) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNTodo2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐTodo(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNTodo2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋ_examplesᚋuuidᚋgraphᚋmodelᚐTodo(ctx context.Context, sel ast.SelectionSet, v *model.Todo) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Todo(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx context.Context, v interface{}) (uuid.UUID, error) { + res, err := graphql.UnmarshalUUID(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx context.Context, sel ast.SelectionSet, v uuid.UUID) graphql.Marshaler { + res := graphql.MarshalUUID(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler { + return ec.___Directive(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__Directive2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirectiveᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Directive) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalN__DirectiveLocation2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalN__DirectiveLocation2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalN__DirectiveLocation2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalN__DirectiveLocation2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalN__DirectiveLocation2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__DirectiveLocation2string(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalN__EnumValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValue(ctx context.Context, sel ast.SelectionSet, v introspection.EnumValue) graphql.Marshaler { + return ec.___EnumValue(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__Field2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐField(ctx context.Context, sel ast.SelectionSet, v introspection.Field) graphql.Marshaler { + return ec.___Field(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__InputValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx context.Context, sel ast.SelectionSet, v introspection.InputValue) graphql.Marshaler { + return ec.___InputValue(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.InputValue) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__InputValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalN__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v introspection.Type) graphql.Marshaler { + return ec.___Type(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Type) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec.___Type(ctx, sel, v) +} + +func (ec *executionContext) unmarshalN__TypeKind2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { + res, err := graphql.UnmarshalBoolean(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { + res := graphql.MarshalBoolean(v) + return res +} + +func (ec *executionContext) unmarshalOBoolean2ᚖbool(ctx context.Context, v interface{}) (*bool, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalBoolean(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast.SelectionSet, v *bool) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalBoolean(*v) + return res +} + +func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalString(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(*v) + return res +} + +func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__EnumValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValue(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__Field2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐFieldᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Field) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Field2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐField(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.InputValue) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__InputValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx context.Context, sel ast.SelectionSet, v *introspection.Schema) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec.___Schema(ctx, sel, v) +} + +func (ec *executionContext) marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Type) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec.___Type(ctx, sel, v) +} + +// endregion ***************************** type.gotpl ***************************** diff --git a/_examples/uuid/graph/model/models_gen.go b/_examples/uuid/graph/model/models_gen.go new file mode 100644 index 0000000000..a4dc0a3239 --- /dev/null +++ b/_examples/uuid/graph/model/models_gen.go @@ -0,0 +1,20 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package model + +import ( + "github.com/google/uuid" +) + +type NewTodo struct { + Text string `json:"text"` + UserID string `json:"userId"` + UID uuid.UUID `json:"uid"` +} + +type Todo struct { + ID string `json:"id"` + Text string `json:"text"` + Done bool `json:"done"` + UID uuid.UUID `json:"uid"` +} diff --git a/_examples/uuid/graph/resolver.go b/_examples/uuid/graph/resolver.go new file mode 100644 index 0000000000..a25c09c619 --- /dev/null +++ b/_examples/uuid/graph/resolver.go @@ -0,0 +1,7 @@ +package graph + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type Resolver struct{} diff --git a/_examples/uuid/graph/schema.graphqls b/_examples/uuid/graph/schema.graphqls new file mode 100644 index 0000000000..074282574f --- /dev/null +++ b/_examples/uuid/graph/schema.graphqls @@ -0,0 +1,27 @@ +# GraphQL schema example +# +# https://gqlgen.com/getting-started/ + +type Todo { + id: ID! + text: String! + done: Boolean! + uid: UUID! +} + + +type Query { + todos: [Todo!]! +} + +input NewTodo { + text: String! + userId: String! + uid: UUID! +} + +type Mutation { + createTodo(input: NewTodo!): Todo! +} + +scalar UUID diff --git a/_examples/uuid/graph/schema.resolvers.go b/_examples/uuid/graph/schema.resolvers.go new file mode 100644 index 0000000000..1fd92e5290 --- /dev/null +++ b/_examples/uuid/graph/schema.resolvers.go @@ -0,0 +1,39 @@ +package graph + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.36 + +import ( + "context" + + "github.com/99designs/gqlgen/_examples/uuid/graph/model" + "github.com/google/uuid" +) + +// CreateTodo is the resolver for the createTodo field. +func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { + return &model.Todo{ + ID: input.UserID, + Text: input.Text, + Done: true, + UID: input.UID, + }, nil +} + +// Todos is the resolver for the todos field. +func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { + return []*model.Todo{ + {ID: "1", Text: "hello", Done: true, UID: uuid.New()}, + {ID: "2", Text: "world", Done: false, UID: uuid.New()}, + }, nil +} + +// Mutation returns MutationResolver implementation. +func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } + +// Query returns QueryResolver implementation. +func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } + +type mutationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } diff --git a/_examples/uuid/server.go b/_examples/uuid/server.go new file mode 100644 index 0000000000..cb10a9dfa8 --- /dev/null +++ b/_examples/uuid/server.go @@ -0,0 +1,28 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/99designs/gqlgen/_examples/uuid/graph" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/playground" +) + +const defaultPort = "8080" + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = defaultPort + } + + srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}})) + + http.Handle("/", playground.Handler("GraphQL playground", "/query")) + http.Handle("/query", srv) + + log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +} diff --git a/_examples/websocket-initfunc/server/go.mod b/_examples/websocket-initfunc/server/go.mod index 494e3cbf33..20722874ba 100644 --- a/_examples/websocket-initfunc/server/go.mod +++ b/_examples/websocket-initfunc/server/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-chi/chi v1.5.4 github.com/gorilla/websocket v1.5.0 github.com/rs/cors v1.9.0 - github.com/vektah/gqlparser/v2 v2.5.7 + github.com/vektah/gqlparser/v2 v2.5.10 ) require ( diff --git a/_examples/websocket-initfunc/server/go.sum b/_examples/websocket-initfunc/server/go.sum index 6be6085a3d..352a0a6587 100644 --- a/_examples/websocket-initfunc/server/go.sum +++ b/_examples/websocket-initfunc/server/go.sum @@ -3,12 +3,9 @@ github.com/99designs/gqlgen v0.17.34/go.mod h1:Axcd3jIFHBVcqzixujJQr1wGqE+lGTpz6 github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= @@ -17,25 +14,14 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.3 h1:kmRrRLlInXvng0SmLxmQpQkpbYAvcXm7NPDrgxJa9mE= github.com/hashicorp/golang-lru/v2 v2.0.3/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/vektah/gqlparser/v2 v2.5.7 h1:QnW4lWFSaycZ1jqvVaQ/tDXGGzQfqAuWdyC4S9g/KVM= -github.com/vektah/gqlparser/v2 v2.5.7/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU= +github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/_examples/websocket-initfunc/server/server.go b/_examples/websocket-initfunc/server/server.go index 4704708dab..7027e9728e 100644 --- a/_examples/websocket-initfunc/server/server.go +++ b/_examples/websocket-initfunc/server/server.go @@ -8,22 +8,23 @@ import ( "os" "time" - "github.com/99designs/gqlgen/graphql/handler" - "github.com/99designs/gqlgen/graphql/handler/extension" - "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/99designs/gqlgen/graphql/playground" "github.com/go-chi/chi" "github.com/gorilla/websocket" "github.com/gqlgen/_examples/websocket-initfunc/server/graph" "github.com/rs/cors" + + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/99designs/gqlgen/graphql/playground" ) -func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { +func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) { // Get the token from payload payload := initPayload["authToken"] token, ok := payload.(string) if !ok || token == "" { - return nil, errors.New("authToken not found in transport payload") + return nil, nil, errors.New("authToken not found in transport payload") } // Perform token verification and authentication... @@ -32,7 +33,7 @@ func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (cont // put it in context ctxNew := context.WithValue(ctx, "username", userId) - return ctxNew, nil + return ctxNew, nil, nil } const defaultPort = "8080" @@ -62,7 +63,7 @@ func main() { return true }, }, - InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { + InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (*transport.InitPayload, context.Context, error) { return webSocketInit(ctx, initPayload) }, }) diff --git a/api/generate_test.go b/api/generate_test.go index 417e6f3060..7778f95343 100644 --- a/api/generate_test.go +++ b/api/generate_test.go @@ -5,8 +5,9 @@ import ( "path" "testing" - "github.com/99designs/gqlgen/codegen/config" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/codegen/config" ) func cleanup(workDir string) { diff --git a/api/option_test.go b/api/option_test.go index 5819c4bdd0..d84da902fd 100644 --- a/api/option_test.go +++ b/api/option_test.go @@ -3,12 +3,13 @@ package api import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/plugin" "github.com/99designs/gqlgen/plugin/federation" "github.com/99designs/gqlgen/plugin/modelgen" "github.com/99designs/gqlgen/plugin/resolvergen" - "github.com/stretchr/testify/require" ) type testPlugin struct{} diff --git a/api/testdata/default/graph/generated.go b/api/testdata/default/graph/generated.go index 386e78e65b..143fa7da3c 100644 --- a/api/testdata/default/graph/generated.go +++ b/api/testdata/default/graph/generated.go @@ -24,6 +24,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -31,6 +32,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -74,12 +76,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -244,14 +250,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphqls" diff --git a/api/testdata/federation2/graph/generated.go b/api/testdata/federation2/graph/generated.go index cc5924b8b1..4e6932e824 100644 --- a/api/testdata/federation2/graph/generated.go +++ b/api/testdata/federation2/graph/generated.go @@ -25,6 +25,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -32,6 +33,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -43,8 +45,6 @@ type ResolverRoot interface { } type DirectiveRoot struct { - ComposeDirective func(ctx context.Context, obj interface{}, next graphql.Resolver, name string) (res interface{}, err error) - InterfaceObject func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) } type ComplexityRoot struct { @@ -82,12 +82,16 @@ type QueryResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -266,14 +270,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema.graphqls" @@ -290,6 +294,7 @@ func sourceData(filename string) string { var sources = []*ast.Source{ {Name: "schema.graphqls", Input: sourceData("schema.graphqls"), BuiltIn: false}, {Name: "../federation/directives.graphql", Input: ` + directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION @@ -310,6 +315,12 @@ var sources = []*ast.Source{ directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION + directive @requiresScopes(scopes: [[federation__Scope!]!]!) on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | SCALAR + | ENUM directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag(name: String!) repeatable on | ARGUMENT_DEFINITION @@ -324,6 +335,7 @@ var sources = []*ast.Source{ | UNION scalar _Any scalar FieldSet + scalar federation__Scope `, BuiltIn: true}, {Name: "../federation/entity.graphql", Input: ` type _Service { @@ -341,21 +353,6 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** -func (ec *executionContext) dir_composeDirective_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 string - if tmp, ok := rawArgs["name"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) - arg0, err = ec.unmarshalNString2string(ctx, tmp) - if err != nil { - return nil, err - } - } - args["name"] = arg0 - return args, nil -} - func (ec *executionContext) field_Mutation_createTodo_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -3843,6 +3840,85 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } +func (ec *executionContext) unmarshalNfederation__Scope2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNfederation__Scope2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNfederation__Scope2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNfederation__Scope2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNfederation__Scope2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNfederation__Scope2string(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalNfederation__Scope2ᚕᚕstringᚄ(ctx context.Context, v interface{}) ([][]string, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([][]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNfederation__Scope2ᚕstringᚄ(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNfederation__Scope2ᚕᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v [][]string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNfederation__Scope2ᚕstringᚄ(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/client/client_test.go b/client/client_test.go index 4755ca5fa8..d7b95dcae2 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -12,9 +12,10 @@ import ( "testing" "time" - "github.com/99designs/gqlgen/client" "github.com/mitchellh/mapstructure" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/client" ) func TestClient(t *testing.T) { diff --git a/client/withfilesoption_test.go b/client/withfilesoption_test.go index dbb7756cd7..054c3622fe 100644 --- a/client/withfilesoption_test.go +++ b/client/withfilesoption_test.go @@ -10,8 +10,9 @@ import ( "strings" "testing" - "github.com/99designs/gqlgen/client" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/client" ) func TestWithFiles(t *testing.T) { diff --git a/codegen/args.go b/codegen/args.go index c4cecea932..2f174332c7 100644 --- a/codegen/args.go +++ b/codegen/args.go @@ -5,9 +5,10 @@ import ( "go/types" "strings" + "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" - "github.com/vektah/gqlparser/v2/ast" ) type ArgSet struct { diff --git a/codegen/config/binder.go b/codegen/config/binder.go index 0483afdbdf..dd25d6f5e6 100644 --- a/codegen/config/binder.go +++ b/codegen/config/binder.go @@ -5,12 +5,12 @@ import ( "fmt" "go/token" "go/types" - "strings" + "github.com/vektah/gqlparser/v2/ast" "golang.org/x/tools/go/packages" + "github.com/99designs/gqlgen/codegen/templates" "github.com/99designs/gqlgen/internal/code" - "github.com/vektah/gqlparser/v2/ast" ) var ErrTypeNotFound = errors.New("unable to find type") @@ -274,6 +274,10 @@ func (ref *TypeReference) IsScalar() bool { return ref.Definition.Kind == ast.Scalar } +func (ref *TypeReference) IsMap() bool { + return ref.GO == MapType +} + func (ref *TypeReference) UniquenessKey() string { nullability := "O" if ref.GQL.NonNull { @@ -285,7 +289,7 @@ func (ref *TypeReference) UniquenessKey() string { // Fix for #896 elemNullability = "ᚄ" } - return nullability + ref.Definition.Name + "2" + TypeIdentifier(ref.GO) + elemNullability + return nullability + ref.Definition.Name + "2" + templates.TypeIdentifier(ref.GO) + elemNullability } func (ref *TypeReference) MarshalFunc() string { @@ -540,41 +544,3 @@ func basicUnderlying(it types.Type) *types.Basic { return nil } - -var pkgReplacer = strings.NewReplacer( - "/", "ᚋ", - ".", "ᚗ", - "-", "ᚑ", - "~", "א", -) - -func TypeIdentifier(t types.Type) string { - res := "" - for { - switch it := t.(type) { - case *types.Pointer: - t.Underlying() - res += "ᚖ" - t = it.Elem() - case *types.Slice: - res += "ᚕ" - t = it.Elem() - case *types.Named: - res += pkgReplacer.Replace(it.Obj().Pkg().Path()) - res += "ᚐ" - res += it.Obj().Name() - return res - case *types.Basic: - res += it.Name() - return res - case *types.Map: - res += "map" - return res - case *types.Interface: - res += "interface" - return res - default: - panic(fmt.Errorf("unexpected type %T", it)) - } - } -} diff --git a/codegen/config/binder_test.go b/codegen/config/binder_test.go index fed3913adb..f436a8efe3 100644 --- a/codegen/config/binder_test.go +++ b/codegen/config/binder_test.go @@ -4,11 +4,11 @@ import ( "go/types" "testing" - "github.com/99designs/gqlgen/internal/code" - "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/internal/code" ) func TestBindingToInvalid(t *testing.T) { @@ -187,7 +187,7 @@ func createBinder(cfg Config) (*Binder, *ast.Schema) { Model: []string{"github.com/99designs/gqlgen/graphql.String"}, }, } - cfg.Packages = &code.Packages{} + cfg.Packages = code.NewPackages() cfg.Schema = gqlparser.MustLoadSchema(&ast.Source{Name: "TestAutobinding.schema", Input: ` type Message { id: ID } diff --git a/codegen/config/config.go b/codegen/config/config.go index 8a7205f06b..50ccaeffda 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -3,6 +3,7 @@ package config import ( "bytes" "fmt" + "go/types" "io" "os" "path/filepath" @@ -10,10 +11,13 @@ import ( "sort" "strings" - "github.com/99designs/gqlgen/internal/code" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + "golang.org/x/tools/go/packages" "gopkg.in/yaml.v3" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/internal/code" ) type Config struct { @@ -26,6 +30,7 @@ type Config struct { Models TypeMap `yaml:"models,omitempty"` StructTag string `yaml:"struct_tag,omitempty"` Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` + GoBuildTags StringList `yaml:"go_build_tags,omitempty"` GoInitialisms GoInitialismsConfig `yaml:"go_initialisms,omitempty"` OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"` OmitGetters bool `yaml:"omit_getters,omitempty"` @@ -211,7 +216,9 @@ func CompleteConfig(config *Config) error { func (c *Config) Init() error { if c.Packages == nil { - c.Packages = &code.Packages{} + c.Packages = code.NewPackages( + code.WithBuildTags(c.GoBuildTags...), + ) } if c.Schema == nil { @@ -281,6 +288,7 @@ func (c *Config) injectTypesFromSchema() error { c.Models.Add(schemaType.Name, mv.(string)) } } + if ma := bd.Arguments.ForName("models"); ma != nil { if mvs, err := ma.Value.Value(nil); err == nil { for _, mv := range mvs.([]interface{}) { @@ -288,6 +296,12 @@ func (c *Config) injectTypesFromSchema() error { } } } + + if fg := bd.Arguments.ForName("forceGenerate"); fg != nil { + if mv, err := fg.Value.Value(nil); err == nil { + c.Models.ForceGenerate(schemaType.Name, mv.(bool)) + } + } } if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject { @@ -329,8 +343,9 @@ func (c *Config) injectTypesFromSchema() error { } type TypeMapEntry struct { - Model StringList `yaml:"model"` - Fields map[string]TypeMapField `yaml:"fields,omitempty"` + Model StringList `yaml:"model,omitempty"` + ForceGenerate bool `yaml:"forceGenerate,omitempty"` + Fields map[string]TypeMapField `yaml:"fields,omitempty"` // Key is the Go name of the field. ExtraFields map[string]ModelExtraField `yaml:"extraFields,omitempty"` @@ -529,6 +544,12 @@ func (tm TypeMap) Add(name string, goType string) { tm[name] = modelCfg } +func (tm TypeMap) ForceGenerate(name string, forceGenerate bool) { + modelCfg := tm[name] + modelCfg.ForceGenerate = forceGenerate + tm[name] = modelCfg +} + type DirectiveConfig struct { SkipRuntime bool `yaml:"skip_runtime"` } @@ -583,7 +604,7 @@ func (c *Config) autobind() error { ps := c.Packages.LoadAll(c.AutoBind...) for _, t := range c.Schema.Types { - if c.Models.UserDefined(t.Name) { + if c.Models.UserDefined(t.Name) || c.Models[t.Name].ForceGenerate { continue } @@ -591,14 +612,20 @@ func (c *Config) autobind() error { if p == nil || p.Module == nil { return fmt.Errorf("unable to load %s - make sure you're using an import path to a package that exists", c.AutoBind[i]) } - if t := p.Types.Scope().Lookup(t.Name); t != nil { - c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name()) + + autobindType := c.lookupAutobindType(p, t) + if autobindType != nil { + c.Models.Add(t.Name, autobindType.Pkg().Path()+"."+autobindType.Name()) break } } } for i, t := range c.Models { + if t.ForceGenerate { + continue + } + for j, m := range t.Model { pkg, typename := code.PkgAndType(m) @@ -622,6 +649,17 @@ func (c *Config) autobind() error { return nil } +func (c *Config) lookupAutobindType(p *packages.Package, schemaType *ast.Definition) types.Object { + // Try binding to either the original schema type name, or the normalized go type name + for _, lookupName := range []string{schemaType.Name, templates.ToGo(schemaType.Name)} { + if t := p.Types.Scope().Lookup(lookupName); t != nil { + return t + } + } + + return nil +} + func (c *Config) injectBuiltins() { builtins := TypeMap{ "__Directive": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}}, @@ -671,7 +709,9 @@ func (c *Config) injectBuiltins() { func (c *Config) LoadSchema() error { if c.Packages != nil { - c.Packages = &code.Packages{} + c.Packages = code.NewPackages( + code.WithBuildTags(c.GoBuildTags...), + ) } if err := c.check(); err != nil { diff --git a/codegen/config/config_test.go b/codegen/config/config_test.go index 2ed5d5ac86..c8cb42ea3a 100644 --- a/codegen/config/config_test.go +++ b/codegen/config/config_test.go @@ -192,7 +192,7 @@ func TestAutobinding(t *testing.T) { "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat", "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/scalars/model", }, - Packages: &code.Packages{}, + Packages: code.NewPackages(), } cfg.Schema = gqlparser.MustLoadSchema(&ast.Source{Name: "TestAutobinding.schema", Input: ` @@ -206,13 +206,38 @@ func TestAutobinding(t *testing.T) { require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.Message", cfg.Models["Message"].Model[0]) }) + t.Run("normalized type names", func(t *testing.T) { + cfg := Config{ + Models: TypeMap{}, + AutoBind: []string{ + "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat", + "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/scalars/model", + }, + Packages: code.NewPackages(), + } + + cfg.Schema = gqlparser.MustLoadSchema(&ast.Source{Name: "TestAutobinding.schema", Input: ` + scalar Banned + type Message { id: ID } + enum ProductSKU { ProductSkuTrial } + type ChatAPI { id: ID } + `}) + + require.NoError(t, cfg.autobind()) + + require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/scalars/model.Banned", cfg.Models["Banned"].Model[0]) + require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.Message", cfg.Models["Message"].Model[0]) + require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.ProductSku", cfg.Models["ProductSKU"].Model[0]) + require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.ChatAPI", cfg.Models["ChatAPI"].Model[0]) + }) + t.Run("with file path", func(t *testing.T) { cfg := Config{ Models: TypeMap{}, AutoBind: []string{ "../chat", }, - Packages: &code.Packages{}, + Packages: code.NewPackages(), } cfg.Schema = gqlparser.MustLoadSchema(&ast.Source{Name: "TestAutobinding.schema", Input: ` diff --git a/codegen/config/initialisms.go b/codegen/config/initialisms.go index 5c169c8900..25e7331f8a 100644 --- a/codegen/config/initialisms.go +++ b/codegen/config/initialisms.go @@ -1,62 +1,10 @@ package config -import "strings" +import ( + "strings" -// commonInitialisms is a set of common initialisms. -// Only add entries that are highly unlikely to be non-initialisms. -// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. -var commonInitialisms = map[string]bool{ - "ACL": true, - "API": true, - "ASCII": true, - "CPU": true, - "CSS": true, - "CSV": true, - "DNS": true, - "EOF": true, - "GUID": true, - "HTML": true, - "HTTP": true, - "HTTPS": true, - "ICMP": true, - "ID": true, - "IP": true, - "JSON": true, - "KVK": true, - "LHS": true, - "PDF": true, - "PGP": true, - "QPS": true, - "QR": true, - "RAM": true, - "RHS": true, - "RPC": true, - "SLA": true, - "SMTP": true, - "SQL": true, - "SSH": true, - "SVG": true, - "TCP": true, - "TLS": true, - "TTL": true, - "UDP": true, - "UI": true, - "UID": true, - "URI": true, - "URL": true, - "UTF8": true, - "UUID": true, - "VM": true, - "XML": true, - "XMPP": true, - "XSRF": true, - "XSS": true, -} - -// GetInitialisms returns the initialisms to capitalize in Go names. If unchanged, default initialisms will be returned -var GetInitialisms = func() map[string]bool { - return commonInitialisms -} + "github.com/99designs/gqlgen/codegen/templates" +) // GoInitialismsConfig allows to modify the default behavior of naming Go methods, types and properties type GoInitialismsConfig struct { @@ -69,7 +17,7 @@ type GoInitialismsConfig struct { // setInitialisms adjustes GetInitialisms based on its settings. func (i GoInitialismsConfig) setInitialisms() { toUse := i.determineGoInitialisms() - GetInitialisms = func() map[string]bool { + templates.GetInitialisms = func() map[string]bool { return toUse } } @@ -82,8 +30,8 @@ func (i GoInitialismsConfig) determineGoInitialisms() (initialismsToUse map[stri initialismsToUse[strings.ToUpper(initialism)] = true } } else { - initialismsToUse = make(map[string]bool, len(commonInitialisms)+len(i.Initialisms)) - for initialism, value := range commonInitialisms { + initialismsToUse = make(map[string]bool, len(templates.CommonInitialisms)+len(i.Initialisms)) + for initialism, value := range templates.CommonInitialisms { initialismsToUse[strings.ToUpper(initialism)] = value } for _, initialism := range i.Initialisms { diff --git a/codegen/config/initialisms_test.go b/codegen/config/initialisms_test.go index 5bea561a3a..13c0da2465 100644 --- a/codegen/config/initialisms_test.go +++ b/codegen/config/initialisms_test.go @@ -5,6 +5,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/codegen/templates" ) func TestGoInitialismsConfig(t *testing.T) { @@ -17,12 +19,12 @@ func TestGoInitialismsConfig(t *testing.T) { t.Run("empty initialism config doesn't change anything", func(t *testing.T) { tt := GoInitialismsConfig{} result := tt.determineGoInitialisms() - assert.Equal(t, len(commonInitialisms), len(result)) + assert.Equal(t, len(templates.CommonInitialisms), len(result)) }) t.Run("initialism config appends if desired", func(t *testing.T) { tt := GoInitialismsConfig{ReplaceDefaults: false, Initialisms: []string{"ASDF"}} result := tt.determineGoInitialisms() - assert.Equal(t, len(commonInitialisms)+1, len(result)) + assert.Equal(t, len(templates.CommonInitialisms)+1, len(result)) assert.True(t, result["ASDF"]) }) t.Run("initialism config replaces if desired", func(t *testing.T) { diff --git a/codegen/config/package.go b/codegen/config/package.go index faacd1496f..37692ece7e 100644 --- a/codegen/config/package.go +++ b/codegen/config/package.go @@ -10,9 +10,10 @@ import ( ) type PackageConfig struct { - Filename string `yaml:"filename,omitempty"` - Package string `yaml:"package,omitempty"` - Version int `yaml:"version,omitempty"` + Filename string `yaml:"filename,omitempty"` + Package string `yaml:"package,omitempty"` + Version int `yaml:"version,omitempty"` + ModelTemplate string `yaml:"model_template,omitempty"` } func (c *PackageConfig) ImportPath() string { diff --git a/codegen/config/resolver.go b/codegen/config/resolver.go index a3ec38f8e2..cb5fb72b36 100644 --- a/codegen/config/resolver.go +++ b/codegen/config/resolver.go @@ -17,6 +17,7 @@ type ResolverConfig struct { Layout ResolverLayout `yaml:"layout,omitempty"` DirName string `yaml:"dir"` OmitTemplateComment bool `yaml:"omit_template_comment,omitempty"` + ResolverTemplate string `yaml:"resolver_template,omitempty"` } type ResolverLayout string diff --git a/codegen/config/testdata/autobinding/chat/message.go b/codegen/config/testdata/autobinding/chat/model.go similarity index 62% rename from codegen/config/testdata/autobinding/chat/message.go rename to codegen/config/testdata/autobinding/chat/model.go index b35be48c93..fa621ee90a 100644 --- a/codegen/config/testdata/autobinding/chat/message.go +++ b/codegen/config/testdata/autobinding/chat/model.go @@ -10,3 +10,13 @@ type Message struct { CreatedBy string `json:"createdBy"` CreatedAt time.Time `json:"createdAt"` } + +type ProductSku string + +const ( + ProductSkuTrial ProductSku = "Trial" +) + +type ChatAPI struct { + ID string `json:"id"` +} diff --git a/codegen/data_test.go b/codegen/data_test.go index a1ac4d3df9..64ca8c621f 100644 --- a/codegen/data_test.go +++ b/codegen/data_test.go @@ -3,10 +3,10 @@ package codegen import ( "testing" - "github.com/99designs/gqlgen/codegen/config" + "github.com/stretchr/testify/assert" "github.com/vektah/gqlparser/v2/ast" - "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/codegen/config" ) func TestData_Directives(t *testing.T) { diff --git a/codegen/directive.go b/codegen/directive.go index 973061129a..f955665be8 100644 --- a/codegen/directive.go +++ b/codegen/directive.go @@ -5,8 +5,9 @@ import ( "strconv" "strings" - "github.com/99designs/gqlgen/codegen/templates" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/codegen/templates" ) type DirectiveList map[string]*Directive diff --git a/codegen/field.go b/codegen/field.go index a7e1fb2ba6..6d11538a02 100644 --- a/codegen/field.go +++ b/codegen/field.go @@ -10,11 +10,12 @@ import ( "strconv" "strings" - "github.com/99designs/gqlgen/codegen/config" - "github.com/99designs/gqlgen/codegen/templates" "github.com/vektah/gqlparser/v2/ast" "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/templates" ) type Field struct { diff --git a/codegen/field_test.go b/codegen/field_test.go index 642d2d3f5c..0d5eb16216 100644 --- a/codegen/field_test.go +++ b/codegen/field_test.go @@ -8,9 +8,10 @@ import ( "go/types" "testing" - "github.com/99designs/gqlgen/codegen/config" "github.com/stretchr/testify/require" ast2 "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/codegen/config" ) func TestFindField(t *testing.T) { diff --git a/codegen/generate.go b/codegen/generate.go index 1ce8c329dc..d63758abf1 100644 --- a/codegen/generate.go +++ b/codegen/generate.go @@ -9,9 +9,10 @@ import ( "runtime" "strings" + "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" - "github.com/vektah/gqlparser/v2/ast" ) //go:embed *.gotpl diff --git a/codegen/generated!.gotpl b/codegen/generated!.gotpl index ae1da002e8..eb3baf7054 100644 --- a/codegen/generated!.gotpl +++ b/codegen/generated!.gotpl @@ -18,6 +18,7 @@ // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -25,6 +26,7 @@ } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -93,6 +95,7 @@ {{ if eq .Config.Exec.Layout "single-file" }} type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot @@ -112,6 +115,10 @@ func (e *executableSchema) Schema() *ast.Schema { return gqlparser.MustLoadSchema(e.sources...) + // if e.schema != nil { + // return e.schema + // } + // return parsedSchema } func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) { @@ -280,14 +287,14 @@ if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } {{if .HasEmbeddableSources }} diff --git a/codegen/input.gotpl b/codegen/input.gotpl index dcd125399a..a30c28d9a0 100644 --- a/codegen/input.gotpl +++ b/codegen/input.gotpl @@ -5,7 +5,11 @@ {{- $it = "&it" }} {{- end }} func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{ if .PointersInUmarshalInput }}*{{ end }}{{.Type | ref}}, error) { - var it {{.Type | ref}} + {{- if $input.IsMap }} + it := make(map[string]interface{}, len(obj.(map[string]interface{}))) + {{- else }} + var it {{.Type | ref}} + {{- end }} asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v @@ -29,6 +33,11 @@ case {{$field.Name|quote}}: var err error + {{- $lhs := (printf "it.%s" $field.GoFieldName) }} + {{- if $input.IsMap }} + {{- $lhs = (printf "it[%q]" $field.Name) }} + {{- end }} + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField({{$field.Name|quote}})) {{- if $field.ImplDirectives }} directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) } @@ -44,18 +53,18 @@ } {{- else }} {{- if $field.TypeReference.IsOmittable }} - it.{{$field.GoFieldName}} = graphql.OmittableOf(data) + {{ $lhs }} = graphql.OmittableOf(data) {{- else }} - it.{{$field.GoFieldName}} = data + {{ $lhs }} = data {{- end }} {{- end }} {{- if $field.TypeReference.IsNilable }} {{- if not $field.IsResolver }} } else if tmp == nil { {{- if $field.TypeReference.IsOmittable }} - it.{{$field.GoFieldName}} = graphql.OmittableOf[{{ $field.TypeReference.GO | ref }}](nil) + {{ $lhs }} = graphql.OmittableOf[{{ $field.TypeReference.GO | ref }}](nil) {{- else }} - it.{{$field.GoFieldName}} = nil + {{ $lhs }} = nil {{- end }} {{- end }} {{- end }} @@ -78,9 +87,9 @@ return {{$it}}, err } {{- if $field.TypeReference.IsOmittable }} - it.{{$field.GoFieldName}} = graphql.OmittableOf(data) + {{ $lhs }} = graphql.OmittableOf(data) {{- else }} - it.{{$field.GoFieldName}} = data + {{ $lhs }} = data {{- end }} {{- end }} {{- end }} diff --git a/codegen/object.go b/codegen/object.go index 8609232719..97575f30a8 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -7,10 +7,11 @@ import ( "strings" "unicode" - "github.com/99designs/gqlgen/codegen/config" "github.com/vektah/gqlparser/v2/ast" "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/99designs/gqlgen/codegen/config" ) type GoFieldType int @@ -112,8 +113,8 @@ func (o *Object) HasResolvers() bool { } func (o *Object) HasUnmarshal() bool { - if o.Type == config.MapType { - return true + if o.IsMap() { + return false } for i := 0; i < o.Type.(*types.Named).NumMethods(); i++ { if o.Type.(*types.Named).Method(i).Name() == "UnmarshalGQL" { @@ -149,6 +150,10 @@ func (o *Object) IsReserved() bool { return strings.HasPrefix(o.Definition.Name, "__") } +func (o *Object) IsMap() bool { + return o.Type == config.MapType +} + func (o *Object) Description() string { return o.Definition.Description } diff --git a/codegen/root_.gotpl b/codegen/root_.gotpl index e1cfa1cf0a..6429e97d80 100644 --- a/codegen/root_.gotpl +++ b/codegen/root_.gotpl @@ -17,6 +17,7 @@ // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -25,6 +26,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -67,6 +69,7 @@ type ComplexityRoot struct { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot @@ -84,6 +87,10 @@ func (es *executableSchema) afterUnmarshalInput(ctx context.Context, obj interfa } func (e *executableSchema) Schema() *ast.Schema { return gqlparser.MustLoadSchema(e.sources...) + // if e.schema != nil { + // return e.schema + // } + // return parsedSchema } func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) { @@ -252,14 +259,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } diff --git a/codegen/templates/import_test.go b/codegen/templates/import_test.go index baf88b50ab..d709f72cc9 100644 --- a/codegen/templates/import_test.go +++ b/codegen/templates/import_test.go @@ -6,9 +6,9 @@ import ( "os" "testing" - "github.com/99designs/gqlgen/internal/code" - "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/internal/code" ) func TestImports(t *testing.T) { @@ -20,14 +20,14 @@ func TestImports(t *testing.T) { mismatch := "github.com/99designs/gqlgen/codegen/templates/testdata/pkg_mismatch" t.Run("multiple lookups is ok", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} require.Equal(t, "bar", a.Lookup(aBar)) require.Equal(t, "bar", a.Lookup(aBar)) }) t.Run("lookup by type", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} pkg := types.NewPackage("github.com/99designs/gqlgen/codegen/templates/testdata/b/bar", "bar") typ := types.NewNamed(types.NewTypeName(0, pkg, "Boolean", types.Typ[types.Bool]), types.Typ[types.Bool], nil) @@ -36,7 +36,7 @@ func TestImports(t *testing.T) { }) t.Run("duplicates are decollisioned", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} require.Equal(t, "bar", a.Lookup(aBar)) require.Equal(t, "bar1", a.Lookup(bBar)) @@ -47,7 +47,7 @@ func TestImports(t *testing.T) { }) t.Run("duplicates above 10 are decollisioned", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} for i := 0; i < 100; i++ { cBar := fmt.Sprintf("github.com/99designs/gqlgen/codegen/templates/testdata/%d/bar", i) if i > 0 { @@ -59,13 +59,13 @@ func TestImports(t *testing.T) { }) t.Run("package name defined in code will be used", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} require.Equal(t, "turtles", a.Lookup(mismatch)) }) t.Run("string printing for import block", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} a.Lookup(aBar) a.Lookup(bBar) a.Lookup(mismatch) @@ -80,7 +80,7 @@ turtles "github.com/99designs/gqlgen/codegen/templates/testdata/pkg_mismatch"`, }) t.Run("aliased imports will not collide", func(t *testing.T) { - a := Imports{destDir: wd, packages: &code.Packages{}} + a := Imports{destDir: wd, packages: code.NewPackages()} _, _ = a.Reserve(aBar, "abar") _, _ = a.Reserve(bBar, "bbar") diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 332498f878..4a4e91594e 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -17,7 +17,6 @@ import ( "text/template" "unicode" - "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/internal/code" "github.com/99designs/gqlgen/internal/imports" ) @@ -172,7 +171,7 @@ func parseTemplates(cfg Options, t *template.Template) (*template.Template, erro fileSystem = cfg.TemplateFS } else { // load path relative to calling source file - _, callerFile, _, _ := runtime.Caller(1) + _, callerFile, _, _ := runtime.Caller(2) rootDir := filepath.Dir(callerFile) fileSystem = os.DirFS(rootDir) } @@ -202,7 +201,7 @@ func Funcs() template.FuncMap { "rawQuote": rawQuote, "dump": Dump, "ref": ref, - "ts": config.TypeIdentifier, + "ts": TypeIdentifier, "call": Call, "prefixLines": prefixLines, "notNil": notNil, @@ -465,7 +464,7 @@ func wordWalker(str string, f func(*wordInfo)) { } i++ - initialisms := config.GetInitialisms() + initialisms := GetInitialisms() // [w,i) is a word. word := string(runes[w:i]) if !eow && initialisms[word] && !unicode.IsLower(runes[i]) { @@ -668,3 +667,97 @@ func write(filename string, b []byte, packages *code.Packages) error { return nil } + +var pkgReplacer = strings.NewReplacer( + "/", "ᚋ", + ".", "ᚗ", + "-", "ᚑ", + "~", "א", +) + +func TypeIdentifier(t types.Type) string { + res := "" + for { + switch it := t.(type) { + case *types.Pointer: + t.Underlying() + res += "ᚖ" + t = it.Elem() + case *types.Slice: + res += "ᚕ" + t = it.Elem() + case *types.Named: + res += pkgReplacer.Replace(it.Obj().Pkg().Path()) + res += "ᚐ" + res += it.Obj().Name() + return res + case *types.Basic: + res += it.Name() + return res + case *types.Map: + res += "map" + return res + case *types.Interface: + res += "interface" + return res + default: + panic(fmt.Errorf("unexpected type %T", it)) + } + } +} + +// CommonInitialisms is a set of common initialisms. +// Only add entries that are highly unlikely to be non-initialisms. +// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. +var CommonInitialisms = map[string]bool{ + "ACL": true, + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "CSV": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ICMP": true, + "ID": true, + "IP": true, + "JSON": true, + "KVK": true, + "LHS": true, + "PDF": true, + "PGP": true, + "QPS": true, + "QR": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SQL": true, + "SSH": true, + "SVG": true, + "TCP": true, + "TLS": true, + "TTL": true, + "UDP": true, + "UI": true, + "UID": true, + "URI": true, + "URL": true, + "UTF8": true, + "UUID": true, + "VM": true, + "XML": true, + "XMPP": true, + "XSRF": true, + "XSS": true, +} + +// GetInitialisms returns the initialisms to capitalize in Go names. If unchanged, default initialisms will be returned +var GetInitialisms = func() map[string]bool { + return CommonInitialisms +} diff --git a/codegen/templates/templates_test.go b/codegen/templates/templates_test.go index 2f57fc9ca1..277a979cdb 100644 --- a/codegen/templates/templates_test.go +++ b/codegen/templates/templates_test.go @@ -7,10 +7,10 @@ import ( "path/filepath" "testing" - "github.com/99designs/gqlgen/internal/code" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/internal/code" ) //go:embed *.gotpl @@ -326,7 +326,7 @@ func TestTemplateOverride(t *testing.T) { } defer f.Close() defer os.RemoveAll(f.Name()) - err = Render(Options{Template: "hello", Filename: f.Name(), Packages: &code.Packages{}}) + err = Render(Options{Template: "hello", Filename: f.Name(), Packages: code.NewPackages()}) if err != nil { t.Fatal(err) } @@ -346,7 +346,7 @@ func TestRenderFS(t *testing.T) { } defer f.Close() defer os.RemoveAll(f.Name()) - err = Render(Options{TemplateFS: templateFS, Filename: f.Name(), Packages: &code.Packages{}}) + err = Render(Options{TemplateFS: templateFS, Filename: f.Name(), Packages: code.NewPackages()}) if err != nil { t.Fatal(err) } diff --git a/codegen/testserver/followschema/complexity_test.go b/codegen/testserver/followschema/complexity_test.go index 6a91e0f000..fd69a79c5e 100644 --- a/codegen/testserver/followschema/complexity_test.go +++ b/codegen/testserver/followschema/complexity_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" - "github.com/stretchr/testify/require" ) func TestComplexityCollisions(t *testing.T) { diff --git a/codegen/testserver/followschema/defaults_test.go b/codegen/testserver/followschema/defaults_test.go index e5708a72e3..26adaa1373 100644 --- a/codegen/testserver/followschema/defaults_test.go +++ b/codegen/testserver/followschema/defaults_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func assertDefaults(t *testing.T, ret *DefaultParametersMirror) { diff --git a/codegen/testserver/followschema/directive_test.go b/codegen/testserver/followschema/directive_test.go index e995b2721c..b6d84d6932 100644 --- a/codegen/testserver/followschema/directive_test.go +++ b/codegen/testserver/followschema/directive_test.go @@ -5,10 +5,11 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type ckey string diff --git a/codegen/testserver/followschema/embedded_test.go b/codegen/testserver/followschema/embedded_test.go index b5e1575bac..207ea88182 100644 --- a/codegen/testserver/followschema/embedded_test.go +++ b/codegen/testserver/followschema/embedded_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type fakeUnexportedEmbeddedInterface struct{} diff --git a/codegen/testserver/followschema/enums_test.go b/codegen/testserver/followschema/enums_test.go index e7c80c29d2..c72293f169 100644 --- a/codegen/testserver/followschema/enums_test.go +++ b/codegen/testserver/followschema/enums_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestEnumsResolver(t *testing.T) { diff --git a/codegen/testserver/followschema/fields_order_test.go b/codegen/testserver/followschema/fields_order_test.go index d521c8fc99..2be70d9da5 100644 --- a/codegen/testserver/followschema/fields_order_test.go +++ b/codegen/testserver/followschema/fields_order_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type FieldsOrderPayloadResults struct { diff --git a/codegen/testserver/followschema/generated_test.go b/codegen/testserver/followschema/generated_test.go index 9d10cde2c3..1792b0b4e4 100644 --- a/codegen/testserver/followschema/generated_test.go +++ b/codegen/testserver/followschema/generated_test.go @@ -8,9 +8,10 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestForcedResolverFieldIsPointer(t *testing.T) { diff --git a/codegen/testserver/followschema/input_test.go b/codegen/testserver/followschema/input_test.go index 9a13f2d17f..27f6ada77a 100644 --- a/codegen/testserver/followschema/input_test.go +++ b/codegen/testserver/followschema/input_test.go @@ -6,9 +6,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestInput(t *testing.T) { diff --git a/codegen/testserver/followschema/interfaces_test.go b/codegen/testserver/followschema/interfaces_test.go index 3a7820fe52..2ef7312841 100644 --- a/codegen/testserver/followschema/interfaces_test.go +++ b/codegen/testserver/followschema/interfaces_test.go @@ -6,10 +6,11 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestInterfaces(t *testing.T) { diff --git a/codegen/testserver/followschema/introspection_test.go b/codegen/testserver/followschema/introspection_test.go index ef2bfafb53..2f3fbc30cb 100644 --- a/codegen/testserver/followschema/introspection_test.go +++ b/codegen/testserver/followschema/introspection_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/stretchr/testify/require" ) func TestIntrospection(t *testing.T) { diff --git a/codegen/testserver/followschema/maps.generated.go b/codegen/testserver/followschema/maps.generated.go index 7fcd91fc1f..d2c4e1b885 100644 --- a/codegen/testserver/followschema/maps.generated.go +++ b/codegen/testserver/followschema/maps.generated.go @@ -27,6 +27,47 @@ import ( // region **************************** field.gotpl ***************************** +func (ec *executionContext) _MapNested_value(ctx context.Context, field graphql.CollectedField, obj *MapNested) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MapNested_value(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Value, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(CustomScalar) + fc.Result = res + return ec.marshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_MapNested_value(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MapNested", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CustomScalar does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _MapStringInterfaceType_a(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) (ret graphql.Marshaler) { fc, err := ec.fieldContext_MapStringInterfaceType_a(ctx, field) if err != nil { @@ -121,10 +162,193 @@ func (ec *executionContext) fieldContext_MapStringInterfaceType_b(ctx context.Co return fc, nil } +func (ec *executionContext) _MapStringInterfaceType_c(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MapStringInterfaceType_c(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + switch v := obj["c"].(type) { + case *CustomScalar: + return v, nil + case CustomScalar: + return &v, nil + case nil: + return (*CustomScalar)(nil), nil + default: + return nil, fmt.Errorf("unexpected type %T for field %s", v, "c") + } + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*CustomScalar) + fc.Result = res + return ec.marshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_MapStringInterfaceType_c(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MapStringInterfaceType", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CustomScalar does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MapStringInterfaceType_nested(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MapStringInterfaceType_nested(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + switch v := obj["nested"].(type) { + case *MapNested: + return v, nil + case MapNested: + return &v, nil + case nil: + return (*MapNested)(nil), nil + default: + return nil, fmt.Errorf("unexpected type %T for field %s", v, "nested") + } + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*MapNested) + fc.Result = res + return ec.marshalOMapNested2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐMapNested(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_MapStringInterfaceType_nested(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MapStringInterfaceType", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "value": + return ec.fieldContext_MapNested_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MapNested", field.Name) + }, + } + return fc, nil +} + // endregion **************************** field.gotpl ***************************** // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputMapNestedInput(ctx context.Context, obj interface{}) (MapNested, error) { + var it MapNested + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"value"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "value": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("value")) + data, err := ec.unmarshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx, v) + if err != nil { + return it, err + } + it.Value = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputMapStringInterfaceInput(ctx context.Context, obj interface{}) (map[string]interface{}, error) { + it := make(map[string]interface{}, len(obj.(map[string]interface{}))) + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"a", "b", "c", "nested"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "a": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("a")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it["a"] = data + case "b": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("b")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it["b"] = data + case "c": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("c")) + data, err := ec.unmarshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx, v) + if err != nil { + return it, err + } + it["c"] = data + case "nested": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nested")) + data, err := ec.unmarshalOMapNestedInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐMapNested(ctx, v) + if err != nil { + return it, err + } + it["nested"] = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputNestedMapInput(ctx context.Context, obj interface{}) (NestedMapInput, error) { var it NestedMapInput asMap := map[string]interface{}{} @@ -162,6 +386,45 @@ func (ec *executionContext) unmarshalInputNestedMapInput(ctx context.Context, ob // region **************************** object.gotpl **************************** +var mapNestedImplementors = []string{"MapNested"} + +func (ec *executionContext) _MapNested(ctx context.Context, sel ast.SelectionSet, obj *MapNested) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mapNestedImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MapNested") + case "value": + out.Values[i] = ec._MapNested_value(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var mapStringInterfaceTypeImplementors = []string{"MapStringInterfaceType"} func (ec *executionContext) _MapStringInterfaceType(ctx context.Context, sel ast.SelectionSet, obj map[string]interface{}) graphql.Marshaler { @@ -177,6 +440,10 @@ func (ec *executionContext) _MapStringInterfaceType(ctx context.Context, sel ast out.Values[i] = ec._MapStringInterfaceType_a(ctx, field, obj) case "b": out.Values[i] = ec._MapStringInterfaceType_b(ctx, field, obj) + case "c": + out.Values[i] = ec._MapStringInterfaceType_c(ctx, field, obj) + case "nested": + out.Values[i] = ec._MapStringInterfaceType_nested(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -204,11 +471,53 @@ func (ec *executionContext) _MapStringInterfaceType(ctx context.Context, sel ast // region ***************************** type.gotpl ***************************** +func (ec *executionContext) unmarshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx context.Context, v interface{}) (CustomScalar, error) { + var res CustomScalar + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx context.Context, sel ast.SelectionSet, v CustomScalar) graphql.Marshaler { + return v +} + +func (ec *executionContext) unmarshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx context.Context, v interface{}) (*CustomScalar, error) { + if v == nil { + return nil, nil + } + var res = new(CustomScalar) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐCustomScalar(ctx context.Context, sel ast.SelectionSet, v *CustomScalar) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + +func (ec *executionContext) marshalOMapNested2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐMapNested(ctx context.Context, sel ast.SelectionSet, v *MapNested) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._MapNested(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOMapNestedInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚐMapNested(ctx context.Context, v interface{}) (*MapNested, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMapNestedInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOMapStringInterfaceInput2map(ctx context.Context, v interface{}) (map[string]interface{}, error) { if v == nil { return nil, nil } - return v.(map[string]interface{}), nil + res, err := ec.unmarshalInputMapStringInterfaceInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOMapStringInterfaceType2map(ctx context.Context, sel ast.SelectionSet, v map[string]interface{}) graphql.Marshaler { diff --git a/codegen/testserver/followschema/maps.go b/codegen/testserver/followschema/maps.go new file mode 100644 index 0000000000..871aabf3e6 --- /dev/null +++ b/codegen/testserver/followschema/maps.go @@ -0,0 +1,28 @@ +package followschema + +import ( + "io" + "strconv" +) + +type MapNested struct { + Value CustomScalar +} + +type CustomScalar struct { + value int64 +} + +func (s *CustomScalar) UnmarshalGQL(v interface{}) (err error) { + switch v := v.(type) { + case string: + s.value, err = strconv.ParseInt(v, 10, 64) + case int64: + s.value = v + } + return +} + +func (s CustomScalar) MarshalGQL(w io.Writer) { + _, _ = w.Write([]byte(strconv.Quote(strconv.FormatInt(s.value, 10)))) +} diff --git a/codegen/testserver/followschema/maps.graphql b/codegen/testserver/followschema/maps.graphql index 0fd639b0c4..51eac074ab 100644 --- a/codegen/testserver/followschema/maps.graphql +++ b/codegen/testserver/followschema/maps.graphql @@ -6,13 +6,27 @@ extend type Query { type MapStringInterfaceType @goModel(model: "map[string]interface{}") { a: String b: Int + c: CustomScalar + nested: MapNested +} + +type MapNested @goModel(model: "followschema.MapNested") { + value: CustomScalar! } input MapStringInterfaceInput @goModel(model: "map[string]interface{}") { - a: String + a: String! b: Int + c: CustomScalar + nested: MapNestedInput } +input MapNestedInput @goModel(model: "followschema.MapNested") { + value: CustomScalar! +} + +scalar CustomScalar @goModel(model: "followschema.CustomScalar") + input NestedMapInput { map: MapStringInterfaceInput } diff --git a/codegen/testserver/followschema/maps_test.go b/codegen/testserver/followschema/maps_test.go index fe9750450f..58b559c16a 100644 --- a/codegen/testserver/followschema/maps_test.go +++ b/codegen/testserver/followschema/maps_test.go @@ -4,20 +4,23 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestMaps(t *testing.T) { resolver := &Stub{} resolver.QueryResolver.MapStringInterface = func(ctx context.Context, in map[string]interface{}) (i map[string]interface{}, e error) { + validateMapItemsType(t, in) return in, nil } resolver.QueryResolver.MapNestedStringInterface = func(ctx context.Context, in *NestedMapInput) (i map[string]interface{}, e error) { if in == nil { return nil, nil } + validateMapItemsType(t, in.Map) return in.Map, nil } @@ -28,7 +31,7 @@ func TestMaps(t *testing.T) { var resp struct { MapStringInterface map[string]interface{} } - err := c.Post(`query { mapStringInterface { a, b } }`, &resp) + err := c.Post(`query { mapStringInterface { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Nil(t, resp.MapStringInterface) }) @@ -37,7 +40,7 @@ func TestMaps(t *testing.T) { var resp struct { MapStringInterface map[string]interface{} } - err := c.Post(`query { mapStringInterface(in: null) { a, b } }`, &resp) + err := c.Post(`query { mapStringInterface(in: null) { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Nil(t, resp.MapStringInterface) }) @@ -46,28 +49,53 @@ func TestMaps(t *testing.T) { var resp struct { MapStringInterface map[string]interface{} } - err := c.Post(`query { mapStringInterface(in: { a: "a", b: null }) { a, b } }`, &resp) + err := c.Post(`query($value: CustomScalar!) { mapStringInterface(in: { a: "a", b: null, c: 42, nested: { value: $value } }) { a, b, c, nested { value } } }`, &resp, client.Var("value", "17")) require.NoError(t, err) require.Equal(t, "a", resp.MapStringInterface["a"]) require.Nil(t, resp.MapStringInterface["b"]) + require.Equal(t, "42", resp.MapStringInterface["c"]) + require.NotNil(t, resp.MapStringInterface["nested"]) + require.IsType(t, map[string]interface{}{}, resp.MapStringInterface["nested"]) + require.Equal(t, "17", (resp.MapStringInterface["nested"].(map[string]interface{}))["value"]) }) t.Run("nested", func(t *testing.T) { var resp struct { MapNestedStringInterface map[string]interface{} } - err := c.Post(`query { mapNestedStringInterface(in: { map: { a: "a", b: null } }) { a, b } }`, &resp) + err := c.Post(`query { mapNestedStringInterface(in: { map: { a: "a", c: "42", nested: { value: 31 } } }) { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Equal(t, "a", resp.MapNestedStringInterface["a"]) require.Nil(t, resp.MapNestedStringInterface["b"]) + require.Equal(t, "42", resp.MapNestedStringInterface["c"]) + require.NotNil(t, resp.MapNestedStringInterface["nested"]) + require.IsType(t, map[string]interface{}{}, resp.MapNestedStringInterface["nested"]) + require.Equal(t, "31", (resp.MapNestedStringInterface["nested"].(map[string]interface{}))["value"]) }) t.Run("nested nil", func(t *testing.T) { var resp struct { MapNestedStringInterface map[string]interface{} } - err := c.Post(`query { mapNestedStringInterface(in: { map: null }) { a, b } }`, &resp) + err := c.Post(`query { mapNestedStringInterface(in: { map: null }) { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Nil(t, resp.MapNestedStringInterface) }) } + +func validateMapItemsType(t *testing.T, in map[string]interface{}) { + for k, v := range in { + switch k { + case "a": + require.IsType(t, "", v) + case "b": + require.IsType(t, new(int), v) + case "c": + require.IsType(t, new(CustomScalar), v) + case "nested": + require.IsType(t, new(MapNested), v) + default: + require.Failf(t, "unexpected key in map", "key %q was not expected in map", k) + } + } +} diff --git a/codegen/testserver/followschema/middleware_test.go b/codegen/testserver/followschema/middleware_test.go index 16fe10e612..3d214dc05b 100644 --- a/codegen/testserver/followschema/middleware_test.go +++ b/codegen/testserver/followschema/middleware_test.go @@ -5,12 +5,12 @@ import ( "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMiddleware(t *testing.T) { diff --git a/codegen/testserver/followschema/modelmethod_test.go b/codegen/testserver/followschema/modelmethod_test.go index 4cf1803729..3fda4b2a39 100644 --- a/codegen/testserver/followschema/modelmethod_test.go +++ b/codegen/testserver/followschema/modelmethod_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestModelMethods(t *testing.T) { diff --git a/codegen/testserver/followschema/mutation_with_custom_scalar_test.go b/codegen/testserver/followschema/mutation_with_custom_scalar_test.go index 2f46c3ad4d..67f14ef31a 100644 --- a/codegen/testserver/followschema/mutation_with_custom_scalar_test.go +++ b/codegen/testserver/followschema/mutation_with_custom_scalar_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestErrorInsideMutationArgument(t *testing.T) { diff --git a/codegen/testserver/followschema/nulls_test.go b/codegen/testserver/followschema/nulls_test.go index 6049a7ca6e..e738ffecf5 100644 --- a/codegen/testserver/followschema/nulls_test.go +++ b/codegen/testserver/followschema/nulls_test.go @@ -5,9 +5,10 @@ import ( "errors" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestNullBubbling(t *testing.T) { diff --git a/codegen/testserver/followschema/panics_test.go b/codegen/testserver/followschema/panics_test.go index 4433aad01a..e22fc144b1 100644 --- a/codegen/testserver/followschema/panics_test.go +++ b/codegen/testserver/followschema/panics_test.go @@ -5,12 +5,12 @@ import ( "fmt" "testing" - "github.com/99designs/gqlgen/graphql" + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2/gqlerror" "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser/v2/gqlerror" ) func TestPanics(t *testing.T) { diff --git a/codegen/testserver/followschema/primitive_objects_test.go b/codegen/testserver/followschema/primitive_objects_test.go index 87de88bb45..106518cacc 100644 --- a/codegen/testserver/followschema/primitive_objects_test.go +++ b/codegen/testserver/followschema/primitive_objects_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/assert" ) func TestPrimitiveObjects(t *testing.T) { diff --git a/codegen/testserver/followschema/ptr_to_any_test.go b/codegen/testserver/followschema/ptr_to_any_test.go index c1e78b8d80..3af48a89ed 100644 --- a/codegen/testserver/followschema/ptr_to_any_test.go +++ b/codegen/testserver/followschema/ptr_to_any_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestPtrToAny(t *testing.T) { diff --git a/codegen/testserver/followschema/ptr_to_ptr_input_test.go b/codegen/testserver/followschema/ptr_to_ptr_input_test.go index ab40d1d68d..785b3bceb7 100644 --- a/codegen/testserver/followschema/ptr_to_ptr_input_test.go +++ b/codegen/testserver/followschema/ptr_to_ptr_input_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type UpdatePtrToPtrResults struct { diff --git a/codegen/testserver/followschema/ptr_to_slice_test.go b/codegen/testserver/followschema/ptr_to_slice_test.go index 20818cc4d1..a95b6b46c9 100644 --- a/codegen/testserver/followschema/ptr_to_slice_test.go +++ b/codegen/testserver/followschema/ptr_to_slice_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestPtrToSlice(t *testing.T) { diff --git a/codegen/testserver/followschema/response_extension_test.go b/codegen/testserver/followschema/response_extension_test.go index 4ee1b5749f..92d1ade4af 100644 --- a/codegen/testserver/followschema/response_extension_test.go +++ b/codegen/testserver/followschema/response_extension_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestResponseExtension(t *testing.T) { diff --git a/codegen/testserver/followschema/root_.generated.go b/codegen/testserver/followschema/root_.generated.go index 1d1d0ff5e8..8463011b31 100644 --- a/codegen/testserver/followschema/root_.generated.go +++ b/codegen/testserver/followschema/root_.generated.go @@ -19,6 +19,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -26,6 +27,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -224,9 +226,15 @@ type ComplexityRoot struct { ID func(childComplexity int) int } + MapNested struct { + Value func(childComplexity int) int + } + MapStringInterfaceType struct { - A func(childComplexity int) int - B func(childComplexity int) int + A func(childComplexity int) int + B func(childComplexity int) int + C func(childComplexity int) int + Nested func(childComplexity int) int } ModelMethods struct { @@ -466,12 +474,16 @@ type ComplexityRoot struct { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -893,6 +905,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Map.ID(childComplexity), true + case "MapNested.value": + if e.complexity.MapNested.Value == nil { + break + } + + return e.complexity.MapNested.Value(childComplexity), true + case "MapStringInterfaceType.a": if e.complexity.MapStringInterfaceType.A == nil { break @@ -907,6 +926,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MapStringInterfaceType.B(childComplexity), true + case "MapStringInterfaceType.c": + if e.complexity.MapStringInterfaceType.C == nil { + break + } + + return e.complexity.MapStringInterfaceType.C(childComplexity), true + + case "MapStringInterfaceType.nested": + if e.complexity.MapStringInterfaceType.Nested == nil { + break + } + + return e.complexity.MapStringInterfaceType.Nested(childComplexity), true + case "ModelMethods.noContext": if e.complexity.ModelMethods.NoContext == nil { break @@ -2084,12 +2117,15 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { rc := graphql.GetOperationContext(ctx) ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)} inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputChanges, ec.unmarshalInputDefaultInput, ec.unmarshalInputFieldsOrderInput, ec.unmarshalInputInnerDirectives, ec.unmarshalInputInnerInput, ec.unmarshalInputInputDirectives, ec.unmarshalInputInputWithEnumValue, + ec.unmarshalInputMapNestedInput, + ec.unmarshalInputMapStringInterfaceInput, ec.unmarshalInputNestedInput, ec.unmarshalInputNestedMapInput, ec.unmarshalInputOmittableInput, @@ -2202,14 +2238,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "builtinscalar.graphql" "complexity.graphql" "defaults.graphql" "defer.graphql" "directive.graphql" "embedded.graphql" "enum.graphql" "fields_order.graphql" "interfaces.graphql" "issue896.graphql" "loops.graphql" "maps.graphql" "mutation_with_custom_scalar.graphql" "nulls.graphql" "panics.graphql" "primitive_objects.graphql" "ptr_to_any.graphql" "ptr_to_ptr_input.graphql" "ptr_to_slice.graphql" "scalar_context.graphql" "scalar_default.graphql" "schema.graphql" "slices.graphql" "typefallback.graphql" "useptr.graphql" "v-ok.graphql" "validtypes.graphql" "variadic.graphql" "weird_type_cases.graphql" "wrapped_type.graphql" diff --git a/codegen/testserver/followschema/scalar_context_test.go b/codegen/testserver/followschema/scalar_context_test.go index 2e2a0dc361..2d4bf1c8ba 100644 --- a/codegen/testserver/followschema/scalar_context_test.go +++ b/codegen/testserver/followschema/scalar_context_test.go @@ -5,9 +5,10 @@ import ( "math" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestFloatInfAndNaN(t *testing.T) { diff --git a/codegen/testserver/followschema/scalar_default_test.go b/codegen/testserver/followschema/scalar_default_test.go index f6fc05cb09..f0faa0139e 100644 --- a/codegen/testserver/followschema/scalar_default_test.go +++ b/codegen/testserver/followschema/scalar_default_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestDefaultScalarImplementation(t *testing.T) { diff --git a/codegen/testserver/followschema/schema.generated.go b/codegen/testserver/followschema/schema.generated.go index 0af031ea71..01387a495d 100644 --- a/codegen/testserver/followschema/schema.generated.go +++ b/codegen/testserver/followschema/schema.generated.go @@ -3610,6 +3610,10 @@ func (ec *executionContext) fieldContext_Query_mapStringInterface(ctx context.Co return ec.fieldContext_MapStringInterfaceType_a(ctx, field) case "b": return ec.fieldContext_MapStringInterfaceType_b(ctx, field) + case "c": + return ec.fieldContext_MapStringInterfaceType_c(ctx, field) + case "nested": + return ec.fieldContext_MapStringInterfaceType_nested(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type MapStringInterfaceType", field.Name) }, @@ -3665,6 +3669,10 @@ func (ec *executionContext) fieldContext_Query_mapNestedStringInterface(ctx cont return ec.fieldContext_MapStringInterfaceType_a(ctx, field) case "b": return ec.fieldContext_MapStringInterfaceType_b(ctx, field) + case "c": + return ec.fieldContext_MapStringInterfaceType_c(ctx, field) + case "nested": + return ec.fieldContext_MapStringInterfaceType_nested(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type MapStringInterfaceType", field.Name) }, @@ -5761,6 +5769,44 @@ func (ec *executionContext) fieldContext_User_pets(ctx context.Context, field gr // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputChanges(ctx context.Context, obj interface{}) (map[string]interface{}, error) { + it := make(map[string]interface{}, len(obj.(map[string]interface{}))) + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"a", "b"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "a": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("a")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it["a"] = data + case "b": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("b")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it["b"] = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputInnerInput(ctx context.Context, obj interface{}) (InnerInput, error) { var it InnerInput asMap := map[string]interface{}{} @@ -8220,7 +8266,8 @@ func (ec *executionContext) unmarshalOChanges2map(ctx context.Context, v interfa if v == nil { return nil, nil } - return v.(map[string]interface{}), nil + res, err := ec.unmarshalInputChanges(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOInvalidIdentifier2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋfollowschemaᚋinvalidᚑpackagenameᚐInvalidIdentifier(ctx context.Context, sel ast.SelectionSet, v *invalid_packagename.InvalidIdentifier) graphql.Marshaler { diff --git a/codegen/testserver/followschema/slices_test.go b/codegen/testserver/followschema/slices_test.go index 19df15816b..de29686186 100644 --- a/codegen/testserver/followschema/slices_test.go +++ b/codegen/testserver/followschema/slices_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestSlices(t *testing.T) { diff --git a/codegen/testserver/followschema/subscription_test.go b/codegen/testserver/followschema/subscription_test.go index 6126db99ce..b25fb53bd6 100644 --- a/codegen/testserver/followschema/subscription_test.go +++ b/codegen/testserver/followschema/subscription_test.go @@ -8,12 +8,12 @@ import ( "testing" "time" - "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/stretchr/testify/require" "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/graphql/handler/transport" ) func TestSubscriptions(t *testing.T) { @@ -130,6 +130,9 @@ func TestSubscriptions(t *testing.T) { }) t.Run("will parse init payload", func(t *testing.T) { + runtime.GC() // ensure no go-routines left from preceding tests + initialGoroutineCount := runtime.NumGoroutine() + sub := c.WebsocketWithPayload(`subscription { initPayload }`, map[string]interface{}{ "Authorization": "Bearer of the curse", "number": 32, @@ -155,6 +158,14 @@ func TestSubscriptions(t *testing.T) { require.NoError(t, err) require.Equal(t, "strings = []interface {}{\"hello\", \"world\"}", msg.resp.InitPayload) sub.Close() + + // need a little bit of time for goroutines to settle + start := time.Now() + for time.Since(start).Seconds() < 2 && initialGoroutineCount != runtime.NumGoroutine() { + time.Sleep(5 * time.Millisecond) + } + + require.Equal(t, initialGoroutineCount, runtime.NumGoroutine()) }) t.Run("websocket gets errors", func(t *testing.T) { diff --git a/codegen/testserver/followschema/time_test.go b/codegen/testserver/followschema/time_test.go index 60a098750a..947c1cc293 100644 --- a/codegen/testserver/followschema/time_test.go +++ b/codegen/testserver/followschema/time_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestTime(t *testing.T) { diff --git a/codegen/testserver/followschema/typefallback_test.go b/codegen/testserver/followschema/typefallback_test.go index 764661f84b..013ac1b7f8 100644 --- a/codegen/testserver/followschema/typefallback_test.go +++ b/codegen/testserver/followschema/typefallback_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestTypeFallback(t *testing.T) { diff --git a/codegen/testserver/followschema/validtypes_test.go b/codegen/testserver/followschema/validtypes_test.go index 2c0e67942a..026ebe029c 100644 --- a/codegen/testserver/followschema/validtypes_test.go +++ b/codegen/testserver/followschema/validtypes_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestValidType(t *testing.T) { diff --git a/codegen/testserver/followschema/wrapped_type_test.go b/codegen/testserver/followschema/wrapped_type_test.go index 95280b3d95..d28db51c3d 100644 --- a/codegen/testserver/followschema/wrapped_type_test.go +++ b/codegen/testserver/followschema/wrapped_type_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/codegen/testserver/followschema/otherpkg" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestWrappedTypes(t *testing.T) { diff --git a/codegen/testserver/singlefile/complexity_test.go b/codegen/testserver/singlefile/complexity_test.go index ac8e05a8c3..4dfbdffe7d 100644 --- a/codegen/testserver/singlefile/complexity_test.go +++ b/codegen/testserver/singlefile/complexity_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" - "github.com/stretchr/testify/require" ) func TestComplexityCollisions(t *testing.T) { diff --git a/codegen/testserver/singlefile/defaults_test.go b/codegen/testserver/singlefile/defaults_test.go index 4b403ad151..f69efcf49a 100644 --- a/codegen/testserver/singlefile/defaults_test.go +++ b/codegen/testserver/singlefile/defaults_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func assertDefaults(t *testing.T, ret *DefaultParametersMirror) { diff --git a/codegen/testserver/singlefile/defer_test.go b/codegen/testserver/singlefile/defer_test.go index 876123401f..a5a1628da1 100644 --- a/codegen/testserver/singlefile/defer_test.go +++ b/codegen/testserver/singlefile/defer_test.go @@ -9,11 +9,12 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestDefer(t *testing.T) { diff --git a/codegen/testserver/singlefile/directive_test.go b/codegen/testserver/singlefile/directive_test.go index 0b7cb97df8..e5df58d2e5 100644 --- a/codegen/testserver/singlefile/directive_test.go +++ b/codegen/testserver/singlefile/directive_test.go @@ -5,10 +5,11 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type ckey string diff --git a/codegen/testserver/singlefile/embedded_test.go b/codegen/testserver/singlefile/embedded_test.go index dd9ededd14..d3dc9a1a71 100644 --- a/codegen/testserver/singlefile/embedded_test.go +++ b/codegen/testserver/singlefile/embedded_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type fakeUnexportedEmbeddedInterface struct{} diff --git a/codegen/testserver/singlefile/enums_test.go b/codegen/testserver/singlefile/enums_test.go index 11ac0d1fbf..9a12bd3fb5 100644 --- a/codegen/testserver/singlefile/enums_test.go +++ b/codegen/testserver/singlefile/enums_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestEnumsResolver(t *testing.T) { diff --git a/codegen/testserver/singlefile/fields_order_test.go b/codegen/testserver/singlefile/fields_order_test.go index eac294187f..daff5b6a38 100644 --- a/codegen/testserver/singlefile/fields_order_test.go +++ b/codegen/testserver/singlefile/fields_order_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type FieldsOrderPayloadResults struct { diff --git a/codegen/testserver/singlefile/generated.go b/codegen/testserver/singlefile/generated.go index 369cbf70d1..f260711bfb 100644 --- a/codegen/testserver/singlefile/generated.go +++ b/codegen/testserver/singlefile/generated.go @@ -28,6 +28,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -35,6 +36,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -233,9 +235,15 @@ type ComplexityRoot struct { ID func(childComplexity int) int } + MapNested struct { + Value func(childComplexity int) int + } + MapStringInterfaceType struct { - A func(childComplexity int) int - B func(childComplexity int) int + A func(childComplexity int) int + B func(childComplexity int) int + C func(childComplexity int) int + Nested func(childComplexity int) int } ModelMethods struct { @@ -618,12 +626,16 @@ type FieldsOrderInputResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -1045,6 +1057,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Map.ID(childComplexity), true + case "MapNested.value": + if e.complexity.MapNested.Value == nil { + break + } + + return e.complexity.MapNested.Value(childComplexity), true + case "MapStringInterfaceType.a": if e.complexity.MapStringInterfaceType.A == nil { break @@ -1059,6 +1078,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MapStringInterfaceType.B(childComplexity), true + case "MapStringInterfaceType.c": + if e.complexity.MapStringInterfaceType.C == nil { + break + } + + return e.complexity.MapStringInterfaceType.C(childComplexity), true + + case "MapStringInterfaceType.nested": + if e.complexity.MapStringInterfaceType.Nested == nil { + break + } + + return e.complexity.MapStringInterfaceType.Nested(childComplexity), true + case "ModelMethods.noContext": if e.complexity.ModelMethods.NoContext == nil { break @@ -2236,12 +2269,15 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { rc := graphql.GetOperationContext(ctx) ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)} inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputChanges, ec.unmarshalInputDefaultInput, ec.unmarshalInputFieldsOrderInput, ec.unmarshalInputInnerDirectives, ec.unmarshalInputInnerInput, ec.unmarshalInputInputDirectives, ec.unmarshalInputInputWithEnumValue, + ec.unmarshalInputMapNestedInput, + ec.unmarshalInputMapStringInterfaceInput, ec.unmarshalInputNestedInput, ec.unmarshalInputNestedMapInput, ec.unmarshalInputOmittableInput, @@ -2354,14 +2390,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "builtinscalar.graphql" "complexity.graphql" "defaults.graphql" "defer.graphql" "directive.graphql" "embedded.graphql" "enum.graphql" "fields_order.graphql" "interfaces.graphql" "issue896.graphql" "loops.graphql" "maps.graphql" "mutation_with_custom_scalar.graphql" "nulls.graphql" "panics.graphql" "primitive_objects.graphql" "ptr_to_any.graphql" "ptr_to_ptr_input.graphql" "ptr_to_slice.graphql" "scalar_context.graphql" "scalar_default.graphql" "schema.graphql" "slices.graphql" "typefallback.graphql" "useptr.graphql" "v-ok.graphql" "validtypes.graphql" "variadic.graphql" "weird_type_cases.graphql" "wrapped_type.graphql" @@ -6047,6 +6083,47 @@ func (ec *executionContext) fieldContext_Map_id(ctx context.Context, field graph return fc, nil } +func (ec *executionContext) _MapNested_value(ctx context.Context, field graphql.CollectedField, obj *MapNested) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MapNested_value(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Value, nil + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(CustomScalar) + fc.Result = res + return ec.marshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_MapNested_value(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MapNested", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CustomScalar does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _MapStringInterfaceType_a(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) (ret graphql.Marshaler) { fc, err := ec.fieldContext_MapStringInterfaceType_a(ctx, field) if err != nil { @@ -6141,6 +6218,104 @@ func (ec *executionContext) fieldContext_MapStringInterfaceType_b(ctx context.Co return fc, nil } +func (ec *executionContext) _MapStringInterfaceType_c(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MapStringInterfaceType_c(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + switch v := obj["c"].(type) { + case *CustomScalar: + return v, nil + case CustomScalar: + return &v, nil + case nil: + return (*CustomScalar)(nil), nil + default: + return nil, fmt.Errorf("unexpected type %T for field %s", v, "c") + } + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*CustomScalar) + fc.Result = res + return ec.marshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_MapStringInterfaceType_c(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MapStringInterfaceType", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CustomScalar does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MapStringInterfaceType_nested(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MapStringInterfaceType_nested(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + switch v := obj["nested"].(type) { + case *MapNested: + return v, nil + case MapNested: + return &v, nil + case nil: + return (*MapNested)(nil), nil + default: + return nil, fmt.Errorf("unexpected type %T for field %s", v, "nested") + } + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*MapNested) + fc.Result = res + return ec.marshalOMapNested2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐMapNested(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_MapStringInterfaceType_nested(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MapStringInterfaceType", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "value": + return ec.fieldContext_MapNested_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MapNested", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _ModelMethods_resolverField(ctx context.Context, field graphql.CollectedField, obj *ModelMethods) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ModelMethods_resolverField(ctx, field) if err != nil { @@ -9890,6 +10065,10 @@ func (ec *executionContext) fieldContext_Query_mapStringInterface(ctx context.Co return ec.fieldContext_MapStringInterfaceType_a(ctx, field) case "b": return ec.fieldContext_MapStringInterfaceType_b(ctx, field) + case "c": + return ec.fieldContext_MapStringInterfaceType_c(ctx, field) + case "nested": + return ec.fieldContext_MapStringInterfaceType_nested(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type MapStringInterfaceType", field.Name) }, @@ -9945,6 +10124,10 @@ func (ec *executionContext) fieldContext_Query_mapNestedStringInterface(ctx cont return ec.fieldContext_MapStringInterfaceType_a(ctx, field) case "b": return ec.fieldContext_MapStringInterfaceType_b(ctx, field) + case "c": + return ec.fieldContext_MapStringInterfaceType_c(ctx, field) + case "nested": + return ec.fieldContext_MapStringInterfaceType_nested(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type MapStringInterfaceType", field.Name) }, @@ -14769,6 +14952,44 @@ func (ec *executionContext) fieldContext_iIt_id(ctx context.Context, field graph // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputChanges(ctx context.Context, obj interface{}) (map[string]interface{}, error) { + it := make(map[string]interface{}, len(obj.(map[string]interface{}))) + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"a", "b"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "a": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("a")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it["a"] = data + case "b": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("b")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it["b"] = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDefaultInput(ctx context.Context, obj interface{}) (DefaultInput, error) { var it DefaultInput asMap := map[string]interface{}{} @@ -15144,6 +15365,91 @@ func (ec *executionContext) unmarshalInputInputWithEnumValue(ctx context.Context return it, nil } +func (ec *executionContext) unmarshalInputMapNestedInput(ctx context.Context, obj interface{}) (MapNested, error) { + var it MapNested + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"value"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "value": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("value")) + data, err := ec.unmarshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx, v) + if err != nil { + return it, err + } + it.Value = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputMapStringInterfaceInput(ctx context.Context, obj interface{}) (map[string]interface{}, error) { + it := make(map[string]interface{}, len(obj.(map[string]interface{}))) + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"a", "b", "c", "nested"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "a": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("a")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it["a"] = data + case "b": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("b")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it["b"] = data + case "c": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("c")) + data, err := ec.unmarshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx, v) + if err != nil { + return it, err + } + it["c"] = data + case "nested": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nested")) + data, err := ec.unmarshalOMapNestedInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐMapNested(ctx, v) + if err != nil { + return it, err + } + it["nested"] = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputNestedInput(ctx context.Context, obj interface{}) (NestedInput, error) { var it NestedInput asMap := map[string]interface{}{} @@ -17443,6 +17749,45 @@ func (ec *executionContext) _Map(ctx context.Context, sel ast.SelectionSet, obj return out } +var mapNestedImplementors = []string{"MapNested"} + +func (ec *executionContext) _MapNested(ctx context.Context, sel ast.SelectionSet, obj *MapNested) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mapNestedImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MapNested") + case "value": + out.Values[i] = ec._MapNested_value(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var mapStringInterfaceTypeImplementors = []string{"MapStringInterfaceType"} func (ec *executionContext) _MapStringInterfaceType(ctx context.Context, sel ast.SelectionSet, obj map[string]interface{}) graphql.Marshaler { @@ -17458,6 +17803,10 @@ func (ec *executionContext) _MapStringInterfaceType(ctx context.Context, sel ast out.Values[i] = ec._MapStringInterfaceType_a(ctx, field, obj) case "b": out.Values[i] = ec._MapStringInterfaceType_b(ctx, field, obj) + case "c": + out.Values[i] = ec._MapStringInterfaceType_c(ctx, field, obj) + case "nested": + out.Values[i] = ec._MapStringInterfaceType_nested(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -21096,6 +21445,16 @@ func (ec *executionContext) marshalNCheckIssue8962ᚖgithubᚗcomᚋ99designsᚋ return ec._CheckIssue896(ctx, sel, v) } +func (ec *executionContext) unmarshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx context.Context, v interface{}) (CustomScalar, error) { + var res CustomScalar + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNCustomScalar2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx context.Context, sel ast.SelectionSet, v CustomScalar) graphql.Marshaler { + return v +} + func (ec *executionContext) unmarshalNDefaultInput2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐDefaultInput(ctx context.Context, v interface{}) (DefaultInput, error) { res, err := ec.unmarshalInputDefaultInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -22199,7 +22558,8 @@ func (ec *executionContext) unmarshalOChanges2map(ctx context.Context, v interfa if v == nil { return nil, nil } - return v.(map[string]interface{}), nil + res, err := ec.unmarshalInputChanges(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOCheckIssue8962ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCheckIssue896(ctx context.Context, sel ast.SelectionSet, v []*CheckIssue896) graphql.Marshaler { @@ -22308,6 +22668,22 @@ func (ec *executionContext) marshalOCoordinates2githubᚗcomᚋ99designsᚋgqlge return ec._Coordinates(ctx, sel, &v) } +func (ec *executionContext) unmarshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx context.Context, v interface{}) (*CustomScalar, error) { + if v == nil { + return nil, nil + } + var res = new(CustomScalar) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOCustomScalar2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐCustomScalar(ctx context.Context, sel ast.SelectionSet, v *CustomScalar) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + func (ec *executionContext) unmarshalODefaultScalarImplementation2ᚖstring(ctx context.Context, v interface{}) (*string, error) { if v == nil { return nil, nil @@ -22588,11 +22964,27 @@ func (ec *executionContext) marshalOIt2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋco return ec._It(ctx, sel, v) } +func (ec *executionContext) marshalOMapNested2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐMapNested(ctx context.Context, sel ast.SelectionSet, v *MapNested) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._MapNested(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOMapNestedInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚋsinglefileᚐMapNested(ctx context.Context, v interface{}) (*MapNested, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMapNestedInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOMapStringInterfaceInput2map(ctx context.Context, v interface{}) (map[string]interface{}, error) { if v == nil { return nil, nil } - return v.(map[string]interface{}), nil + res, err := ec.unmarshalInputMapStringInterfaceInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOMapStringInterfaceType2map(ctx context.Context, sel ast.SelectionSet, v map[string]interface{}) graphql.Marshaler { diff --git a/codegen/testserver/singlefile/generated_test.go b/codegen/testserver/singlefile/generated_test.go index 6d1dfa7e3c..63a6a4f2a1 100644 --- a/codegen/testserver/singlefile/generated_test.go +++ b/codegen/testserver/singlefile/generated_test.go @@ -8,9 +8,10 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestForcedResolverFieldIsPointer(t *testing.T) { diff --git a/codegen/testserver/singlefile/input_test.go b/codegen/testserver/singlefile/input_test.go index d33081b26c..ea05b2edfb 100644 --- a/codegen/testserver/singlefile/input_test.go +++ b/codegen/testserver/singlefile/input_test.go @@ -6,9 +6,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestInput(t *testing.T) { diff --git a/codegen/testserver/singlefile/interfaces_test.go b/codegen/testserver/singlefile/interfaces_test.go index 23b3fb0206..9a38a3f942 100644 --- a/codegen/testserver/singlefile/interfaces_test.go +++ b/codegen/testserver/singlefile/interfaces_test.go @@ -6,10 +6,11 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestInterfaces(t *testing.T) { diff --git a/codegen/testserver/singlefile/introspection_test.go b/codegen/testserver/singlefile/introspection_test.go index f78a8202c1..64958e7e79 100644 --- a/codegen/testserver/singlefile/introspection_test.go +++ b/codegen/testserver/singlefile/introspection_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/stretchr/testify/require" ) func TestIntrospection(t *testing.T) { diff --git a/codegen/testserver/singlefile/maps.go b/codegen/testserver/singlefile/maps.go new file mode 100644 index 0000000000..58366271b9 --- /dev/null +++ b/codegen/testserver/singlefile/maps.go @@ -0,0 +1,28 @@ +package singlefile + +import ( + "io" + "strconv" +) + +type MapNested struct { + Value CustomScalar +} + +type CustomScalar struct { + value int64 +} + +func (s *CustomScalar) UnmarshalGQL(v interface{}) (err error) { + switch v := v.(type) { + case string: + s.value, err = strconv.ParseInt(v, 10, 64) + case int64: + s.value = v + } + return +} + +func (s CustomScalar) MarshalGQL(w io.Writer) { + _, _ = w.Write([]byte(strconv.Quote(strconv.FormatInt(s.value, 10)))) +} diff --git a/codegen/testserver/singlefile/maps.graphql b/codegen/testserver/singlefile/maps.graphql index 0fd639b0c4..38205ad2ac 100644 --- a/codegen/testserver/singlefile/maps.graphql +++ b/codegen/testserver/singlefile/maps.graphql @@ -6,13 +6,27 @@ extend type Query { type MapStringInterfaceType @goModel(model: "map[string]interface{}") { a: String b: Int + c: CustomScalar + nested: MapNested +} + +type MapNested @goModel(model: "singlefile.MapNested") { + value: CustomScalar! } input MapStringInterfaceInput @goModel(model: "map[string]interface{}") { - a: String + a: String! b: Int + c: CustomScalar + nested: MapNestedInput } +input MapNestedInput @goModel(model: "singlefile.MapNested") { + value: CustomScalar! +} + +scalar CustomScalar @goModel(model: "singlefile.CustomScalar") + input NestedMapInput { map: MapStringInterfaceInput } diff --git a/codegen/testserver/singlefile/maps_test.go b/codegen/testserver/singlefile/maps_test.go index 0c7129ae61..1b532909fb 100644 --- a/codegen/testserver/singlefile/maps_test.go +++ b/codegen/testserver/singlefile/maps_test.go @@ -4,20 +4,23 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestMaps(t *testing.T) { resolver := &Stub{} resolver.QueryResolver.MapStringInterface = func(ctx context.Context, in map[string]interface{}) (i map[string]interface{}, e error) { + validateMapItemsType(t, in) return in, nil } resolver.QueryResolver.MapNestedStringInterface = func(ctx context.Context, in *NestedMapInput) (i map[string]interface{}, e error) { if in == nil { return nil, nil } + validateMapItemsType(t, in.Map) return in.Map, nil } @@ -28,7 +31,7 @@ func TestMaps(t *testing.T) { var resp struct { MapStringInterface map[string]interface{} } - err := c.Post(`query { mapStringInterface { a, b } }`, &resp) + err := c.Post(`query { mapStringInterface { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Nil(t, resp.MapStringInterface) }) @@ -37,7 +40,7 @@ func TestMaps(t *testing.T) { var resp struct { MapStringInterface map[string]interface{} } - err := c.Post(`query { mapStringInterface(in: null) { a, b } }`, &resp) + err := c.Post(`query { mapStringInterface(in: null) { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Nil(t, resp.MapStringInterface) }) @@ -46,28 +49,53 @@ func TestMaps(t *testing.T) { var resp struct { MapStringInterface map[string]interface{} } - err := c.Post(`query { mapStringInterface(in: { a: "a", b: null }) { a, b } }`, &resp) + err := c.Post(`query($value: CustomScalar!) { mapStringInterface(in: { a: "a", b: null, c: 42, nested: { value: $value } }) { a, b, c, nested { value } } }`, &resp, client.Var("value", "17")) require.NoError(t, err) require.Equal(t, "a", resp.MapStringInterface["a"]) require.Nil(t, resp.MapStringInterface["b"]) + require.Equal(t, "42", resp.MapStringInterface["c"]) + require.NotNil(t, resp.MapStringInterface["nested"]) + require.IsType(t, map[string]interface{}{}, resp.MapStringInterface["nested"]) + require.Equal(t, "17", (resp.MapStringInterface["nested"].(map[string]interface{}))["value"]) }) t.Run("nested", func(t *testing.T) { var resp struct { MapNestedStringInterface map[string]interface{} } - err := c.Post(`query { mapNestedStringInterface(in: { map: { a: "a", b: null } }) { a, b } }`, &resp) + err := c.Post(`query { mapNestedStringInterface(in: { map: { a: "a", c: "42", nested: { value: 31 } } }) { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Equal(t, "a", resp.MapNestedStringInterface["a"]) require.Nil(t, resp.MapNestedStringInterface["b"]) + require.Equal(t, "42", resp.MapNestedStringInterface["c"]) + require.NotNil(t, resp.MapNestedStringInterface["nested"]) + require.IsType(t, map[string]interface{}{}, resp.MapNestedStringInterface["nested"]) + require.Equal(t, "31", (resp.MapNestedStringInterface["nested"].(map[string]interface{}))["value"]) }) t.Run("nested nil", func(t *testing.T) { var resp struct { MapNestedStringInterface map[string]interface{} } - err := c.Post(`query { mapNestedStringInterface(in: { map: null }) { a, b } }`, &resp) + err := c.Post(`query { mapNestedStringInterface(in: { map: null }) { a, b, c, nested { value } } }`, &resp) require.NoError(t, err) require.Nil(t, resp.MapNestedStringInterface) }) } + +func validateMapItemsType(t *testing.T, in map[string]interface{}) { + for k, v := range in { + switch k { + case "a": + require.IsType(t, "", v) + case "b": + require.IsType(t, new(int), v) + case "c": + require.IsType(t, new(CustomScalar), v) + case "nested": + require.IsType(t, new(MapNested), v) + default: + require.Failf(t, "unexpected key in map", "key %q was not expected in map", k) + } + } +} diff --git a/codegen/testserver/singlefile/middleware_test.go b/codegen/testserver/singlefile/middleware_test.go index 5a840d0751..7b404d41ea 100644 --- a/codegen/testserver/singlefile/middleware_test.go +++ b/codegen/testserver/singlefile/middleware_test.go @@ -5,12 +5,12 @@ import ( "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMiddleware(t *testing.T) { diff --git a/codegen/testserver/singlefile/modelmethod_test.go b/codegen/testserver/singlefile/modelmethod_test.go index aefb14aa7b..a584b35b78 100644 --- a/codegen/testserver/singlefile/modelmethod_test.go +++ b/codegen/testserver/singlefile/modelmethod_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestModelMethods(t *testing.T) { diff --git a/codegen/testserver/singlefile/mutation_with_custom_scalar_test.go b/codegen/testserver/singlefile/mutation_with_custom_scalar_test.go index 3290b373e2..5237e99f2f 100644 --- a/codegen/testserver/singlefile/mutation_with_custom_scalar_test.go +++ b/codegen/testserver/singlefile/mutation_with_custom_scalar_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestErrorInsideMutationArgument(t *testing.T) { diff --git a/codegen/testserver/singlefile/nulls_test.go b/codegen/testserver/singlefile/nulls_test.go index 853428f485..c2609676fc 100644 --- a/codegen/testserver/singlefile/nulls_test.go +++ b/codegen/testserver/singlefile/nulls_test.go @@ -5,9 +5,10 @@ import ( "errors" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestNullBubbling(t *testing.T) { diff --git a/codegen/testserver/singlefile/panics_test.go b/codegen/testserver/singlefile/panics_test.go index 209344d79d..a426130618 100644 --- a/codegen/testserver/singlefile/panics_test.go +++ b/codegen/testserver/singlefile/panics_test.go @@ -5,12 +5,12 @@ import ( "fmt" "testing" - "github.com/99designs/gqlgen/graphql" + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2/gqlerror" "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser/v2/gqlerror" ) func TestPanics(t *testing.T) { diff --git a/codegen/testserver/singlefile/primitive_objects_test.go b/codegen/testserver/singlefile/primitive_objects_test.go index 80e7a96d7a..c16e305565 100644 --- a/codegen/testserver/singlefile/primitive_objects_test.go +++ b/codegen/testserver/singlefile/primitive_objects_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/assert" ) func TestPrimitiveObjects(t *testing.T) { diff --git a/codegen/testserver/singlefile/ptr_to_any_test.go b/codegen/testserver/singlefile/ptr_to_any_test.go index 88feadecc7..7ad1dcb408 100644 --- a/codegen/testserver/singlefile/ptr_to_any_test.go +++ b/codegen/testserver/singlefile/ptr_to_any_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestPtrToAny(t *testing.T) { diff --git a/codegen/testserver/singlefile/ptr_to_ptr_input_test.go b/codegen/testserver/singlefile/ptr_to_ptr_input_test.go index 1e46e7e33c..f5f1f381bd 100644 --- a/codegen/testserver/singlefile/ptr_to_ptr_input_test.go +++ b/codegen/testserver/singlefile/ptr_to_ptr_input_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) type UpdatePtrToPtrResults struct { diff --git a/codegen/testserver/singlefile/ptr_to_slice_test.go b/codegen/testserver/singlefile/ptr_to_slice_test.go index 0eee0bb873..5549ec68ab 100644 --- a/codegen/testserver/singlefile/ptr_to_slice_test.go +++ b/codegen/testserver/singlefile/ptr_to_slice_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestPtrToSlice(t *testing.T) { diff --git a/codegen/testserver/singlefile/response_extension_test.go b/codegen/testserver/singlefile/response_extension_test.go index 9e570dbda0..8fca678027 100644 --- a/codegen/testserver/singlefile/response_extension_test.go +++ b/codegen/testserver/singlefile/response_extension_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestResponseExtension(t *testing.T) { diff --git a/codegen/testserver/singlefile/scalar_context_test.go b/codegen/testserver/singlefile/scalar_context_test.go index 2070b0996b..63d2ad32fb 100644 --- a/codegen/testserver/singlefile/scalar_context_test.go +++ b/codegen/testserver/singlefile/scalar_context_test.go @@ -5,9 +5,10 @@ import ( "math" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestFloatInfAndNaN(t *testing.T) { diff --git a/codegen/testserver/singlefile/scalar_default_test.go b/codegen/testserver/singlefile/scalar_default_test.go index 556a3eccb4..f23f6c2e82 100644 --- a/codegen/testserver/singlefile/scalar_default_test.go +++ b/codegen/testserver/singlefile/scalar_default_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestDefaultScalarImplementation(t *testing.T) { diff --git a/codegen/testserver/singlefile/slices_test.go b/codegen/testserver/singlefile/slices_test.go index b6c3e37b9a..596e332528 100644 --- a/codegen/testserver/singlefile/slices_test.go +++ b/codegen/testserver/singlefile/slices_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestSlices(t *testing.T) { diff --git a/codegen/testserver/singlefile/subscription_test.go b/codegen/testserver/singlefile/subscription_test.go index 24186e5ebe..6199bab6d5 100644 --- a/codegen/testserver/singlefile/subscription_test.go +++ b/codegen/testserver/singlefile/subscription_test.go @@ -8,12 +8,12 @@ import ( "testing" "time" - "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/stretchr/testify/require" "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/graphql/handler/transport" ) func TestSubscriptions(t *testing.T) { @@ -130,6 +130,9 @@ func TestSubscriptions(t *testing.T) { }) t.Run("will parse init payload", func(t *testing.T) { + runtime.GC() // ensure no go-routines left from preceding tests + initialGoroutineCount := runtime.NumGoroutine() + sub := c.WebsocketWithPayload(`subscription { initPayload }`, map[string]interface{}{ "Authorization": "Bearer of the curse", "number": 32, @@ -155,6 +158,14 @@ func TestSubscriptions(t *testing.T) { require.NoError(t, err) require.Equal(t, "strings = []interface {}{\"hello\", \"world\"}", msg.resp.InitPayload) sub.Close() + + // need a little bit of time for goroutines to settle + start := time.Now() + for time.Since(start).Seconds() < 2 && initialGoroutineCount != runtime.NumGoroutine() { + time.Sleep(5 * time.Millisecond) + } + + require.Equal(t, initialGoroutineCount, runtime.NumGoroutine()) }) t.Run("websocket gets errors", func(t *testing.T) { diff --git a/codegen/testserver/singlefile/time_test.go b/codegen/testserver/singlefile/time_test.go index 90c8a0c539..1a7c9f9ac3 100644 --- a/codegen/testserver/singlefile/time_test.go +++ b/codegen/testserver/singlefile/time_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestTime(t *testing.T) { diff --git a/codegen/testserver/singlefile/typefallback_test.go b/codegen/testserver/singlefile/typefallback_test.go index 13ba74449c..55f49282ed 100644 --- a/codegen/testserver/singlefile/typefallback_test.go +++ b/codegen/testserver/singlefile/typefallback_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestTypeFallback(t *testing.T) { diff --git a/codegen/testserver/singlefile/validtypes_test.go b/codegen/testserver/singlefile/validtypes_test.go index 28c5f07974..6b27a4a399 100644 --- a/codegen/testserver/singlefile/validtypes_test.go +++ b/codegen/testserver/singlefile/validtypes_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestValidType(t *testing.T) { diff --git a/codegen/testserver/singlefile/wrapped_type_test.go b/codegen/testserver/singlefile/wrapped_type_test.go index 13a9ccab84..98ffaa3a7c 100644 --- a/codegen/testserver/singlefile/wrapped_type_test.go +++ b/codegen/testserver/singlefile/wrapped_type_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" "github.com/99designs/gqlgen/codegen/testserver/singlefile/otherpkg" "github.com/99designs/gqlgen/graphql/handler" - "github.com/stretchr/testify/require" ) func TestWrappedTypes(t *testing.T) { diff --git a/codegen/type.gotpl b/codegen/type.gotpl index 5cbd737c77..99ad9e0006 100644 --- a/codegen/type.gotpl +++ b/codegen/type.gotpl @@ -59,8 +59,6 @@ {{- else}} return res, graphql.ErrorOnPath(ctx, err) {{- end }} - {{- else if eq ($type.GO | ref) "map[string]interface{}" }} - return v.(map[string]interface{}), nil {{- else if $type.IsMarshaler }} {{- if and $type.IsNilable $type.Elem }} var res = new({{ $type.Elem.GO | ref }}) @@ -75,7 +73,7 @@ return res, graphql.ErrorOnPath(ctx, err) {{- else }} res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v) - {{- if and $type.IsNilable (not $type.PointersInUmarshalInput) }} + {{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUmarshalInput) }} return &res, graphql.ErrorOnPath(ctx, err) {{- else if and (not $type.IsNilable) $type.PointersInUmarshalInput }} return *res, graphql.ErrorOnPath(ctx, err) diff --git a/complexity/complexity.go b/complexity/complexity.go index e3ecf7612d..aa0f86432e 100644 --- a/complexity/complexity.go +++ b/complexity/complexity.go @@ -1,8 +1,9 @@ package complexity import ( - "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" ) func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars map[string]interface{}) int { diff --git a/complexity/complexity_test.go b/complexity/complexity_test.go index 0c4be24e1c..e99acf0efb 100644 --- a/complexity/complexity_test.go +++ b/complexity/complexity_test.go @@ -4,10 +4,11 @@ import ( "math" "testing" - "github.com/99designs/gqlgen/graphql" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" ) var schema = gqlparser.MustLoadSchema( diff --git a/docs/content/config.md b/docs/content/config.md index 7233055346..f464359aca 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -30,6 +30,8 @@ federation: model: filename: graph/model/models_gen.go package: model + # Optional: Pass in a path to a new gotpl template to use for generating the models + # model_template: [your/path/model.gotpl] # Where should the resolver implementations go? resolver: @@ -39,6 +41,8 @@ resolver: filename_template: "{name}.resolvers.go" # Optional: turn on to not generate template comments above resolvers # omit_template_comment: false + # Optional: Pass in a path to a new gotpl template to use for generating resolvers + # resolver_template: [your/path/resolver.gotpl] # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models # struct_tag: json @@ -80,6 +84,11 @@ resolver: # Optional: set to skip running `go mod tidy` when generating server code # skip_mod_tidy: true +# Optional: set build tags that will be used to load packages +# go_build_tags: +# - private +# - enterprise + # Optional: set to modify the initialisms regarded for Go names # go_initialisms: # replace_defaults: false # if true, the default initialisms will get dropped in favor of the new ones instead of being added @@ -109,6 +118,9 @@ models: - github.com/99designs/gqlgen/graphql.Int - github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int32 + UUID: + model: + - github.com/99designs/gqlgen/graphql.UUID ``` Everything has defaults, so add things as you need. @@ -123,12 +135,13 @@ To start using them you first need to define them: directive @goModel( model: String models: [String!] + forceGenerate: Boolean ) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION directive @goField( forceResolver: Boolean name: String - omittable: Boolean + omittable: Boolean ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION directive @goTag( @@ -152,6 +165,12 @@ type User @goModel(model: "github.com/my/app/models.User") { @goTag(key: "xorm", value: "-") @goTag(key: "yaml") } + +# This make sense when autobind activated. +type Person @goModel(forceGenerate: true) { + id: ID! + name: String! +} ``` The builtin directives `goField`, `goModel` and `goTag` are automatically registered to `skip_runtime`. Any directives registered as `skip_runtime` will not exposed during introspection and are used during code generation only. diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md index 42fee8eb9a..ba3c442406 100644 --- a/docs/content/getting-started.md +++ b/docs/content/getting-started.md @@ -135,14 +135,14 @@ type Resolver struct{ } ``` -Returning to `graph/schema.resolvers.go`, let's implement the bodies of those automatically generated resolver functions. For `CreateTodo`, we'll use the [`math.rand` package](https://pkg.go.dev/math/rand#Rand.Int) to simply return a todo with a randomly generated ID and store that in the in-memory todos list --- in a real app, you're likely to use a database or some other backend service. +Returning to `graph/schema.resolvers.go`, let's implement the bodies of those automatically generated resolver functions. For `CreateTodo`, we'll use the [`crypto.rand` package](https://pkg.go.dev/crypto/rand#Int) to simply return a todo with a randomly generated ID and store that in the in-memory todos list --- in a real app, you're likely to use a database or some other backend service. ```go func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { - rand, _ := rand.Int(rand.Reader, big.NewInt(100)) + randNumber, _ := rand.Int(rand.Reader, big.NewInt(100)) todo := &model.Todo{ Text: input.Text, - ID: fmt.Sprintf("T%d", rand), + ID: fmt.Sprintf("T%d", randNumber), User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, } r.todos = append(r.todos, todo) @@ -250,10 +250,10 @@ And run `go run github.com/99designs/gqlgen generate`. Now if we look in `graph/schema.resolvers.go` we can see a new resolver, lets implement it and fix `CreateTodo`. ```go func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { + randNumber, _ := rand.Int(rand.Reader, big.NewInt(100)) todo := &model.Todo{ Text: input.Text, - ID: fmt.Sprintf("T%d", rand.Int()), - User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, + ID: fmt.Sprintf("T%d", randNumber), UserID: input.UserID, } r.todos = append(r.todos, todo) diff --git a/docs/content/recipes/subscriptions.md b/docs/content/recipes/subscriptions.md index 5845bb724c..4cf72204e4 100644 --- a/docs/content/recipes/subscriptions.md +++ b/docs/content/recipes/subscriptions.md @@ -121,6 +121,9 @@ func (r *subscriptionResolver) CurrentTime(ctx context.Context) (<-chan *model.T // You can (and probably should) handle your channels in a central place outside of `schema.resolvers.go`. // For this example we'll simply use a Goroutine with a simple loop. go func() { + // Handle deregistration of the channel here. Note the `defer` + defer close(ch) + for { // In our example we'll send the current time every second. time.Sleep(1 * time.Second) @@ -294,6 +297,8 @@ func (r *subscriptionResolver) CurrentTime(ctx context.Context) (<-chan *model.T ch := make(chan *model.Time) go func() { + defer close(ch) + for { time.Sleep(1 * time.Second) fmt.Println("Tick") diff --git a/docs/content/reference/changesets.md b/docs/content/reference/changesets.md index 329206ba5e..513eb67817 100644 --- a/docs/content/reference/changesets.md +++ b/docs/content/reference/changesets.md @@ -13,7 +13,7 @@ type Mutation { updateUser(id: ID!, changes: UserChanges!): User } -type UserChanges { +input UserChanges { name: String email: String } @@ -36,6 +36,9 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id int, changes map[s } ``` +Please note that map values are automatically coerced to the types defined in the schema. +This means that optional, nested inputs or scalars will conform to their expected types. + We often use the mapstructure library to directly apply these changesets directly to the object using reflection: ```go diff --git a/docs/content/reference/dataloaders.md b/docs/content/reference/dataloaders.md index 15cfda3e3e..d28be2a8fe 100644 --- a/docs/content/reference/dataloaders.md +++ b/docs/content/reference/dataloaders.md @@ -18,22 +18,30 @@ query { todos { user { name } } } and the `todo.user` resolver reads the `User` from a database... ```go func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) { - res := db.LogAndQuery( - r.Conn, - "SELECT id, name FROM users WHERE id = ?", - obj.UserID, - ) - defer res.Close() - - if !res.Next() { - return nil, nil + stmt, err := r.db.PrepareContext(ctx, "SELECT id, name FROM users WHERE id = ?") + if err != nil { + return nil, err + } + defer stmt.Close() + + rows, err := stmt.QueryContext(ctx, obj.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + return nil, rows.Err() } + var user model.User - if err := res.Scan(&user.ID, &user.Name); err != nil { - panic(err) + if err := rows.Scan(&user.ID, &user.Name); err != nil { + return nil, err } return &user, nil } + + ``` The query executor will call the `Query.Todos` resolver which does a `select * from todo` and returns `N` todos. If the nested `User` is selected, the above `UserRaw` resolver will run a separate query for each user, resulting in `N+1` database queries. @@ -58,17 +66,23 @@ Dataloaders allow us to consolidate the fetching of `todo.user` across all resol We're going to use [graph-gophers/dataloader](https://github.com/graph-gophers/dataloader) to implement a dataloader for bulk-fetching users. ```bash -go get -u github.com/graph-gophers/dataloader +go get -u github.com/graph-gophers/dataloader/v7 ``` Next, we implement a data loader and a middleware for injecting the data loader on a request context. ```go -package storage +package loaders // import graph gophers with your other imports import ( - "github.com/graph-gophers/dataloader" + "context" + "database/sql" + "net/http" + "strings" + "time" + + "github.com/graph-gophers/dataloader/v7" ) type ctxKey string @@ -77,71 +91,67 @@ const ( loadersKey = ctxKey("dataloaders") ) -// UserReader reads Users from a database -type UserReader struct { - conn *sql.DB +// userReader reads Users from a database +type userReader struct { + db *sql.DB } -// GetUsers implements a batch function that can retrieve many users by ID, +// getUsers implements a batch function that can retrieve many users by ID, // for use in a dataloader -func (u *UserReader) GetUsers(ctx context.Context, keys dataloader.Keys) []*dataloader.Result { - // read all requested users in a single query - userIDs := make([]string, len(keys)) - for ix, key := range keys { - userIDs[ix] = key.String() +func (u *userReader) getUsers(ctx context.Context, userIds []string) []*dataloader.Result[*model.User] { + stmt, err := u.db.PrepareContext(ctx, `SELECT id, name FROM users WHERE id IN (?`+strings.Repeat(",?", len(userIds)-1)+`)`) + if err != nil { + return handleError[*model.User](len(userIds), err) } - res := u.db.Exec( - r.Conn, - "SELECT id, name - FROM users - WHERE id IN (?" + strings.Repeat(",?", len(userIDs-1)) + ")", - userIDs..., - ) - defer res.Close() - // return User records into a map by ID - userById := map[string]*model.User{} - for res.Next() { - user := model.User{} - if err := res.Scan(&user.ID, &user.Name); err != nil { - panic(err) - } - userById[user.ID] = &user + defer stmt.Close() + + rows, err := stmt.QueryContext(ctx, userIds) + if err != nil { + return handleError[*model.User](len(userIds), err) } - // return users in the same order requested - output := make([]*dataloader.Result, len(keys)) - for index, userKey := range keys { - user, ok := userById[userKey.String()] - if ok { - output[index] = &dataloader.Result{Data: user, Error: nil} - } else { - err := fmt.Errorf("user not found %s", userKey.String()) - output[index] = &dataloader.Result{Data: nil, Error: err} + defer rows.Close() + + result := make([]*dataloader.Result[*model.User], 0, len(userIds)) + for rows.Next() { + var user model.User + if err := rows.Scan(&user.ID, &user.Name); err != nil { + result = append(result, &dataloader.Result[*model.User]{Error: err}) + continue } + result = append(result, &dataloader.Result[*model.User]{Data: &user}) } - return output + return result +} + +// handleError creates array of result with the same error repeated for as many items requested +func handleError[T any](itemsLength int, err error) []*dataloader.Result[T] { + result := make([]*dataloader.Result[T], itemsLength) + for i := 0; i < itemsLength; i++ { + result[i] = &dataloader.Result[T]{Error: err} + } + return result } // Loaders wrap your data loaders to inject via middleware type Loaders struct { - UserLoader *dataloader.Loader + UserLoader *dataloader.Loader[string, *model.User] } // NewLoaders instantiates data loaders for the middleware func NewLoaders(conn *sql.DB) *Loaders { // define the data loader - userReader := &UserReader{conn: conn} - loaders := &Loaders{ - UserLoader: dataloader.NewBatchedLoader(userReader.GetUsers), + ur := &userReader{db: conn} + return &Loaders{ + UserLoader: dataloader.NewBatchedLoader(ur.getUsers, dataloader.WithWait[string, *model.User](time.Millisecond)), } - return loaders } // Middleware injects data loaders into the context -func Middleware(loaders *Loaders, next http.Handler) http.Handler { +func Middleware(conn *sql.DB, next http.Handler) http.Handler { // return a middleware that injects the loader to the request context return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - nextCtx := context.WithValue(r.Context(), loadersKey, loaders) - r = r.WithContext(nextCtx) + loader := NewLoaders(conn) + r = r.WithContext(context.WithValue(r.Context(), loadersKey, loader)) next.ServeHTTP(w, r) }) } @@ -151,23 +161,35 @@ func For(ctx context.Context) *Loaders { return ctx.Value(loadersKey).(*Loaders) } -// GetUser wraps the User dataloader for efficient retrieval by user ID +// GetUser returns single user by id efficiently func GetUser(ctx context.Context, userID string) (*model.User, error) { loaders := For(ctx) - thunk := loaders.UserLoader.Load(ctx, dataloader.StringKey(userID)) - result, err := thunk() - if err != nil { - return nil, err - } - return result.(*model.User), nil + return loaders.UserLoader.Load(ctx, userID)() +} + +// GetUsers returns many users by ids efficiently +func GetUsers(ctx context.Context, userIDs []string) ([]*model.User, []error) { + loaders := For(ctx) + return loaders.UserLoader.LoadMany(ctx, userIDs)() } ``` +Add the dataloader middleware to your server... +```go +// create the query handler +var srv http.Handler = handler.NewDefaultServer(generated.NewExecutableSchema(...)) +// wrap the query handler with middleware to inject dataloader in requests. +// pass in your dataloader dependencies, in this case the db connection. +srv = loaders.Middleware(db, srv) +// register the wrapped handler +http.Handle("/query", srv) +``` + Now lets update our resolver to call the dataloader: ```go func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) { - return storage.GetUser(ctx, obj.UserID) + return loaders.GetUser(ctx, obj.UserID) } ``` diff --git a/docs/content/reference/scalars.md b/docs/content/reference/scalars.md index 8241090b6a..137d3db8f6 100644 --- a/docs/content/reference/scalars.md +++ b/docs/content/reference/scalars.md @@ -7,7 +7,8 @@ menu: { main: { parent: "reference", weight: 10 } } ## Built-in helpers -gqlgen ships with some built-in helpers for common custom scalar use-cases, `Time`, `Any`, `Upload` and `Map`. Adding any of these to a schema will automatically add the marshalling behaviour to Go types. +gqlgen ships with some built-in helpers for common custom scalar use-cases, `Time`, `Any`, `Upload` and `Map`. +Adding any of these to a schema will automatically add the marshalling behaviour to Go types. ### Time @@ -15,7 +16,27 @@ gqlgen ships with some built-in helpers for common custom scalar use-cases, `Tim scalar Time ``` -Maps a `Time` GraphQL scalar to a Go `time.Time` struct. This scalar adheres to the [time.RFC3339Nano](https://pkg.go.dev/time#pkg-constants) format. +Maps a `Time` GraphQL scalar to a Go `time.Time` struct. +This scalar adheres to the [time.RFC3339Nano](https://pkg.go.dev/time#pkg-constants) format. + +### Universally Unique Identifier (UUID) + +```graphql +scalar UUID +``` +This maps a `UUID` scalar value to a `uuid.UUID` type. + +If you add to gqlgen.yml: +```yaml +models: + UUID: + model: + - github.com/99designs/gqlgen/graphql.UUID +``` + +And then add `scalar UUID` to `schema.graphql` + +See the _examples/uuid package for more examples. ### Map @@ -50,6 +71,23 @@ scalar Any Maps an arbitrary GraphQL value to a `interface{}` Go type. +### Duration + +```graphql +scalar Duration +``` +This maps a `Duration` scalar value conforming to the `ISO8601` standard (ex.: `P1Y2D`) to a `time.Duration` type. + +If you add to gqlgen.yml: +```yaml +models: + Duration: + model: + - github.com/99designs/gqlgen/graphql.Duration +``` + +And then add `scalar Duration` to `schema.graphql` + ## Custom scalars with user defined types For user defined types you can implement the [graphql.Marshaler](https://pkg.go.dev/github.com/99designs/gqlgen/graphql#Marshaler) and [graphql.Unmarshaler](https://pkg.go.dev/github.com/99designs/gqlgen/graphql#Unmarshaler) or implement the [graphql.ContextMarshaler](https://pkg.go.dev/github.com/99designs/gqlgen/graphql#ContextMarshaler) and [graphql.ContextUnmarshaler](https://pkg.go.dev/github.com/99designs/gqlgen/graphql#ContextUnmarshaler) interfaces and they will be called. @@ -131,7 +169,7 @@ func ParseLength(string) (Length, error) func (l Length) FormatContext(ctx context.Context) (string, error) ``` -and then wire up the type in .gqlgen.yml or via directives like normal: +and then wire up the type in `.gqlgen.yml` or via directives like normal: ```yaml models: @@ -141,8 +179,8 @@ models: ## Custom scalars with third party types -Sometimes you are unable to add add methods to a type - perhaps you don't own the type, or it is part of the standard -library (eg string or time.Time). To support this we can build an external marshaler: +Sometimes you are unable to add add methods to a type — perhaps you don't own the type, or it is part of the standard +library (eg `string` or `time.Time`). To support this we can build an external marshaler: ```go package mypkg @@ -180,7 +218,7 @@ func UnmarshalMyCustomBooleanScalar(v interface{}) (bool, error) { } ``` -Then in .gqlgen.yml point to the name without the Marshal|Unmarshal in front: +Then in `.gqlgen.yml` point to the name without the Marshal|Unmarshal in front: ```yaml models: @@ -188,10 +226,10 @@ models: model: github.com/me/mypkg.MyCustomBooleanScalar ``` -**Note:** you also can un/marshal to pointer types via this approach, simply accept a pointer in your +**Note:** You also can (un)marshal to pointer types via this approach, simply accept a pointer in your `Marshal...` func and return one in your `Unmarshal...` func. -**Note:** you can also un/marshal with a context by having your custom marshal function return a +**Note:** You can also (un)marshal with a context by having your custom marshal function return a `graphql.ContextMarshaler` _and_ your unmarshal function take a `context.Context` as the first argument. See the [_examples/scalars](https://github.com/99designs/gqlgen/tree/master/_examples/scalars) package for more examples. @@ -199,7 +237,7 @@ See the [_examples/scalars](https://github.com/99designs/gqlgen/tree/master/_exa ## Marshaling/Unmarshaling Errors The errors that occur as part of custom scalar marshaling/unmarshaling will return a full path to the field. -For example, given the following schema ... +For example, given the following schema: ```graphql extend type Mutation{ @@ -213,6 +251,7 @@ input UserInput { } scalar Email + input ContactDetailsInput { email: Email! } @@ -221,7 +260,6 @@ input ContactDetailsInput { ... and the following variables: ```json - { "userInput": { "name": "George", @@ -235,7 +273,9 @@ input ContactDetailsInput { } ``` -... and an unmarshal function that returns an error if the email is invalid. The mutation will return an error containing the full path: +... and an unmarshal function that returns an error if the email is invalid. +The mutation will return an error containing the full path: + ```json { "message": "email invalid", diff --git a/go.mod b/go.mod index 62fd40b813..b2bbd184b1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/99designs/gqlgen go 1.18 require ( + github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru/v2 v2.0.3 github.com/kevinmbeaulieu/eq-go v1.0.0 @@ -11,9 +12,10 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.19 github.com/mitchellh/mapstructure v1.5.0 + github.com/sosodev/duration v1.1.0 github.com/stretchr/testify v1.8.2 github.com/urfave/cli/v2 v2.25.5 - github.com/vektah/gqlparser/v2 v2.5.7 + github.com/vektah/gqlparser/v2 v2.5.10 golang.org/x/text v0.9.0 golang.org/x/tools v0.9.3 google.golang.org/protobuf v1.30.0 diff --git a/go.sum b/go.sum index 309f798936..46bd6ffb74 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= @@ -14,17 +13,14 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.3 h1:kmRrRLlInXvng0SmLxmQpQkpbYAvcXm7NPDrgxJa9mE= github.com/hashicorp/golang-lru/v2 v2.0.3/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/kevinmbeaulieu/eq-go v1.0.0 h1:AQgYHURDOmnVJ62jnEk0W/7yFKEn+Lv8RHN6t7mB0Zo= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/matryer/moq v0.2.7 h1:RtpiPUM8L7ZSCbSwK+QcZH/E9tgqAkFjKQxsRs25b4w= @@ -41,19 +37,19 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.1.0 h1:kQcaiGbJaIsRqgQy7VGlZrVw1giWO+lDoX3MCPnpVO4= +github.com/sosodev/duration v1.1.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc= github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= -github.com/vektah/gqlparser/v2 v2.5.7 h1:QnW4lWFSaycZ1jqvVaQ/tDXGGzQfqAuWdyC4S9g/KVM= -github.com/vektah/gqlparser/v2 v2.5.7/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU= +github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -97,12 +93,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/graphql/context_operation.go b/graphql/context_operation.go index 77a42b84b7..3e6a221b0b 100644 --- a/graphql/context_operation.go +++ b/graphql/context_operation.go @@ -73,8 +73,8 @@ func WithOperationContext(ctx context.Context, rc *OperationContext) context.Con // // Some errors can happen outside of an operation, eg json unmarshal errors. func HasOperationContext(ctx context.Context) bool { - _, ok := ctx.Value(operationCtx).(*OperationContext) - return ok + val, ok := ctx.Value(operationCtx).(*OperationContext) + return ok && val != nil } // This is just a convenient wrapper method for CollectFields diff --git a/graphql/context_operation_test.go b/graphql/context_operation_test.go index 4ce374601f..cd4e61e716 100644 --- a/graphql/context_operation_test.go +++ b/graphql/context_operation_test.go @@ -3,11 +3,33 @@ package graphql import ( "context" "testing" + "time" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2/ast" ) +// implement context.Context interface +type testGraphRequestContext struct { + opContext *OperationContext +} + +func (t *testGraphRequestContext) Deadline() (deadline time.Time, ok bool) { + return time.Time{}, false +} + +func (t *testGraphRequestContext) Done() <-chan struct{} { + return nil +} + +func (t *testGraphRequestContext) Err() error { + return nil +} + +func (t *testGraphRequestContext) Value(key interface{}) interface{} { + return t.opContext +} + func TestGetOperationContext(t *testing.T) { rc := &OperationContext{} @@ -26,6 +48,15 @@ func TestGetOperationContext(t *testing.T) { GetOperationContext(ctx) }) }) + + t.Run("with nil operation context", func(t *testing.T) { + ctx := &testGraphRequestContext{opContext: nil} + + require.False(t, HasOperationContext(ctx)) + require.Panics(t, func() { + GetOperationContext(ctx) + }) + }) } func TestCollectAllFields(t *testing.T) { diff --git a/graphql/duration.go b/graphql/duration.go new file mode 100644 index 0000000000..3eb392db87 --- /dev/null +++ b/graphql/duration.go @@ -0,0 +1,27 @@ +package graphql + +import ( + "fmt" + "time" + + dur "github.com/sosodev/duration" +) + +// UnmarshalDuration returns the duration from a string in ISO8601 format +func UnmarshalDuration(v interface{}) (time.Duration, error) { + input, ok := v.(string) + if !ok { + return 0, fmt.Errorf("input must be a string") + } + + d2, err := dur.Parse(input) + if err != nil { + return 0, err + } + return d2.ToTimeDuration(), nil +} + +// MarshalDuration returns the duration on ISO8601 format +func MarshalDuration(d time.Duration) Marshaler { + return MarshalString(dur.Format(d)) +} diff --git a/graphql/duration_test.go b/graphql/duration_test.go new file mode 100644 index 0000000000..56b6031f71 --- /dev/null +++ b/graphql/duration_test.go @@ -0,0 +1,26 @@ +package graphql + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDurationMarshaling(t *testing.T) { + t.Run("UnmarshalDuration", func(t *testing.T) { + d, err := UnmarshalDuration("P2Y") + assert.NoError(t, err) + + assert.Equal(t, float64(365*24*2), d.Hours()) + }) + t.Run("MarshalDuration", func(t *testing.T) { + m := MarshalDuration(time.Hour * 365 * 24 * 2) + + buf := new(bytes.Buffer) + m.MarshalGQL(buf) + + assert.Equal(t, "\"P2Y\"", buf.String()) + }) +} diff --git a/graphql/executor/executor.go b/graphql/executor/executor.go index c46a007b99..ef0603eaa0 100644 --- a/graphql/executor/executor.go +++ b/graphql/executor/executor.go @@ -3,12 +3,13 @@ package executor import ( "context" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/errcode" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/parser" "github.com/vektah/gqlparser/v2/validator" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" ) // Executor executes graphql queries against a schema. diff --git a/graphql/executor/executor_test.go b/graphql/executor/executor_test.go index cf1944bd9d..79530c1fc7 100644 --- a/graphql/executor/executor_test.go +++ b/graphql/executor/executor_test.go @@ -4,14 +4,15 @@ import ( "context" "testing" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/errcode" - "github.com/99designs/gqlgen/graphql/executor/testexecutor" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/parser" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" + "github.com/99designs/gqlgen/graphql/executor/testexecutor" ) func TestExecutor(t *testing.T) { diff --git a/graphql/executor/testexecutor/testexecutor.go b/graphql/executor/testexecutor/testexecutor.go index bf8fd9850f..a3e0a300dc 100644 --- a/graphql/executor/testexecutor/testexecutor.go +++ b/graphql/executor/testexecutor/testexecutor.go @@ -8,10 +8,11 @@ import ( "io" "time" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/executor" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/executor" ) type MockResponse struct { diff --git a/graphql/handler/apollofederatedtracingv1/tracing.go b/graphql/handler/apollofederatedtracingv1/tracing.go index b186201beb..1c969b4f64 100644 --- a/graphql/handler/apollofederatedtracingv1/tracing.go +++ b/graphql/handler/apollofederatedtracingv1/tracing.go @@ -5,8 +5,9 @@ import ( "encoding/base64" "fmt" - "github.com/99designs/gqlgen/graphql" "google.golang.org/protobuf/proto" + + "github.com/99designs/gqlgen/graphql" ) type ( diff --git a/graphql/handler/apollofederatedtracingv1/tracing_test.go b/graphql/handler/apollofederatedtracingv1/tracing_test.go index 7217217ed6..aa05de7127 100644 --- a/graphql/handler/apollofederatedtracingv1/tracing_test.go +++ b/graphql/handler/apollofederatedtracingv1/tracing_test.go @@ -12,6 +12,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2/gqlerror" + "google.golang.org/protobuf/proto" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1" "github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1/generated" @@ -19,10 +24,6 @@ import ( "github.com/99designs/gqlgen/graphql/handler/lru" "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser/v2/gqlerror" - "google.golang.org/protobuf/proto" ) type alwaysError struct{} diff --git a/graphql/handler/apollofederatedtracingv1/tree_builder.go b/graphql/handler/apollofederatedtracingv1/tree_builder.go index a54b12db58..e211e626c0 100644 --- a/graphql/handler/apollofederatedtracingv1/tree_builder.go +++ b/graphql/handler/apollofederatedtracingv1/tree_builder.go @@ -6,9 +6,10 @@ import ( "sync" "time" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler/apollofederatedtracingv1/generated" - "google.golang.org/protobuf/types/known/timestamppb" ) type TreeBuilder struct { diff --git a/graphql/handler/apollotracing/tracer.go b/graphql/handler/apollotracing/tracer.go index d1e92bbfdc..0d5fc597c0 100644 --- a/graphql/handler/apollotracing/tracer.go +++ b/graphql/handler/apollotracing/tracer.go @@ -5,8 +5,9 @@ import ( "sync" "time" - "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" ) type ( diff --git a/graphql/handler/apollotracing/tracer_test.go b/graphql/handler/apollotracing/tracer_test.go index 789448cae9..74e4e6e127 100644 --- a/graphql/handler/apollotracing/tracer_test.go +++ b/graphql/handler/apollotracing/tracer_test.go @@ -9,16 +9,17 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler/apollotracing" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/lru" "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser/v2/ast" - "github.com/vektah/gqlparser/v2/gqlerror" ) type alwaysError struct{} diff --git a/graphql/handler/extension/apq.go b/graphql/handler/extension/apq.go index 866276eed9..465c2ada61 100644 --- a/graphql/handler/extension/apq.go +++ b/graphql/handler/extension/apq.go @@ -6,12 +6,11 @@ import ( "encoding/hex" "fmt" - "github.com/99designs/gqlgen/graphql/errcode" - + "github.com/mitchellh/mapstructure" "github.com/vektah/gqlparser/v2/gqlerror" "github.com/99designs/gqlgen/graphql" - "github.com/mitchellh/mapstructure" + "github.com/99designs/gqlgen/graphql/errcode" ) const ( diff --git a/graphql/handler/extension/apq_test.go b/graphql/handler/extension/apq_test.go index 66970c62e9..92b06a5133 100644 --- a/graphql/handler/extension/apq_test.go +++ b/graphql/handler/extension/apq_test.go @@ -5,11 +5,12 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/require" ) func TestAPQIntegration(t *testing.T) { diff --git a/graphql/handler/extension/complexity.go b/graphql/handler/extension/complexity.go index e88848d18c..a5b6a60409 100644 --- a/graphql/handler/extension/complexity.go +++ b/graphql/handler/extension/complexity.go @@ -4,10 +4,11 @@ import ( "context" "fmt" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/99designs/gqlgen/complexity" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/errcode" - "github.com/vektah/gqlparser/v2/gqlerror" ) const errComplexityLimit = "COMPLEXITY_LIMIT_EXCEEDED" diff --git a/graphql/handler/extension/complexity_test.go b/graphql/handler/extension/complexity_test.go index e533403e1a..5141f43ee4 100644 --- a/graphql/handler/extension/complexity_test.go +++ b/graphql/handler/extension/complexity_test.go @@ -7,11 +7,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/require" ) func TestHandlerComplexity(t *testing.T) { diff --git a/graphql/handler/extension/introspection.go b/graphql/handler/extension/introspection.go index acc5db2fbc..8e3912651d 100644 --- a/graphql/handler/extension/introspection.go +++ b/graphql/handler/extension/introspection.go @@ -3,8 +3,9 @@ package extension import ( "context" - "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/99designs/gqlgen/graphql" ) // EnableIntrospection enables clients to reflect all of the types available on the graph. diff --git a/graphql/handler/extension/introspection_test.go b/graphql/handler/extension/introspection_test.go index e001eb529f..f719c262a6 100644 --- a/graphql/handler/extension/introspection_test.go +++ b/graphql/handler/extension/introspection_test.go @@ -4,8 +4,9 @@ import ( "context" "testing" - "github.com/99designs/gqlgen/graphql" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/graphql" ) func TestIntrospection(t *testing.T) { diff --git a/graphql/handler/lru/lru.go b/graphql/handler/lru/lru.go index 68241ababf..6ae8a38e64 100644 --- a/graphql/handler/lru/lru.go +++ b/graphql/handler/lru/lru.go @@ -3,8 +3,9 @@ package lru import ( "context" - "github.com/99designs/gqlgen/graphql" lru "github.com/hashicorp/golang-lru/v2" + + "github.com/99designs/gqlgen/graphql" ) type LRU struct { diff --git a/graphql/handler/server.go b/graphql/handler/server.go index b6524d8da1..fd365ccbbf 100644 --- a/graphql/handler/server.go +++ b/graphql/handler/server.go @@ -7,12 +7,13 @@ import ( "net/http" "time" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/executor" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/lru" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/vektah/gqlparser/v2/gqlerror" ) type ( diff --git a/graphql/handler/server_test.go b/graphql/handler/server_test.go index b1ac60fb49..4b31c09ac4 100644 --- a/graphql/handler/server_test.go +++ b/graphql/handler/server_test.go @@ -8,14 +8,15 @@ import ( "net/url" "testing" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/handler/testserver" - "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/parser" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler/testserver" + "github.com/99designs/gqlgen/graphql/handler/transport" ) func TestServer(t *testing.T) { diff --git a/graphql/handler/testserver/testserver.go b/graphql/handler/testserver/testserver.go index 9556d6828a..85dc7d2899 100644 --- a/graphql/handler/testserver/testserver.go +++ b/graphql/handler/testserver/testserver.go @@ -5,10 +5,11 @@ import ( "fmt" "time" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/handler" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler" ) // New provides a server for use in tests that isn't relying on generated code. It isnt a perfect reproduction of diff --git a/graphql/handler/transport/error.go b/graphql/handler/transport/error.go index b1aeaf144d..18f09f5567 100644 --- a/graphql/handler/transport/error.go +++ b/graphql/handler/transport/error.go @@ -5,8 +5,9 @@ import ( "fmt" "net/http" - "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/99designs/gqlgen/graphql" ) // SendError sends a best effort error to a raw response writer. It assumes the client can understand the standard diff --git a/graphql/handler/transport/headers_test.go b/graphql/handler/transport/headers_test.go index 4673522de7..93fa4bf6b6 100644 --- a/graphql/handler/transport/headers_test.go +++ b/graphql/handler/transport/headers_test.go @@ -6,14 +6,15 @@ import ( "net/http/httptest" "testing" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/handler" - "github.com/99designs/gqlgen/graphql/handler/testserver" - "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/testserver" + "github.com/99designs/gqlgen/graphql/handler/transport" ) func TestHeadersWithPOST(t *testing.T) { diff --git a/graphql/handler/transport/http_form_multipart_test.go b/graphql/handler/transport/http_form_multipart_test.go index 4eb636378b..6d7c327933 100644 --- a/graphql/handler/transport/http_form_multipart_test.go +++ b/graphql/handler/transport/http_form_multipart_test.go @@ -11,12 +11,13 @@ import ( "net/textproto" "testing" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/handler" - "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/transport" ) func TestFileUpload(t *testing.T) { diff --git a/graphql/handler/transport/http_form_urlencode_test.go b/graphql/handler/transport/http_form_urlencode_test.go index 8eecf449e1..fb984167a0 100644 --- a/graphql/handler/transport/http_form_urlencode_test.go +++ b/graphql/handler/transport/http_form_urlencode_test.go @@ -7,9 +7,10 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" ) func TestUrlEncodedForm(t *testing.T) { diff --git a/graphql/handler/transport/http_get.go b/graphql/handler/transport/http_get.go index 324fd98683..9a47bfbef8 100644 --- a/graphql/handler/transport/http_get.go +++ b/graphql/handler/transport/http_get.go @@ -7,10 +7,11 @@ import ( "net/url" "strings" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/errcode" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" ) // GET implements the GET side of the default HTTP transport diff --git a/graphql/handler/transport/http_get_test.go b/graphql/handler/transport/http_get_test.go index 5e46d23185..6e0dde07f5 100644 --- a/graphql/handler/transport/http_get_test.go +++ b/graphql/handler/transport/http_get_test.go @@ -4,9 +4,10 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" ) func TestGET(t *testing.T) { diff --git a/graphql/handler/transport/http_graphql_test.go b/graphql/handler/transport/http_graphql_test.go index e1add4a498..53fb891f00 100644 --- a/graphql/handler/transport/http_graphql_test.go +++ b/graphql/handler/transport/http_graphql_test.go @@ -7,9 +7,10 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" ) func TestGRAPHQL(t *testing.T) { diff --git a/graphql/handler/transport/http_post_test.go b/graphql/handler/transport/http_post_test.go index a26ba12912..a7ecd7d5eb 100644 --- a/graphql/handler/transport/http_post_test.go +++ b/graphql/handler/transport/http_post_test.go @@ -7,9 +7,10 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/99designs/gqlgen/graphql/handler/testserver" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/stretchr/testify/assert" ) func TestPOST(t *testing.T) { diff --git a/graphql/handler/transport/util.go b/graphql/handler/transport/util.go index ce845c1964..19b7521c08 100644 --- a/graphql/handler/transport/util.go +++ b/graphql/handler/transport/util.go @@ -5,8 +5,9 @@ import ( "fmt" "io" - "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/99designs/gqlgen/graphql" ) func writeJson(w io.Writer, response *graphql.Response) { diff --git a/graphql/handler/transport/websocket.go b/graphql/handler/transport/websocket.go index acd124fe8e..e1334b9290 100644 --- a/graphql/handler/transport/websocket.go +++ b/graphql/handler/transport/websocket.go @@ -12,10 +12,11 @@ import ( "sync" "time" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/errcode" "github.com/gorilla/websocket" "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" ) type ( @@ -26,7 +27,17 @@ type ( ErrorFunc WebsocketErrorFunc CloseFunc WebsocketCloseFunc KeepAlivePingInterval time.Duration + PongOnlyInterval time.Duration PingPongInterval time.Duration + /* If PingPongInterval has a non-0 duration, then when the server sends a ping + * it sets a ReadDeadline of PingPongInterval*2 and if the client doesn't respond + * with pong before that deadline is reached then the connection will die with a + * 1006 error code. + * + * MissingPongOk if true, tells the server to not use a ReadDeadline such that a + * missing/slow pong response from the client doesn't kill the connection. + */ + MissingPongOk bool didInjectSubprotocols bool } @@ -38,13 +49,16 @@ type ( active map[string]context.CancelFunc mu sync.Mutex keepAliveTicker *time.Ticker + pongOnlyTicker *time.Ticker pingPongTicker *time.Ticker + receivedPong bool exec graphql.GraphExecutor + closed bool initPayload InitPayload } - WebsocketInitFunc func(ctx context.Context, initPayload InitPayload) (context.Context, error) + WebsocketInitFunc func(ctx context.Context, initPayload InitPayload) (context.Context, *InitPayload, error) WebsocketErrorFunc func(ctx context.Context, err error) // Callback called when websocket is closed. @@ -179,8 +193,10 @@ func (c *wsConnection) init() bool { } } + var initAckPayload *InitPayload = nil if c.InitFunc != nil { - ctx, err := c.InitFunc(c.ctx, c.initPayload) + var ctx context.Context + ctx, initAckPayload, err = c.InitFunc(c.ctx, c.initPayload) if err != nil { c.sendConnectionError(err.Error()) c.close(websocket.CloseNormalClosure, "terminated") @@ -189,7 +205,15 @@ func (c *wsConnection) init() bool { c.ctx = ctx } - c.write(&message{t: connectionAckMessageType}) + if initAckPayload != nil { + initJsonAckPayload, err := json.Marshal(*initAckPayload) + if err != nil { + panic(err) + } + c.write(&message{t: connectionAckMessageType, payload: initJsonAckPayload}) + } else { + c.write(&message{t: connectionAckMessageType}) + } c.write(&message{t: keepAliveMessageType}) case connectionCloseMessageType: c.close(websocket.CloseNormalClosure, "terminated") @@ -228,16 +252,28 @@ func (c *wsConnection) run() { go c.keepAlive(ctx) } + // If we're running in graphql-transport-ws mode, create a timer that will trigger a + // just a pong message every interval + if c.conn.Subprotocol() == graphqltransportwsSubprotocol && c.PongOnlyInterval != 0 { + c.mu.Lock() + c.pongOnlyTicker = time.NewTicker(c.PongOnlyInterval) + c.mu.Unlock() + + go c.keepAlivePongOnly(ctx) + } + // If we're running in graphql-transport-ws mode, create a timer that will - // trigger a ping message every interval + // trigger a ping message every interval and expect a pong! if c.conn.Subprotocol() == graphqltransportwsSubprotocol && c.PingPongInterval != 0 { c.mu.Lock() c.pingPongTicker = time.NewTicker(c.PingPongInterval) c.mu.Unlock() - // Note: when the connection is closed by this deadline, the client - // will receive an "invalid close code" - c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval)) + if !c.MissingPongOk { + // Note: when the connection is closed by this deadline, the client + // will receive an "invalid close code" + c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval)) + } go c.ping(ctx) } @@ -272,7 +308,11 @@ func (c *wsConnection) run() { case pingMessageType: c.write(&message{t: pongMessageType, payload: m.payload}) case pongMessageType: - c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval)) + c.mu.Lock() + c.receivedPong = true + c.mu.Unlock() + // Clear ReadTimeout -- 0 time val clears. + c.conn.SetReadDeadline(time.Time{}) default: c.sendConnectionError("unexpected message %s", m.t) c.close(websocket.CloseProtocolError, "unexpected message") @@ -281,6 +321,18 @@ func (c *wsConnection) run() { } } +func (c *wsConnection) keepAlivePongOnly(ctx context.Context) { + for { + select { + case <-ctx.Done(): + c.pongOnlyTicker.Stop() + return + case <-c.pongOnlyTicker.C: + c.write(&message{t: pongMessageType, payload: json.RawMessage{}}) + } + } +} + func (c *wsConnection) keepAlive(ctx context.Context) { for { select { @@ -301,6 +353,14 @@ func (c *wsConnection) ping(ctx context.Context) { return case <-c.pingPongTicker.C: c.write(&message{t: pingMessageType, payload: json.RawMessage{}}) + // The initial deadline for this method is set in run() + // if we have not yet received a pong, don't reset the deadline. + c.mu.Lock() + if !c.MissingPongOk && c.receivedPong { + c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval)) + } + c.receivedPong = false + c.mu.Unlock() } } } @@ -431,10 +491,15 @@ func (c *wsConnection) sendConnectionError(format string, args ...interface{}) { func (c *wsConnection) close(closeCode int, message string) { c.mu.Lock() + if c.closed { + c.mu.Unlock() + return + } _ = c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(closeCode, message)) for _, closer := range c.active { closer() } + c.closed = true c.mu.Unlock() _ = c.conn.Close() diff --git a/graphql/handler/transport/websocket_test.go b/graphql/handler/transport/websocket_test.go index 9ac8ad65cc..e933677248 100644 --- a/graphql/handler/transport/websocket_test.go +++ b/graphql/handler/transport/websocket_test.go @@ -10,16 +10,17 @@ import ( "testing" "time" - "github.com/99designs/gqlgen/client" - "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/handler" - "github.com/99designs/gqlgen/graphql/handler/testserver" - "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/testserver" + "github.com/99designs/gqlgen/graphql/handler/transport" ) type ckey string @@ -207,8 +208,8 @@ func TestWebsocketInitFunc(t *testing.T) { t.Run("accept connection if WebsocketInitFunc is provided and is accepting connection", func(t *testing.T) { h := testserver.New() h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { - return context.WithValue(ctx, ckey("newkey"), "newvalue"), nil + InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) { + return context.WithValue(ctx, ckey("newkey"), "newvalue"), nil, nil }, }) srv := httptest.NewServer(h) @@ -226,8 +227,8 @@ func TestWebsocketInitFunc(t *testing.T) { t.Run("reject connection if WebsocketInitFunc is provided and is accepting connection", func(t *testing.T) { h := testserver.New() h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { - return ctx, errors.New("invalid init payload") + InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) { + return ctx, nil, errors.New("invalid init payload") }, }) srv := httptest.NewServer(h) @@ -261,8 +262,8 @@ func TestWebsocketInitFunc(t *testing.T) { h := handler.New(es) h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { - return context.WithValue(ctx, ckey("newkey"), "newvalue"), nil + InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) { + return context.WithValue(ctx, ckey("newkey"), "newvalue"), nil, nil }, }) @@ -282,7 +283,7 @@ func TestWebsocketInitFunc(t *testing.T) { h := testserver.New() var cancel func() h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, _ transport.InitPayload) (newCtx context.Context, _ error) { + InitFunc: func(ctx context.Context, _ transport.InitPayload) (newCtx context.Context, _ *transport.InitPayload, _ error) { newCtx, cancel = context.WithTimeout(transport.AppendCloseReason(ctx, "beep boop"), time.Millisecond*5) return }, @@ -303,6 +304,33 @@ func TestWebsocketInitFunc(t *testing.T) { assert.Equal(t, m.Type, connectionErrorMsg) assert.Equal(t, string(m.Payload), `{"message":"beep boop"}`) }) + t.Run("accept connection if WebsocketInitFunc is provided and is accepting connection", func(t *testing.T) { + h := testserver.New() + h.AddTransport(transport.Websocket{ + InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) { + initResponsePayload := transport.InitPayload{"trackingId": "123-456"} + return context.WithValue(ctx, ckey("newkey"), "newvalue"), &initResponsePayload, nil + }, + }) + srv := httptest.NewServer(h) + defer srv.Close() + + c := wsConnect(srv.URL) + defer c.Close() + + require.NoError(t, c.WriteJSON(&operationMessage{Type: connectionInitMsg})) + + connAck := readOp(c) + assert.Equal(t, connectionAckMsg, connAck.Type) + + var payload map[string]interface{} + err := json.Unmarshal(connAck.Payload, &payload) + if err != nil { + t.Fatal("Unexpected Error", err) + } + assert.EqualValues(t, "123-456", payload["trackingId"]) + assert.Equal(t, connectionKeepAliveMsg, readOp(c).Type) + }) } func TestWebSocketInitTimeout(t *testing.T) { @@ -382,8 +410,8 @@ func TestWebSocketErrorFunc(t *testing.T) { t.Run("init func errors do not call the error handler", func(t *testing.T) { h := testserver.New() h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, _ transport.InitPayload) (context.Context, error) { - return ctx, errors.New("this is not what we agreed upon") + InitFunc: func(ctx context.Context, _ transport.InitPayload) (context.Context, *transport.InitPayload, error) { + return ctx, nil, errors.New("this is not what we agreed upon") }, ErrorFunc: func(_ context.Context, err error) { assert.Fail(t, "the error handler got called when it shouldn't have", "error: "+err.Error()) @@ -400,10 +428,10 @@ func TestWebSocketErrorFunc(t *testing.T) { t.Run("init func context closes do not call the error handler", func(t *testing.T) { h := testserver.New() h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, _ transport.InitPayload) (context.Context, error) { + InitFunc: func(ctx context.Context, _ transport.InitPayload) (context.Context, *transport.InitPayload, error) { newCtx, cancel := context.WithCancel(ctx) time.AfterFunc(time.Millisecond*5, cancel) - return newCtx, nil + return newCtx, nil, nil }, ErrorFunc: func(_ context.Context, err error) { assert.Fail(t, "the error handler got called when it shouldn't have", "error: "+err.Error()) @@ -423,9 +451,9 @@ func TestWebSocketErrorFunc(t *testing.T) { h := testserver.New() var cancel func() h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, _ transport.InitPayload) (newCtx context.Context, _ error) { + InitFunc: func(ctx context.Context, _ transport.InitPayload) (newCtx context.Context, _ *transport.InitPayload, _ error) { newCtx, cancel = context.WithDeadline(ctx, time.Now().Add(time.Millisecond*5)) - return newCtx, nil + return newCtx, nil, nil }, ErrorFunc: func(_ context.Context, err error) { assert.Fail(t, "the error handler got called when it shouldn't have", "error: "+err.Error()) @@ -473,12 +501,45 @@ func TestWebSocketCloseFunc(t *testing.T) { } }) + t.Run("the on close handler gets called only once when the websocket is closed", func(t *testing.T) { + closeFuncCalled := make(chan bool, 1) + h := testserver.New() + h.AddTransport(transport.Websocket{ + CloseFunc: func(_ context.Context, _closeCode int) { + closeFuncCalled <- true + }, + }) + + srv := httptest.NewServer(h) + defer srv.Close() + + c := wsConnect(srv.URL) + require.NoError(t, c.WriteJSON(&operationMessage{Type: connectionInitMsg})) + assert.Equal(t, connectionAckMsg, readOp(c).Type) + assert.Equal(t, connectionKeepAliveMsg, readOp(c).Type) + require.NoError(t, c.WriteJSON(&operationMessage{Type: connectionTerminateMsg})) + + select { + case res := <-closeFuncCalled: + assert.True(t, res) + case <-time.NewTimer(time.Millisecond * 20).C: + assert.Fail(t, "The close handler was not called in time") + } + + select { + case <-closeFuncCalled: + assert.Fail(t, "The close handler was called more than once") + case <-time.NewTimer(time.Millisecond * 20).C: + // ok + } + }) + t.Run("init func errors call the close handler", func(t *testing.T) { h := testserver.New() closeFuncCalled := make(chan bool, 1) h.AddTransport(transport.Websocket{ - InitFunc: func(ctx context.Context, _ transport.InitPayload) (context.Context, error) { - return ctx, errors.New("error during init") + InitFunc: func(ctx context.Context, _ transport.InitPayload) (context.Context, *transport.InitPayload, error) { + return ctx, nil, errors.New("error during init") }, CloseFunc: func(_ context.Context, _closeCode int) { closeFuncCalled <- true @@ -578,7 +639,7 @@ func TestWebsocketWithPingPongInterval(t *testing.T) { } t.Run("client receives ping and responds with pong", func(t *testing.T) { - _, srv := initialize(transport.Websocket{PingPongInterval: 10 * time.Millisecond}) + _, srv := initialize(transport.Websocket{PingPongInterval: 20 * time.Millisecond}) defer srv.Close() c := wsConnectWithSubprocotol(srv.URL, graphqltransportwsSubprotocol) @@ -592,6 +653,11 @@ func TestWebsocketWithPingPongInterval(t *testing.T) { assert.Equal(t, graphqltransportwsPingMsg, readOp(c).Type) }) + t.Run("client sends ping and expects pong", func(t *testing.T) { + _, srv := initialize(transport.Websocket{PingPongInterval: 10 * time.Millisecond}) + defer srv.Close() + }) + t.Run("client sends ping and expects pong", func(t *testing.T) { _, srv := initialize(transport.Websocket{PingPongInterval: 10 * time.Millisecond}) defer srv.Close() @@ -606,6 +672,67 @@ func TestWebsocketWithPingPongInterval(t *testing.T) { assert.Equal(t, graphqltransportwsPongMsg, readOp(c).Type) }) + t.Run("server closes with error if client does not pong and !MissingPongOk", func(t *testing.T) { + h := testserver.New() + closeFuncCalled := make(chan bool, 1) + h.AddTransport(transport.Websocket{ + MissingPongOk: false, // default value but beign explicit for test clarity. + PingPongInterval: 5 * time.Millisecond, + CloseFunc: func(_ context.Context, _closeCode int) { + closeFuncCalled <- true + }, + }) + + srv := httptest.NewServer(h) + defer srv.Close() + + c := wsConnectWithSubprocotol(srv.URL, graphqltransportwsSubprotocol) + defer c.Close() + + require.NoError(t, c.WriteJSON(&operationMessage{Type: graphqltransportwsConnectionInitMsg})) + assert.Equal(t, graphqltransportwsConnectionAckMsg, readOp(c).Type) + + assert.Equal(t, graphqltransportwsPingMsg, readOp(c).Type) + + select { + case res := <-closeFuncCalled: + assert.True(t, res) + case <-time.NewTimer(time.Millisecond * 20).C: + // with a 5ms interval 10ms should be the timeout, double that to make the test less likely to flake under load + assert.Fail(t, "The close handler was not called in time") + } + }) + + t.Run("server does not close with error if client does not pong and MissingPongOk", func(t *testing.T) { + h := testserver.New() + closeFuncCalled := make(chan bool, 1) + h.AddTransport(transport.Websocket{ + MissingPongOk: true, + PingPongInterval: 10 * time.Millisecond, + CloseFunc: func(_ context.Context, _closeCode int) { + closeFuncCalled <- true + }, + }) + + srv := httptest.NewServer(h) + defer srv.Close() + + c := wsConnectWithSubprocotol(srv.URL, graphqltransportwsSubprotocol) + defer c.Close() + + require.NoError(t, c.WriteJSON(&operationMessage{Type: graphqltransportwsConnectionInitMsg})) + assert.Equal(t, graphqltransportwsConnectionAckMsg, readOp(c).Type) + + assert.Equal(t, graphqltransportwsPingMsg, readOp(c).Type) + + select { + case <-closeFuncCalled: + assert.Fail(t, "The close handler was called even with MissingPongOk = true") + case _, ok := <-time.NewTimer(time.Millisecond * 20).C: + assert.True(t, ok) + } + }) + t.Run("ping-pongs are not sent when the graphql-ws sub protocol is used", func(t *testing.T) { // Regression test // --- @@ -632,6 +759,41 @@ func TestWebsocketWithPingPongInterval(t *testing.T) { assert.Equal(t, connectionKeepAliveMsg, readOp(c).Type) assert.Equal(t, connectionKeepAliveMsg, readOp(c).Type) }) + t.Run("pong only messages are sent when configured with graphql-transport-ws", func(t *testing.T) { + + h, srv := initialize(transport.Websocket{PongOnlyInterval: 10 * time.Millisecond}) + defer srv.Close() + + c := wsConnectWithSubprocotol(srv.URL, graphqltransportwsSubprotocol) + defer c.Close() + + require.NoError(t, c.WriteJSON(&operationMessage{Type: graphqltransportwsConnectionInitMsg})) + assert.Equal(t, graphqltransportwsConnectionAckMsg, readOp(c).Type) + + assert.Equal(t, graphqltransportwsPongMsg, readOp(c).Type) + + require.NoError(t, c.WriteJSON(&operationMessage{ + Type: graphqltransportwsSubscribeMsg, + ID: "test_1", + Payload: json.RawMessage(`{"query": "subscription { name }"}`), + })) + + // pong + msg := readOp(c) + assert.Equal(t, graphqltransportwsPongMsg, msg.Type) + + // server message + h.SendNextSubscriptionMessage() + msg = readOp(c) + require.Equal(t, graphqltransportwsNextMsg, msg.Type, string(msg.Payload)) + require.Equal(t, "test_1", msg.ID, string(msg.Payload)) + require.Equal(t, `{"data":{"name":"test"}}`, string(msg.Payload)) + + // keepalive + msg = readOp(c) + assert.Equal(t, graphqltransportwsPongMsg, msg.Type) + }) + } func wsConnect(url string) *websocket.Conn { diff --git a/graphql/introspection/schema.go b/graphql/introspection/schema.go index b7b0ad94e0..897b1a098f 100644 --- a/graphql/introspection/schema.go +++ b/graphql/introspection/schema.go @@ -2,7 +2,6 @@ package introspection import ( "sort" - "strings" "github.com/vektah/gqlparser/v2/ast" ) @@ -22,9 +21,6 @@ func (s *Schema) Types() []Type { typeIndex := map[string]Type{} typeNames := make([]string, 0, len(s.schema.Types)) for _, typ := range s.schema.Types { - if strings.HasPrefix(typ.Name, "__") { - continue - } typeNames = append(typeNames, typ.Name) typeIndex[typ.Name] = *WrapTypeFromDef(s.schema, typ) } diff --git a/graphql/introspection/schema_test.go b/graphql/introspection/schema_test.go new file mode 100644 index 0000000000..92022b1179 --- /dev/null +++ b/graphql/introspection/schema_test.go @@ -0,0 +1,68 @@ +package introspection + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2/ast" +) + +func TestSchema(t *testing.T) { + query := &ast.Definition{ + Name: "Query", + Kind: ast.Object, + } + + mutation := &ast.Definition{ + Name: "Mutation", + Kind: ast.Object, + } + + subscription := &ast.Definition{ + Name: "Subscription", + Kind: ast.Object, + } + + directive := &ast.Definition{ + Name: "__Directive", + Kind: ast.Object, + } + + schema := &Schema{ + schema: &ast.Schema{ + Query: query, + Mutation: mutation, + Subscription: subscription, + Types: map[string]*ast.Definition{ + "Query": query, + "Mutation": mutation, + "__Directive": directive, + }, + Description: "test description", + }, + } + + t.Run("description", func(t *testing.T) { + require.EqualValues(t, "test description", *schema.Description()) + }) + + t.Run("query type", func(t *testing.T) { + require.Equal(t, "Query", *schema.QueryType().Name()) + }) + + t.Run("mutation type", func(t *testing.T) { + require.Equal(t, "Mutation", *schema.MutationType().Name()) + }) + + t.Run("subscription type", func(t *testing.T) { + require.Equal(t, "Subscription", *schema.SubscriptionType().Name()) + }) + + t.Run("types", func(t *testing.T) { + types := schema.Types() + require.Len(t, types, 3) + require.Equal(t, "Mutation", *types[0].Name()) + require.Equal(t, "Query", *types[1].Name()) + require.Equal(t, "__Directive", *types[2].Name()) + }) +} diff --git a/graphql/playground/playground.go b/graphql/playground/playground.go index da17ca6201..1719cd7388 100644 --- a/graphql/playground/playground.go +++ b/graphql/playground/playground.go @@ -58,13 +58,24 @@ var page = template.Must(template.New("graphiql").Parse(` const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'; const subscriptionUrl = wsProto + '//' + location.host + {{.endpoint}}; {{- end}} +{{- if .fetcherHeaders}} + const fetcherHeaders = {{.fetcherHeaders}}; +{{- else}} + const fetcherHeaders = undefined; +{{- end}} +{{- if .uiHeaders}} + const uiHeaders = {{.uiHeaders}}; +{{- else}} + const uiHeaders = undefined; +{{- end}} - const fetcher = GraphiQL.createFetcher({ url, subscriptionUrl }); + const fetcher = GraphiQL.createFetcher({ url, subscriptionUrl, headers: fetcherHeaders }); ReactDOM.render( React.createElement(GraphiQL, { fetcher: fetcher, isHeadersEditorEnabled: true, - shouldPersistHeaders: true + shouldPersistHeaders: true, + headers: JSON.stringify(uiHeaders, null, 2) }), document.getElementById('graphiql'), ); @@ -75,11 +86,20 @@ var page = template.Must(template.New("graphiql").Parse(` // Handler responsible for setting up the playground func Handler(title string, endpoint string) http.HandlerFunc { + return HandlerWithHeaders(title, endpoint, nil, nil) +} + +// HandlerWithHeaders sets up the playground. +// fetcherHeaders are used by the playground's fetcher instance and will not be visible in the UI. +// uiHeaders are default headers that will show up in the UI headers editor. +func HandlerWithHeaders(title string, endpoint string, fetcherHeaders map[string]string, uiHeaders map[string]string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/html; charset=UTF-8") err := page.Execute(w, map[string]interface{}{ "title": title, "endpoint": endpoint, + "fetcherHeaders": fetcherHeaders, + "uiHeaders": uiHeaders, "endpointIsAbsolute": endpointHasScheme(endpoint), "subscriptionEndpoint": getSubscriptionEndpoint(endpoint), "version": "3.0.1", diff --git a/graphql/uuid.go b/graphql/uuid.go new file mode 100644 index 0000000000..e9f22dccdb --- /dev/null +++ b/graphql/uuid.go @@ -0,0 +1,25 @@ +package graphql + +import ( + "fmt" + + "github.com/google/uuid" +) + +func MarshalUUID(id uuid.UUID) Marshaler { + if id == uuid.Nil { + return Null + } + return MarshalString(id.String()) +} + +func UnmarshalUUID(v any) (uuid.UUID, error) { + switch v := v.(type) { + case string: + return uuid.Parse(v) + case []byte: + return uuid.ParseBytes(v) + default: + return uuid.Nil, fmt.Errorf("%T is not a uuid", v) + } +} diff --git a/graphql/uuid_test.go b/graphql/uuid_test.go new file mode 100644 index 0000000000..1d20996449 --- /dev/null +++ b/graphql/uuid_test.go @@ -0,0 +1,56 @@ +package graphql + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestMarshalUUID(t *testing.T) { + t.Run("Null Values", func(t *testing.T) { + assert.Equal(t, "null", m2s(MarshalUUID(uuid.Nil))) + }) + + t.Run("Valid Values", func(t *testing.T) { + + var values = []struct { + input uuid.UUID + expected string + }{ + {uuid.MustParse("fd5343a9-0372-11ee-9fb2-0242ac160014"), "\"fd5343a9-0372-11ee-9fb2-0242ac160014\""}, + } + for _, v := range values { + assert.Equal(t, v.expected, m2s(MarshalUUID(v.input))) + } + }) +} + +func TestUnmarshalUUID(t *testing.T) { + t.Run("Invalid Non-String Values", func(t *testing.T) { + var values = []interface{}{123, 1.2345678901, 1.2e+20, 1.2e-20, true, false, nil} + for _, v := range values { + result, err := UnmarshalUUID(v) + assert.Equal(t, uuid.Nil, result) + assert.ErrorContains(t, err, "is not a uuid") + } + }) + + t.Run("Invalid String Values", func(t *testing.T) { + var values = []struct { + input string + expected string + }{ + {"X50e8400-e29b-41d4-a716-446655440000", "invalid UUID format"}, + {"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "invalid UUID format"}, + {"F50e8400-e29b-41d4-a716-44665544000", "invalid UUID length: 35"}, + {"aaa", "invalid UUID length: 3"}, + {"", "invalid UUID length: 0"}, + } + for _, v := range values { + result, err := UnmarshalUUID(v.input) + assert.Equal(t, uuid.Nil, result) + assert.ErrorContains(t, err, v.expected) + } + }) +} diff --git a/graphql/version.go b/graphql/version.go index 8c16fdb5b9..92812f7acc 100644 --- a/graphql/version.go +++ b/graphql/version.go @@ -1,3 +1,3 @@ package graphql -const Version = "v0.17.35-dev" +const Version = "v0.17.40-dev" diff --git a/handler/handler.go b/handler/handler.go index 19491020de..08e6a4c0e1 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -5,13 +5,14 @@ import ( "net/http" "time" + "github.com/gorilla/websocket" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/lru" "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/99designs/gqlgen/graphql/playground" - "github.com/gorilla/websocket" ) // Deprecated: switch to graphql/handler.New diff --git a/integration/package-lock.json b/integration/package-lock.json index 195a333e0c..21e629b5f7 100644 --- a/integration/package-lock.json +++ b/integration/package-lock.json @@ -202,17 +202,80 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", @@ -253,12 +316,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -322,22 +385,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -461,9 +524,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { "@babel/types": "^7.22.5" @@ -482,9 +545,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -514,13 +577,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -590,9 +653,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1038,33 +1101,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1073,13 +1136,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -5835,9 +5898,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { diff --git a/integration/server/cmd/integration/server.go b/integration/server/cmd/integration/server.go index fec1c4827a..df542a4045 100644 --- a/integration/server/cmd/integration/server.go +++ b/integration/server/cmd/integration/server.go @@ -8,6 +8,8 @@ import ( "os" "time" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" @@ -15,7 +17,6 @@ import ( "github.com/99designs/gqlgen/graphql/handler/transport" "github.com/99designs/gqlgen/graphql/playground" "github.com/99designs/gqlgen/integration/server" - "github.com/vektah/gqlparser/v2/gqlerror" ) const defaultPort = "8080" diff --git a/integration/server/generated.go b/integration/server/generated.go index 6928d3f2d5..7b2c5c3ee1 100644 --- a/integration/server/generated.go +++ b/integration/server/generated.go @@ -26,6 +26,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -33,6 +34,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -99,12 +101,16 @@ type UserResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -319,14 +325,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } //go:embed "schema/schema.graphql" "schema/testomitempty.graphql" "schema/user.graphql" diff --git a/internal/code/packages.go b/internal/code/packages.go index 6cb6ef2323..e7f8655aa0 100644 --- a/internal/code/packages.go +++ b/internal/code/packages.go @@ -28,15 +28,37 @@ var mode = packages.NeedName | packages.NeedModule | packages.NeedDeps -// Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages -// that can be invalidated as writes are made and packages are known to change. -type Packages struct { - packages map[string]*packages.Package - importToName map[string]string - loadErrors []error - - numLoadCalls int // stupid test steam. ignore. - numNameCalls int // stupid test steam. ignore. +type ( + // Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages + // that can be invalidated as writes are made and packages are known to change. + Packages struct { + packages map[string]*packages.Package + importToName map[string]string + loadErrors []error + buildFlags []string + + numLoadCalls int // stupid test steam. ignore. + numNameCalls int // stupid test steam. ignore. + } + // Option is a function that can be passed to NewPackages to configure the package loader + Option func(p *Packages) +) + +// WithBuildTags adds build tags to the packages.Load call +func WithBuildTags(tags ...string) func(p *Packages) { + return func(p *Packages) { + p.buildFlags = append(p.buildFlags, "-tags", strings.Join(tags, ",")) + } +} + +// NewPackages creates a new packages cache +// It will load all packages in the current module, and any packages that are passed to Load or LoadAll +func NewPackages(opts ...Option) *Packages { + p := &Packages{} + for _, opt := range opts { + opt(p) + } + return p } func (p *Packages) CleanupUserPackages() { @@ -47,8 +69,8 @@ func (p *Packages) CleanupUserPackages() { modInfo = nil } }) - - // Don't cleanup github.com/99designs/gqlgen prefixed packages, they haven't changed and do not need to be reloaded + // Don't cleanup github.com/99designs/gqlgen prefixed packages, + // they haven't changed and do not need to be reloaded if modInfo != nil { var toRemove []string for k := range p.packages { @@ -56,7 +78,6 @@ func (p *Packages) CleanupUserPackages() { toRemove = append(toRemove, k) } } - for _, k := range toRemove { delete(p.packages, k) } @@ -91,7 +112,10 @@ func (p *Packages) LoadAll(importPaths ...string) []*packages.Package { if len(missing) > 0 { p.numLoadCalls++ - pkgs, err := packages.Load(&packages.Config{Mode: mode}, missing...) + pkgs, err := packages.Load(&packages.Config{ + Mode: mode, + BuildFlags: p.buildFlags, + }, missing...) if err != nil { p.loadErrors = append(p.loadErrors, err) } @@ -140,7 +164,10 @@ func (p *Packages) LoadWithTypes(importPath string) *packages.Package { pkg := p.Load(importPath) if pkg == nil || pkg.TypesInfo == nil { p.numLoadCalls++ - pkgs, err := packages.Load(&packages.Config{Mode: mode}, importPath) + pkgs, err := packages.Load(&packages.Config{ + Mode: mode, + BuildFlags: p.buildFlags, + }, importPath) if err != nil { p.loadErrors = append(p.loadErrors, err) return nil @@ -173,7 +200,10 @@ func (p *Packages) NameForPackage(importPath string) string { if pkg == nil { // otherwise do a name only lookup for it but don't put it in the package cache. p.numNameCalls++ - pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName}, importPath) + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName, + BuildFlags: p.buildFlags, + }, importPath) if err != nil { p.loadErrors = append(p.loadErrors, err) } else { diff --git a/internal/code/packages_test.go b/internal/code/packages_test.go index 2fbf780c55..1d67419eae 100644 --- a/internal/code/packages_test.go +++ b/internal/code/packages_test.go @@ -38,6 +38,14 @@ func TestPackages(t *testing.T) { require.Equal(t, "b", p.Load("github.com/99designs/gqlgen/internal/code/testdata/b").Name) require.Equal(t, 3, p.numLoadCalls) }) + t.Run("able to load private package with build tags", func(t *testing.T) { + p := initialState(t, WithBuildTags("private")) + p.Evict("github.com/99designs/gqlgen/internal/code/testdata/a") + require.Equal(t, "a", p.Load("github.com/99designs/gqlgen/internal/code/testdata/a").Name) + require.Equal(t, 2, p.numLoadCalls) + require.Equal(t, "p", p.Load("github.com/99designs/gqlgen/internal/code/testdata/p").Name) + require.Equal(t, 3, p.numLoadCalls) + }) } func TestNameForPackage(t *testing.T) { @@ -50,8 +58,8 @@ func TestNameForPackage(t *testing.T) { assert.Equal(t, "github_com", p.NameForPackage("github.com")) } -func initialState(t *testing.T) *Packages { - p := &Packages{} +func initialState(t *testing.T, opts ...Option) *Packages { + p := NewPackages(opts...) pkgs := p.LoadAll( "github.com/99designs/gqlgen/internal/code/testdata/a", "github.com/99designs/gqlgen/internal/code/testdata/b", diff --git a/internal/code/testdata/p/p.go b/internal/code/testdata/p/p.go new file mode 100644 index 0000000000..bf516e9813 --- /dev/null +++ b/internal/code/testdata/p/p.go @@ -0,0 +1,13 @@ +//go:build private +// +build private + +// This file is excluded from the build unless the "private" build tag is set. +// This is used to test loading private packages. +// See internal/code/packages_test.go for more details. +package p + +import ( + "github.com/99designs/gqlgen/internal/code/testdata/b" +) + +var P = b.C + " P" diff --git a/internal/imports/prune.go b/internal/imports/prune.go index d42a415791..f5fc71c697 100644 --- a/internal/imports/prune.go +++ b/internal/imports/prune.go @@ -10,10 +10,10 @@ import ( "go/token" "strings" - "github.com/99designs/gqlgen/internal/code" - "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/imports" + + "github.com/99designs/gqlgen/internal/code" ) type visitFn func(node ast.Node) diff --git a/internal/imports/prune_test.go b/internal/imports/prune_test.go index 15af13dcd2..d312aff6bc 100644 --- a/internal/imports/prune_test.go +++ b/internal/imports/prune_test.go @@ -5,14 +5,15 @@ import ( "strings" "testing" - "github.com/99designs/gqlgen/internal/code" "github.com/stretchr/testify/require" + + "github.com/99designs/gqlgen/internal/code" ) func TestPrune(t *testing.T) { // prime the packages cache so that it's not considered uninitialized - b, err := Prune("testdata/unused.go", mustReadFile("testdata/unused.go"), &code.Packages{}) + b, err := Prune("testdata/unused.go", mustReadFile("testdata/unused.go"), code.NewPackages()) require.NoError(t, err) require.Equal(t, strings.ReplaceAll(string(mustReadFile("testdata/unused.expected.go")), "\r\n", "\n"), string(b)) } diff --git a/internal/imports/testdata/unused.expected.go b/internal/imports/testdata/unused.expected.go index 95cbe2d00f..f9d393d7ee 100644 --- a/internal/imports/testdata/unused.expected.go +++ b/internal/imports/testdata/unused.expected.go @@ -3,6 +3,7 @@ package testdata import ( a "fmt" "time" + _ "underscore" ) diff --git a/internal/imports/testdata/unused.go b/internal/imports/testdata/unused.go index 95cbe2d00f..f9d393d7ee 100644 --- a/internal/imports/testdata/unused.go +++ b/internal/imports/testdata/unused.go @@ -3,6 +3,7 @@ package testdata import ( a "fmt" "time" + _ "underscore" ) diff --git a/internal/rewrite/rewriter.go b/internal/rewrite/rewriter.go index 1ca722dcfd..7e68e55de9 100644 --- a/internal/rewrite/rewriter.go +++ b/internal/rewrite/rewriter.go @@ -10,8 +10,9 @@ import ( "strconv" "strings" - "github.com/99designs/gqlgen/internal/code" "golang.org/x/tools/go/packages" + + "github.com/99designs/gqlgen/internal/code" ) type Rewriter struct { diff --git a/internal/rewrite/rewriter_test.go b/internal/rewrite/rewriter_test.go index fae86668fb..9ac5ffb0e7 100644 --- a/internal/rewrite/rewriter_test.go +++ b/internal/rewrite/rewriter_test.go @@ -26,14 +26,14 @@ func TestRewriter(t *testing.T) { imps := r.ExistingImports("testdata/example.go") require.Len(t, imps, 2) assert.Equal(t, []Import{ - { - Alias: "", - ImportPath: "fmt", - }, { Alias: "lol", ImportPath: "bytes", }, + { + Alias: "", + ImportPath: "fmt", + }, }, imps) }) diff --git a/internal/rewrite/testdata/example.go b/internal/rewrite/testdata/example.go index 949bbe8235..6a3297ad2b 100644 --- a/internal/rewrite/testdata/example.go +++ b/internal/rewrite/testdata/example.go @@ -1,9 +1,8 @@ package testdata import ( - "fmt" - lol "bytes" + "fmt" ) type Foo struct { diff --git a/main.go b/main.go index af365bfeb9..e0420d1502 100644 --- a/main.go +++ b/main.go @@ -12,12 +12,13 @@ import ( "os" "path/filepath" + "github.com/urfave/cli/v2" + "github.com/99designs/gqlgen/api" "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/internal/code" "github.com/99designs/gqlgen/plugin/servergen" - "github.com/urfave/cli/v2" ) //go:embed init-templates/schema.graphqls diff --git a/plugin/federation/entity.go b/plugin/federation/entity.go index 04a3c033b0..042dd59ad7 100644 --- a/plugin/federation/entity.go +++ b/plugin/federation/entity.go @@ -3,10 +3,11 @@ package federation import ( "go/types" + "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" "github.com/99designs/gqlgen/plugin/federation/fieldset" - "github.com/vektah/gqlparser/v2/ast" ) // Entity represents a federated type diff --git a/plugin/federation/federation.go b/plugin/federation/federation.go index d0ee8435e1..7d98850315 100644 --- a/plugin/federation/federation.go +++ b/plugin/federation/federation.go @@ -59,6 +59,9 @@ func (f *federation) MutateConfig(cfg *config.Config) error { "_Any": { Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"}, }, + "federation__Scope": { + Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"}, + }, } for typeName, entry := range builtins { @@ -80,6 +83,10 @@ func (f *federation) MutateConfig(cfg *config.Config) error { cfg.Directives["tag"] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["override"] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["inaccessible"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["authenticated"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["requiresScopes"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["interfaceObject"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["composeDirective"] = config.DirectiveConfig{SkipRuntime: true} } return nil @@ -101,6 +108,7 @@ func (f *federation) InjectSourceEarly() *ast.Source { ` } else if f.Version == 2 { input += ` + directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION @@ -121,6 +129,12 @@ func (f *federation) InjectSourceEarly() *ast.Source { directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION + directive @requiresScopes(scopes: [[federation__Scope!]!]!) on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | SCALAR + | ENUM directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag(name: String!) repeatable on | ARGUMENT_DEFINITION @@ -135,6 +149,7 @@ func (f *federation) InjectSourceEarly() *ast.Source { | UNION scalar _Any scalar FieldSet + scalar federation__Scope ` } return &ast.Source{ @@ -170,10 +185,14 @@ func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source { } entityResolverInputDefinitions += "input " + r.InputTypeName + " {\n" for _, keyField := range r.KeyFields { - entityResolverInputDefinitions += fmt.Sprintf("\t%s: %s\n", keyField.Field.ToGo(), keyField.Definition.Type.String()) + entityResolverInputDefinitions += fmt.Sprintf( + "\t%s: %s\n", + keyField.Field.ToGo(), + keyField.Definition.Type.String(), + ) } entityResolverInputDefinitions += "}" - resolvers += fmt.Sprintf("\t%s(reps: [%s!]!): [%s]\n", r.ResolverName, r.InputTypeName, e.Name) + resolvers += fmt.Sprintf("\t%s(reps: [%s]!): [%s]\n", r.ResolverName, r.InputTypeName, e.Name) } else { resolverArgs := "" for _, keyField := range r.KeyFields { diff --git a/plugin/federation/federation_entityresolver_test.go b/plugin/federation/federation_entityresolver_test.go index 4cbc160245..cd96c5d498 100644 --- a/plugin/federation/federation_entityresolver_test.go +++ b/plugin/federation/federation_entityresolver_test.go @@ -7,10 +7,10 @@ import ( "strings" "testing" - "github.com/99designs/gqlgen/client" - "github.com/99designs/gqlgen/graphql/handler" "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/plugin/federation/testdata/entityresolver" "github.com/99designs/gqlgen/plugin/federation/testdata/entityresolver/generated" ) diff --git a/plugin/federation/federation_test.go b/plugin/federation/federation_test.go index 7e8d924290..1458743174 100644 --- a/plugin/federation/federation_test.go +++ b/plugin/federation/federation_test.go @@ -3,9 +3,10 @@ package federation import ( "testing" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" - "github.com/stretchr/testify/require" ) func TestWithEntities(t *testing.T) { @@ -144,6 +145,50 @@ func TestCodeGenerationFederation2(t *testing.T) { require.NoError(t, f.GenerateCode(data)) } +// This test is to ensure that the input arguments are not +// changed when cfg.OmitSliceElementPointers is false OR true +func TestMultiWithOmitSliceElemPointersCfg(t *testing.T) { + + staticRepsString := "reps: [HelloByNamesInput]!" + t.Run("OmitSliceElementPointers true", func(t *testing.T) { + f, cfg := load(t, "testdata/multi/multi.yml") + cfg.OmitSliceElementPointers = true + err := f.MutateConfig(cfg) + require.NoError(t, err) + require.Len(t, cfg.Schema.Types["_Entity"].Types, 1) + require.Len(t, f.Entities, 1) + + entityGraphqlGenerated := false + for _, source := range cfg.Sources { + if source.Name != "federation/entity.graphql" { + continue + } + entityGraphqlGenerated = true + require.Contains(t, source.Input, staticRepsString) + } + require.True(t, entityGraphqlGenerated) + }) + + t.Run("OmitSliceElementPointers false", func(t *testing.T) { + f, cfg := load(t, "testdata/multi/multi.yml") + cfg.OmitSliceElementPointers = false + err := f.MutateConfig(cfg) + require.NoError(t, err) + require.Len(t, cfg.Schema.Types["_Entity"].Types, 1) + require.Len(t, f.Entities, 1) + + entityGraphqlGenerated := false + for _, source := range cfg.Sources { + if source.Name != "federation/entity.graphql" { + continue + } + entityGraphqlGenerated = true + require.Contains(t, source.Input, staticRepsString) + } + require.True(t, entityGraphqlGenerated) + }) +} + func TestInjectSourceLate(t *testing.T) { _, cfg := load(t, "testdata/allthethings/gqlgen.yml") entityGraphqlGenerated := false diff --git a/plugin/federation/fieldset/fieldset.go b/plugin/federation/fieldset/fieldset.go index 059a3c8325..59e4775368 100644 --- a/plugin/federation/fieldset/fieldset.go +++ b/plugin/federation/fieldset/fieldset.go @@ -4,9 +4,10 @@ import ( "fmt" "strings" + "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/templates" - "github.com/vektah/gqlparser/v2/ast" ) // Set represents a FieldSet that is used in federation directives @key and @requires. diff --git a/plugin/federation/readme.md b/plugin/federation/readme.md index 4333ed4797..d5dd0628a3 100644 --- a/plugin/federation/readme.md +++ b/plugin/federation/readme.md @@ -37,3 +37,6 @@ func (r *entityResolver) FindManyMultiHellosByName(ctx context.Context, reps []* /// } ``` + +**Note:** +If you are using `omit_slice_element_pointers: true` option in your config yaml, your `GetMany` resolver will still generate in the example above the same signature `FindManyMultiHellosByName(ctx context.Context, reps []*generated.ManyMultiHellosByNameInput) ([]*generated.MultiHello, error)`. But all other instances will continue to honor `omit_slice_element_pointers: true` \ No newline at end of file diff --git a/plugin/federation/testdata/entityresolver/entity.resolvers.go b/plugin/federation/testdata/entityresolver/entity.resolvers.go index 85b6a59f18..bc1e3c0d78 100644 --- a/plugin/federation/testdata/entityresolver/entity.resolvers.go +++ b/plugin/federation/testdata/entityresolver/entity.resolvers.go @@ -2,7 +2,7 @@ package entityresolver // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.35-dev +// Code generated by github.com/99designs/gqlgen version v0.17.40-dev import ( "context" diff --git a/plugin/federation/testdata/entityresolver/generated/exec.go b/plugin/federation/testdata/entityresolver/generated/exec.go index 0b1e4a60ab..25a9f93d9b 100644 --- a/plugin/federation/testdata/entityresolver/generated/exec.go +++ b/plugin/federation/testdata/entityresolver/generated/exec.go @@ -24,6 +24,7 @@ import ( // NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { return &executableSchema{ + schema: cfg.Schema, resolvers: cfg.Resolvers, directives: cfg.Directives, complexity: cfg.Complexity, @@ -31,6 +32,7 @@ func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { } type Config struct { + Schema *ast.Schema Resolvers ResolverRoot Directives DirectiveRoot Complexity ComplexityRoot @@ -168,12 +170,16 @@ type EntityResolver interface { } type executableSchema struct { + schema *ast.Schema resolvers ResolverRoot directives DirectiveRoot complexity ComplexityRoot } func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } return parsedSchema } @@ -710,14 +716,14 @@ func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapSchema(parsedSchema), nil + return introspection.WrapSchema(ec.Schema()), nil } func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") } - return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil } var sources = []*ast.Source{ @@ -837,11 +843,11 @@ type Entity { findHelloByName(name: String!,): Hello! findHelloMultiSingleKeysByKey1AndKey2(key1: String!,key2: String!,): HelloMultiSingleKeys! findHelloWithErrorsByName(name: String!,): HelloWithErrors! - findManyMultiHelloByNames(reps: [MultiHelloByNamesInput!]!): [MultiHello] - findManyMultiHelloMultipleRequiresByNames(reps: [MultiHelloMultipleRequiresByNamesInput!]!): [MultiHelloMultipleRequires] - findManyMultiHelloRequiresByNames(reps: [MultiHelloRequiresByNamesInput!]!): [MultiHelloRequires] - findManyMultiHelloWithErrorByNames(reps: [MultiHelloWithErrorByNamesInput!]!): [MultiHelloWithError] - findManyMultiPlanetRequiresNestedByNames(reps: [MultiPlanetRequiresNestedByNamesInput!]!): [MultiPlanetRequiresNested] + findManyMultiHelloByNames(reps: [MultiHelloByNamesInput]!): [MultiHello] + findManyMultiHelloMultipleRequiresByNames(reps: [MultiHelloMultipleRequiresByNamesInput]!): [MultiHelloMultipleRequires] + findManyMultiHelloRequiresByNames(reps: [MultiHelloRequiresByNamesInput]!): [MultiHelloRequires] + findManyMultiHelloWithErrorByNames(reps: [MultiHelloWithErrorByNamesInput]!): [MultiHelloWithError] + findManyMultiPlanetRequiresNestedByNames(reps: [MultiPlanetRequiresNestedByNamesInput]!): [MultiPlanetRequiresNested] findPlanetMultipleRequiresByName(name: String!,): PlanetMultipleRequires! findPlanetRequiresByName(name: String!,): PlanetRequires! findPlanetRequiresNestedByName(name: String!,): PlanetRequiresNested! @@ -943,7 +949,7 @@ func (ec *executionContext) field_Entity_findManyMultiHelloByNames_args(ctx cont var arg0 []*model.MultiHelloByNamesInput if tmp, ok := rawArgs["reps"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reps")) - arg0, err = ec.unmarshalNMultiHelloByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInputᚄ(ctx, tmp) + arg0, err = ec.unmarshalNMultiHelloByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInput(ctx, tmp) if err != nil { return nil, err } @@ -958,7 +964,7 @@ func (ec *executionContext) field_Entity_findManyMultiHelloMultipleRequiresByNam var arg0 []*model.MultiHelloMultipleRequiresByNamesInput if tmp, ok := rawArgs["reps"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reps")) - arg0, err = ec.unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInputᚄ(ctx, tmp) + arg0, err = ec.unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInput(ctx, tmp) if err != nil { return nil, err } @@ -973,7 +979,7 @@ func (ec *executionContext) field_Entity_findManyMultiHelloRequiresByNames_args( var arg0 []*model.MultiHelloRequiresByNamesInput if tmp, ok := rawArgs["reps"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reps")) - arg0, err = ec.unmarshalNMultiHelloRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInputᚄ(ctx, tmp) + arg0, err = ec.unmarshalNMultiHelloRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInput(ctx, tmp) if err != nil { return nil, err } @@ -988,7 +994,7 @@ func (ec *executionContext) field_Entity_findManyMultiHelloWithErrorByNames_args var arg0 []*model.MultiHelloWithErrorByNamesInput if tmp, ok := rawArgs["reps"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reps")) - arg0, err = ec.unmarshalNMultiHelloWithErrorByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInputᚄ(ctx, tmp) + arg0, err = ec.unmarshalNMultiHelloWithErrorByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInput(ctx, tmp) if err != nil { return nil, err } @@ -1003,7 +1009,7 @@ func (ec *executionContext) field_Entity_findManyMultiPlanetRequiresNestedByName var arg0 []*model.MultiPlanetRequiresNestedByNamesInput if tmp, ok := rawArgs["reps"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reps")) - arg0, err = ec.unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInputᚄ(ctx, tmp) + arg0, err = ec.unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInput(ctx, tmp) if err != nil { return nil, err } @@ -7586,7 +7592,7 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } -func (ec *executionContext) unmarshalNMultiHelloByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInputᚄ(ctx context.Context, v interface{}) ([]*model.MultiHelloByNamesInput, error) { +func (ec *executionContext) unmarshalNMultiHelloByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInput(ctx context.Context, v interface{}) ([]*model.MultiHelloByNamesInput, error) { var vSlice []interface{} if v != nil { vSlice = graphql.CoerceList(v) @@ -7595,7 +7601,7 @@ func (ec *executionContext) unmarshalNMultiHelloByNamesInput2ᚕᚖgithubᚗcom res := make([]*model.MultiHelloByNamesInput, len(vSlice)) for i := range vSlice { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNMultiHelloByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInput(ctx, vSlice[i]) + res[i], err = ec.unmarshalOMultiHelloByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInput(ctx, vSlice[i]) if err != nil { return nil, err } @@ -7603,12 +7609,7 @@ func (ec *executionContext) unmarshalNMultiHelloByNamesInput2ᚕᚖgithubᚗcom return res, nil } -func (ec *executionContext) unmarshalNMultiHelloByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloByNamesInput, error) { - res, err := ec.unmarshalInputMultiHelloByNamesInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInputᚄ(ctx context.Context, v interface{}) ([]*model.MultiHelloMultipleRequiresByNamesInput, error) { +func (ec *executionContext) unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInput(ctx context.Context, v interface{}) ([]*model.MultiHelloMultipleRequiresByNamesInput, error) { var vSlice []interface{} if v != nil { vSlice = graphql.CoerceList(v) @@ -7617,7 +7618,7 @@ func (ec *executionContext) unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚕ res := make([]*model.MultiHelloMultipleRequiresByNamesInput, len(vSlice)) for i := range vSlice { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInput(ctx, vSlice[i]) + res[i], err = ec.unmarshalOMultiHelloMultipleRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInput(ctx, vSlice[i]) if err != nil { return nil, err } @@ -7625,12 +7626,7 @@ func (ec *executionContext) unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚕ return res, nil } -func (ec *executionContext) unmarshalNMultiHelloMultipleRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloMultipleRequiresByNamesInput, error) { - res, err := ec.unmarshalInputMultiHelloMultipleRequiresByNamesInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) unmarshalNMultiHelloRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInputᚄ(ctx context.Context, v interface{}) ([]*model.MultiHelloRequiresByNamesInput, error) { +func (ec *executionContext) unmarshalNMultiHelloRequiresByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInput(ctx context.Context, v interface{}) ([]*model.MultiHelloRequiresByNamesInput, error) { var vSlice []interface{} if v != nil { vSlice = graphql.CoerceList(v) @@ -7639,7 +7635,7 @@ func (ec *executionContext) unmarshalNMultiHelloRequiresByNamesInput2ᚕᚖgithu res := make([]*model.MultiHelloRequiresByNamesInput, len(vSlice)) for i := range vSlice { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNMultiHelloRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInput(ctx, vSlice[i]) + res[i], err = ec.unmarshalOMultiHelloRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInput(ctx, vSlice[i]) if err != nil { return nil, err } @@ -7647,12 +7643,7 @@ func (ec *executionContext) unmarshalNMultiHelloRequiresByNamesInput2ᚕᚖgithu return res, nil } -func (ec *executionContext) unmarshalNMultiHelloRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloRequiresByNamesInput, error) { - res, err := ec.unmarshalInputMultiHelloRequiresByNamesInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) unmarshalNMultiHelloWithErrorByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInputᚄ(ctx context.Context, v interface{}) ([]*model.MultiHelloWithErrorByNamesInput, error) { +func (ec *executionContext) unmarshalNMultiHelloWithErrorByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInput(ctx context.Context, v interface{}) ([]*model.MultiHelloWithErrorByNamesInput, error) { var vSlice []interface{} if v != nil { vSlice = graphql.CoerceList(v) @@ -7661,7 +7652,7 @@ func (ec *executionContext) unmarshalNMultiHelloWithErrorByNamesInput2ᚕᚖgith res := make([]*model.MultiHelloWithErrorByNamesInput, len(vSlice)) for i := range vSlice { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNMultiHelloWithErrorByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInput(ctx, vSlice[i]) + res[i], err = ec.unmarshalOMultiHelloWithErrorByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInput(ctx, vSlice[i]) if err != nil { return nil, err } @@ -7669,12 +7660,7 @@ func (ec *executionContext) unmarshalNMultiHelloWithErrorByNamesInput2ᚕᚖgith return res, nil } -func (ec *executionContext) unmarshalNMultiHelloWithErrorByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloWithErrorByNamesInput, error) { - res, err := ec.unmarshalInputMultiHelloWithErrorByNamesInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInputᚄ(ctx context.Context, v interface{}) ([]*model.MultiPlanetRequiresNestedByNamesInput, error) { +func (ec *executionContext) unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInput(ctx context.Context, v interface{}) ([]*model.MultiPlanetRequiresNestedByNamesInput, error) { var vSlice []interface{} if v != nil { vSlice = graphql.CoerceList(v) @@ -7683,7 +7669,7 @@ func (ec *executionContext) unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚕ res := make([]*model.MultiPlanetRequiresNestedByNamesInput, len(vSlice)) for i := range vSlice { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInput(ctx, vSlice[i]) + res[i], err = ec.unmarshalOMultiPlanetRequiresNestedByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInput(ctx, vSlice[i]) if err != nil { return nil, err } @@ -7691,11 +7677,6 @@ func (ec *executionContext) unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚕ return res, nil } -func (ec *executionContext) unmarshalNMultiPlanetRequiresNestedByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInput(ctx context.Context, v interface{}) (*model.MultiPlanetRequiresNestedByNamesInput, error) { - res, err := ec.unmarshalInputMultiPlanetRequiresNestedByNamesInput(ctx, v) - return &res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) marshalNPlanetMultipleRequires2githubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐPlanetMultipleRequires(ctx context.Context, sel ast.SelectionSet, v model.PlanetMultipleRequires) graphql.Marshaler { return ec._PlanetMultipleRequires(ctx, sel, &v) } @@ -8239,6 +8220,14 @@ func (ec *executionContext) marshalOMultiHello2ᚖgithubᚗcomᚋ99designsᚋgql return ec._MultiHello(ctx, sel, v) } +func (ec *executionContext) unmarshalOMultiHelloByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloByNamesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMultiHelloByNamesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOMultiHelloMultipleRequires2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequires(ctx context.Context, sel ast.SelectionSet, v []*model.MultiHelloMultipleRequires) graphql.Marshaler { if v == nil { return graphql.Null @@ -8287,6 +8276,14 @@ func (ec *executionContext) marshalOMultiHelloMultipleRequires2ᚖgithubᚗcom return ec._MultiHelloMultipleRequires(ctx, sel, v) } +func (ec *executionContext) unmarshalOMultiHelloMultipleRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloMultipleRequiresByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloMultipleRequiresByNamesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMultiHelloMultipleRequiresByNamesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOMultiHelloRequires2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequires(ctx context.Context, sel ast.SelectionSet, v []*model.MultiHelloRequires) graphql.Marshaler { if v == nil { return graphql.Null @@ -8335,6 +8332,14 @@ func (ec *executionContext) marshalOMultiHelloRequires2ᚖgithubᚗcomᚋ99desig return ec._MultiHelloRequires(ctx, sel, v) } +func (ec *executionContext) unmarshalOMultiHelloRequiresByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloRequiresByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloRequiresByNamesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMultiHelloRequiresByNamesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOMultiHelloWithError2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithError(ctx context.Context, sel ast.SelectionSet, v []*model.MultiHelloWithError) graphql.Marshaler { if v == nil { return graphql.Null @@ -8383,6 +8388,14 @@ func (ec *executionContext) marshalOMultiHelloWithError2ᚖgithubᚗcomᚋ99desi return ec._MultiHelloWithError(ctx, sel, v) } +func (ec *executionContext) unmarshalOMultiHelloWithErrorByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiHelloWithErrorByNamesInput(ctx context.Context, v interface{}) (*model.MultiHelloWithErrorByNamesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMultiHelloWithErrorByNamesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOMultiPlanetRequiresNested2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNested(ctx context.Context, sel ast.SelectionSet, v []*model.MultiPlanetRequiresNested) graphql.Marshaler { if v == nil { return graphql.Null @@ -8431,6 +8444,14 @@ func (ec *executionContext) marshalOMultiPlanetRequiresNested2ᚖgithubᚗcomᚋ return ec._MultiPlanetRequiresNested(ctx, sel, v) } +func (ec *executionContext) unmarshalOMultiPlanetRequiresNestedByNamesInput2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚋmodelᚐMultiPlanetRequiresNestedByNamesInput(ctx context.Context, v interface{}) (*model.MultiPlanetRequiresNestedByNamesInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputMultiPlanetRequiresNestedByNamesInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/plugin/federation/testdata/federation2/federation2.graphql b/plugin/federation/testdata/federation2/federation2.graphql index 770f896970..5355cb804c 100644 --- a/plugin/federation/testdata/federation2/federation2.graphql +++ b/plugin/federation/testdata/federation2/federation2.graphql @@ -1,6 +1,6 @@ extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", - import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible"]) + import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible", "@interfaceObject"]) schema { query: CustomQuery @@ -23,4 +23,3 @@ extend type ExternalExtension @key(fields: " upc ") { type CustomQuery { hello: Hello! } - diff --git a/plugin/federation/testdata/multi/multi.graphqls b/plugin/federation/testdata/multi/multi.graphqls new file mode 100644 index 0000000000..bf37c5a067 --- /dev/null +++ b/plugin/federation/testdata/multi/multi.graphqls @@ -0,0 +1,10 @@ +extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", + import: ["@key"]) + +directive @entityResolver(multi: Boolean) on OBJECT + +type Hello @key(fields: "name") @entityResolver(multi: true) { + name: String! + secondary: String! +} diff --git a/plugin/federation/testdata/multi/multi.yml b/plugin/federation/testdata/multi/multi.yml new file mode 100644 index 0000000000..891466c73f --- /dev/null +++ b/plugin/federation/testdata/multi/multi.yml @@ -0,0 +1,8 @@ +schema: + - "testdata/multi/multi.graphqls" +exec: + filename: testdata/multi/generated/exec.go +federation: + version: 2 + filename: testdata/multi/generated/federation.go +omit_slice_element_pointers: true \ No newline at end of file diff --git a/plugin/modelgen/models.go b/plugin/modelgen/models.go index 6b4f483752..798a936f57 100644 --- a/plugin/modelgen/models.go +++ b/plugin/modelgen/models.go @@ -4,14 +4,16 @@ import ( _ "embed" "fmt" "go/types" + "os" "sort" "strings" "text/template" + "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" "github.com/99designs/gqlgen/plugin" - "github.com/vektah/gqlparser/v2/ast" ) //go:embed models.gotpl @@ -51,6 +53,7 @@ type Interface struct { Fields []*Field Implements []string OmitCheck bool + Models []*Object } type Object struct { @@ -199,6 +202,15 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name)) } for _, it := range b.Interfaces { + // On a given interface we want to keep a reference to all the models that implement it + for _, model := range b.Models { + for _, impl := range model.Implements { + if impl == it.Name { + // If it does, add it to the Interface's Models + it.Models = append(it.Models, model) + } + } + } cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name)) } for _, it := range b.Scalars { @@ -282,6 +294,10 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { "getInterfaceByName": getInterfaceByName, "generateGetter": generateGetter, } + newModelTemplate := modelTemplate + if cfg.Model.ModelTemplate != "" { + newModelTemplate = readModelTemplate(cfg.Model.ModelTemplate) + } err := templates.Render(templates.Options{ PackageName: cfg.Model.Package, @@ -289,7 +305,7 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { Data: b, GeneratedHeader: true, Packages: cfg.Packages, - Template: modelTemplate, + Template: newModelTemplate, Funcs: funcMap, }) if err != nil { @@ -553,17 +569,19 @@ func removeDuplicateTags(t string) string { continue } - processed[kv[0]] = true + key := kv[0] + value := strings.Join(kv[1:], ":") + processed[key] = true if len(returnTags) > 0 { returnTags = " " + returnTags } - isContained := containsInvalidSpace(kv[1]) + isContained := containsInvalidSpace(value) if isContained { - panic(fmt.Errorf("tag value should not contain any leading or trailing spaces: %s", kv[1])) + panic(fmt.Errorf("tag value should not contain any leading or trailing spaces: %s", value)) } - returnTags = kv[0] + ":" + kv[1] + returnTags + returnTags = key + ":" + value + returnTags } return returnTags @@ -645,3 +663,11 @@ func findAndHandleCyclicalRelationships(b *ModelBuild) { } } } + +func readModelTemplate(customModelTemplate string) string { + contentBytes, err := os.ReadFile(customModelTemplate) + if err != nil { + panic(err) + } + return string(contentBytes) +} diff --git a/plugin/modelgen/models_test.go b/plugin/modelgen/models_test.go index f4cd43beae..a9182d02f0 100644 --- a/plugin/modelgen/models_test.go +++ b/plugin/modelgen/models_test.go @@ -14,6 +14,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/plugin/modelgen/internal/extrafields" @@ -23,8 +26,6 @@ import ( "github.com/99designs/gqlgen/plugin/modelgen/out_enable_model_json_omitempty_tag_true" "github.com/99designs/gqlgen/plugin/modelgen/out_nullable_input_omittable" "github.com/99designs/gqlgen/plugin/modelgen/out_struct_pointers" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestModelGeneration(t *testing.T) { @@ -534,6 +535,22 @@ func TestRemoveDuplicate(t *testing.T) { want: "gorm:\"unique;not null\" json:\"name,name2\"", wantPanic: false, }, + { + name: "Test gorm tag with colon", + args: args{ + t: "gorm:\"type:varchar(63);unique_index\"", + }, + want: "gorm:\"type:varchar(63);unique_index\"", + wantPanic: false, + }, + { + name: "Test mix use of gorm and duplicate json tags with colon", + args: args{ + t: "json:\"name0\" gorm:\"type:varchar(63);unique_index\" json:\"name,name2\"", + }, + want: "gorm:\"type:varchar(63);unique_index\" json:\"name,name2\"", + wantPanic: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -637,3 +654,14 @@ func Test_splitTagsBySpace(t *testing.T) { }) } } + +func TestCustomTemplate(t *testing.T) { + cfg, err := config.LoadConfig("testdata/gqlgen_custom_model_template.yml") + require.NoError(t, err) + require.NoError(t, cfg.Init()) + p := Plugin{ + MutateHook: mutateHook, + FieldHook: DefaultFieldMutateHook, + } + require.NoError(t, p.MutateConfig(cfg)) +} diff --git a/plugin/modelgen/out/generated.go b/plugin/modelgen/out/generated.go index f90f8b974a..656913e510 100644 --- a/plugin/modelgen/out/generated.go +++ b/plugin/modelgen/out/generated.go @@ -11,6 +11,8 @@ import ( "github.com/99designs/gqlgen/plugin/modelgen/internal/extrafields" ) +// Add any new functions or any additional code/template functionality here + type A interface { IsA() GetA() string diff --git a/plugin/modelgen/testdata/customModelTemplate.gotpl b/plugin/modelgen/testdata/customModelTemplate.gotpl new file mode 100644 index 0000000000..4ff192a297 --- /dev/null +++ b/plugin/modelgen/testdata/customModelTemplate.gotpl @@ -0,0 +1,106 @@ +{{ reserveImport "context" }} +{{ reserveImport "fmt" }} +{{ reserveImport "io" }} +{{ reserveImport "strconv" }} +{{ reserveImport "time" }} +{{ reserveImport "sync" }} +{{ reserveImport "errors" }} +{{ reserveImport "bytes" }} + +{{ reserveImport "github.com/vektah/gqlparser/v2" }} +{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} +{{ reserveImport "github.com/99designs/gqlgen/graphql" }} +{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} + +// Add any new functions or any additional code/template functionality here + +{{- range $model := .Interfaces }} + {{ with .Description }} {{.|prefixLines "// "}} {{ end }} + type {{ goModelName .Name }} interface { + {{- if not .OmitCheck }} + {{- range $impl := .Implements }} + Is{{ goModelName $impl }}() + {{- end }} + Is{{ goModelName .Name }}() + {{- end }} + {{- range $field := .Fields }} + {{- with .Description }} + {{.|prefixLines "// "}} + {{- end}} + Get{{ $field.GoName }}() {{ $field.Type | ref }} + {{- end }} + } +{{- end }} + +{{ range $model := .Models }} + {{with .Description }} {{.|prefixLines "// "}} {{end}} + type {{ goModelName .Name }} struct { + {{- range $field := .Fields }} + {{- with .Description }} + {{.|prefixLines "// "}} + {{- end}} + {{ $field.GoName }} {{$field.Type | ref}} `{{$field.Tag}}` + {{- end }} + } + + {{ range .Implements }} + func ({{ goModelName $model.Name }}) Is{{ goModelName . }}() {} + {{- with getInterfaceByName . }} + {{- range .Fields }} + {{- with .Description }} + {{.|prefixLines "// "}} + {{- end}} + {{ generateGetter $model . }} + {{- end }} + {{- end }} + {{ end }} +{{- end}} + +{{ range $enum := .Enums }} + {{ with .Description }} {{.|prefixLines "// "}} {{end}} + type {{ goModelName .Name }} string + const ( + {{- range $value := .Values}} + {{- with .Description}} + {{.|prefixLines "// "}} + {{- end}} + {{ goModelName $enum.Name .Name }} {{ goModelName $enum.Name }} = {{ .Name|quote }} + {{- end }} + ) + + var All{{ goModelName .Name }} = []{{ goModelName .Name }}{ + {{- range $value := .Values}} + {{ goModelName $enum.Name .Name }}, + {{- end }} + } + + func (e {{ goModelName .Name }}) IsValid() bool { + switch e { + case {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ goModelName $enum.Name $element.Name }}{{end}}: + return true + } + return false + } + + func (e {{ goModelName .Name }}) String() string { + return string(e) + } + + func (e *{{ goModelName .Name }}) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = {{ goModelName .Name }}(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid {{ .Name }}", str) + } + return nil + } + + func (e {{ goModelName .Name }}) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) + } + +{{- end }} diff --git a/plugin/modelgen/testdata/gqlgen_custom_model_template.yml b/plugin/modelgen/testdata/gqlgen_custom_model_template.yml new file mode 100644 index 0000000000..ea3d96b5b7 --- /dev/null +++ b/plugin/modelgen/testdata/gqlgen_custom_model_template.yml @@ -0,0 +1,38 @@ +schema: + - "testdata/schema.graphql" + +exec: + filename: out/ignored.go +model: + filename: out/generated.go + model_template: "testdata/customModelTemplate.gotpl" + +models: + ExistingModel: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingModel + ExistingInput: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingInput + ExistingEnum: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingEnum + ExistingInterface: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingInterface + ExistingUnion: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingUnion + ExistingType: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingType + RenameFieldTest: + fields: + badName: + fieldName: GOODnaME + ExtraFieldsTest: + extraFields: + FieldInternalType: + description: "Internal field" + type: github.com/99designs/gqlgen/plugin/modelgen/internal/extrafields.Type + FieldStringPtr: + type: "*string" + FieldInt: + type: "int64" + overrideTags: 'json:"field_int_tag"' + FieldIntSlice: + type: "[]int64" diff --git a/plugin/plugin.go b/plugin/plugin.go index 69801c8816..214c58d9e1 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -3,9 +3,10 @@ package plugin import ( + "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" - "github.com/vektah/gqlparser/v2/ast" ) type Plugin interface { diff --git a/plugin/resolvergen/resolver.go b/plugin/resolvergen/resolver.go index 09cfbab63b..c3a2c40822 100644 --- a/plugin/resolvergen/resolver.go +++ b/plugin/resolvergen/resolver.go @@ -81,13 +81,18 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error { OmitTemplateComment: data.Config.Resolver.OmitTemplateComment, } + newResolverTemplate := resolverTemplate + if data.Config.Resolver.ResolverTemplate != "" { + newResolverTemplate = readResolverTemplate(data.Config.Resolver.ResolverTemplate) + } + return templates.Render(templates.Options{ PackageName: data.Config.Resolver.Package, FileNotice: `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`, Filename: data.Config.Resolver.Filename, Data: resolverBuild, Packages: data.Config.Packages, - Template: resolverTemplate, + Template: newResolverTemplate, }) } @@ -105,9 +110,12 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error { for _, o := range objects { if o.HasResolvers() { - fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate) + fnCase := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate) + fn := strings.ToLower(fnCase) if files[fn] == nil { - files[fn] = &File{} + files[fn] = &File{ + name: fnCase, + } } caser := cases.Title(language.English, cases.NoLower) @@ -145,21 +153,28 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error { } resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation} - fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate) + fnCase := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate) + fn := strings.ToLower(fnCase) if files[fn] == nil { - files[fn] = &File{} + files[fn] = &File{ + name: fnCase, + } } files[fn].Resolvers = append(files[fn].Resolvers, &resolver) } } - for filename, file := range files { - file.imports = rewriter.ExistingImports(filename) - file.RemainingSource = rewriter.RemainingSource(filename) + for _, file := range files { + file.imports = rewriter.ExistingImports(file.name) + file.RemainingSource = rewriter.RemainingSource(file.name) + } + newResolverTemplate := resolverTemplate + if data.Config.Resolver.ResolverTemplate != "" { + newResolverTemplate = readResolverTemplate(data.Config.Resolver.ResolverTemplate) } - for filename, file := range files { + for _, file := range files { resolverBuild := &ResolverBuild{ File: file, PackageName: data.Config.Resolver.Package, @@ -183,10 +198,10 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error { err := templates.Render(templates.Options{ PackageName: data.Config.Resolver.Package, FileNotice: fileNotice.String(), - Filename: filename, + Filename: file.name, Data: resolverBuild, Packages: data.Config.Packages, - Template: resolverTemplate, + Template: newResolverTemplate, }) if err != nil { return err @@ -221,6 +236,7 @@ type ResolverBuild struct { } type File struct { + name string // These are separated because the type definition of the resolver object may live in a different file from the // resolver method implementations, for example when extending a type in a different graphql schema file Objects []*codegen.Object @@ -257,3 +273,11 @@ func gqlToResolverName(base string, gqlname, filenameTmpl string) string { filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext)) return filepath.Join(base, filename) } + +func readResolverTemplate(customResolverTemplate string) string { + contentBytes, err := os.ReadFile(customResolverTemplate) + if err != nil { + panic(err) + } + return string(contentBytes) +} diff --git a/plugin/resolvergen/resolver_test.go b/plugin/resolvergen/resolver_test.go index 5a1300113c..17650e8f20 100644 --- a/plugin/resolvergen/resolver_test.go +++ b/plugin/resolvergen/resolver_test.go @@ -6,10 +6,11 @@ import ( "syscall" "testing" - "github.com/99designs/gqlgen/codegen" - "github.com/99designs/gqlgen/codegen/config" "github.com/stretchr/testify/require" "golang.org/x/tools/go/packages" + + "github.com/99designs/gqlgen/codegen" + "github.com/99designs/gqlgen/codegen/config" ) func TestLayoutSingleFile(t *testing.T) { @@ -83,6 +84,22 @@ func TestOmitTemplateComment(t *testing.T) { assertNoErrors(t, "github.com/99designs/gqlgen/plugin/resolvergen/testdata/omit_template_comment/out") } +func TestCustomResolverTemplate(t *testing.T) { + _ = syscall.Unlink("testdata/resolvertemplate/out/resolver.go") + cfg, err := config.LoadConfig("testdata/resolvertemplate/gqlgen.yml") + require.NoError(t, err) + p := Plugin{} + + require.NoError(t, cfg.Init()) + + data, err := codegen.BuildData(cfg) + if err != nil { + panic(err) + } + + require.NoError(t, p.GenerateCode(data)) +} + func testFollowSchemaPersistence(t *testing.T, dir string) { _ = syscall.Unlink(dir + "/out/resolver.go") diff --git a/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go b/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go index 63da9a5e14..c0c9b89f85 100644 --- a/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go +++ b/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go @@ -2,7 +2,7 @@ package customresolver // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.33-dev +// Code generated by github.com/99designs/gqlgen version v0.17.39-dev import ( "context" diff --git a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go index 40c378b608..c2b7384156 100644 --- a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go +++ b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go @@ -2,7 +2,7 @@ package customresolver // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.33-dev +// Code generated by github.com/99designs/gqlgen version v0.17.39-dev import ( "context" diff --git a/plugin/resolvergen/testdata/omit_template_comment/out/schema.resolvers.go b/plugin/resolvergen/testdata/omit_template_comment/out/schema.resolvers.go index 31ce8f5741..349f77fc44 100644 --- a/plugin/resolvergen/testdata/omit_template_comment/out/schema.resolvers.go +++ b/plugin/resolvergen/testdata/omit_template_comment/out/schema.resolvers.go @@ -2,7 +2,7 @@ package out // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.33-dev +// Code generated by github.com/99designs/gqlgen version v0.17.39-dev import ( "context" diff --git a/plugin/resolvergen/testdata/resolvertemplate/customResolverTemplate.gotpl b/plugin/resolvergen/testdata/resolvertemplate/customResolverTemplate.gotpl new file mode 100644 index 0000000000..ff652f0148 --- /dev/null +++ b/plugin/resolvergen/testdata/resolvertemplate/customResolverTemplate.gotpl @@ -0,0 +1,54 @@ +{{ reserveImport "context" }} +{{ reserveImport "fmt" }} +{{ reserveImport "io" }} +{{ reserveImport "strconv" }} +{{ reserveImport "time" }} +{{ reserveImport "sync" }} +{{ reserveImport "errors" }} +{{ reserveImport "bytes" }} +{{ reserveImport "encoding/json" }} + +{{ reserveImport "github.com/vektah/gqlparser/v2" }} +{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} +{{ reserveImport "github.com/99designs/gqlgen/graphql" }} +{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} + +{{ .Imports }} + +{{ if .HasRoot }} + type {{.ResolverType}} struct {} +{{ end }} + +{{ range $resolver := .Resolvers -}} + {{ if $resolver.Comment -}} + // {{ $resolver.Comment }} + {{- else if not $.OmitTemplateComment -}} + // {{ $resolver.Field.GoFieldName }} is the resolver for the {{ $resolver.Field.Name }} field. + {{- end }} + func (r *{{lcFirst $resolver.Object.Name}}{{ucFirst $.ResolverType}}) {{$resolver.Field.GoFieldName}}{{ with $resolver.PrevDecl }}{{ $resolver.Field.ShortResolverSignature .Type }}{{ else }}{{ $resolver.Field.ShortResolverDeclaration }}{{ end }}{ + // Custom Resolver implementation + panic(fmt.Errorf("custom Resolver not implemented: {{ $resolver.Field.GoFieldName }} - {{lcFirst $resolver.Field.GoFieldName }}")) + } + +{{ end }} + +{{ range $object := .Objects -}} + {{ if not $.OmitTemplateComment -}} + // {{ucFirst $object.Name}} returns {{ $object.ResolverInterface | ref }} implementation. + {{- end }} + func (r *{{$.ResolverType}}) {{ucFirst $object.Name}}() {{ $object.ResolverInterface | ref }} { return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r} } +{{ end }} + +{{ range $object := .Objects -}} + type {{lcFirst $object.Name}}{{ucFirst $.ResolverType}} struct { *{{$.ResolverType}} } +{{ end }} + +{{ if (ne .RemainingSource "") }} + // !!! WARNING !!! + // The code below was going to be deleted when updating resolvers. It has been copied here so you have + // one last chance to move it out of harms way if you want. There are two reasons this happens: + // - When renaming or deleting a resolver the old code will be put in here. You can safely delete + // it when you're done. + // - You have helper methods in this file. Move them out to keep these resolver files clean. + {{ .RemainingSource }} +{{ end }} diff --git a/plugin/resolvergen/testdata/resolvertemplate/gqlgen.yml b/plugin/resolvergen/testdata/resolvertemplate/gqlgen.yml new file mode 100644 index 0000000000..c7ce0aaddd --- /dev/null +++ b/plugin/resolvergen/testdata/resolvertemplate/gqlgen.yml @@ -0,0 +1,17 @@ +schema: + - "testdata/schema.graphql" + +exec: + filename: testdata/singlefile/out/ignored.go +model: + filename: testdata/singlefile/out/generated.go +resolver: + type: CustomResolverType + layout: follow-schema + dir: testdata/resolvertemplate/out + filename_template: "{name}.resolvers.go" + resolver_template: "testdata/resolvertemplate/customResolverTemplate.gotpl" + +models: + Resolver: + model: github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile/out.Resolver diff --git a/plugin/resolvergen/testdata/resolvertemplate/out/resolver.go b/plugin/resolvergen/testdata/resolvertemplate/out/resolver.go new file mode 100644 index 0000000000..640b3c53ff --- /dev/null +++ b/plugin/resolvergen/testdata/resolvertemplate/out/resolver.go @@ -0,0 +1,7 @@ +package out + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type CustomResolverType struct{} diff --git a/plugin/resolvergen/testdata/resolvertemplate/out/schema.resolvers.go b/plugin/resolvergen/testdata/resolvertemplate/out/schema.resolvers.go new file mode 100644 index 0000000000..27f0503602 --- /dev/null +++ b/plugin/resolvergen/testdata/resolvertemplate/out/schema.resolvers.go @@ -0,0 +1,35 @@ +package out + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.39-dev + +import ( + "context" + "fmt" + + customresolver "github.com/99designs/gqlgen/plugin/resolvergen/testdata/singlefile/out" +) + +// Resolver is the resolver for the resolver field. +func (r *queryCustomResolverType) Resolver(ctx context.Context) (*customresolver.Resolver, error) { + // Custom Resolver implementation + panic(fmt.Errorf("custom Resolver not implemented: Resolver - resolver")) +} + +// Name is the resolver for the name field. +func (r *resolverCustomResolverType) Name(ctx context.Context, obj *customresolver.Resolver) (string, error) { + // Custom Resolver implementation + panic(fmt.Errorf("custom Resolver not implemented: Name - name")) +} + +// Query returns customresolver.QueryResolver implementation. +func (r *CustomResolverType) Query() customresolver.QueryResolver { return &queryCustomResolverType{r} } + +// Resolver returns customresolver.ResolverResolver implementation. +func (r *CustomResolverType) Resolver() customresolver.ResolverResolver { + return &resolverCustomResolverType{r} +} + +type queryCustomResolverType struct{ *CustomResolverType } +type resolverCustomResolverType struct{ *CustomResolverType } diff --git a/plugin/stubgen/stubs.go b/plugin/stubgen/stubs.go index 01db65b1f9..8f1b81293c 100644 --- a/plugin/stubgen/stubs.go +++ b/plugin/stubgen/stubs.go @@ -5,11 +5,10 @@ import ( "path/filepath" "syscall" - "github.com/99designs/gqlgen/internal/code" - "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/internal/code" "github.com/99designs/gqlgen/plugin" )