From 481a4e44cbc114382dee087feb9cf18c15907d4a Mon Sep 17 00:00:00 2001 From: Franky W Date: Tue, 13 Apr 2021 10:03:09 +0200 Subject: [PATCH 01/12] Marshaling & Unmarshaling time return initial value There was a lack of symmetry that would prevent times for being symmetrical. That is because time.Parse actually parses an RFC3339Nano implicitly, thereby allowing nanosecond resolution on unmarshaling a time. Therefore we now marshal into nanoseconds, getting more information into GraphQL times when querying for a time, and restoring the symmetry --- graphql/time.go | 2 +- graphql/time_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 graphql/time_test.go diff --git a/graphql/time.go b/graphql/time.go index 9945f3fbfd..bd7b2e6080 100644 --- a/graphql/time.go +++ b/graphql/time.go @@ -13,7 +13,7 @@ func MarshalTime(t time.Time) Marshaler { } return WriterFunc(func(w io.Writer) { - io.WriteString(w, strconv.Quote(t.Format(time.RFC3339))) + io.WriteString(w, strconv.Quote(t.Format(time.RFC3339Nano))) }) } diff --git a/graphql/time_test.go b/graphql/time_test.go new file mode 100644 index 0000000000..32876436a6 --- /dev/null +++ b/graphql/time_test.go @@ -0,0 +1,25 @@ +package graphql + +import ( + "bytes" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestTime(t *testing.T) { + t.Run("symmetry", func(t *testing.T) { + initialTime := time.Now() + buf := bytes.NewBuffer([]byte{}) + MarshalTime(initialTime).MarshalGQL(buf) + + str, err := strconv.Unquote(buf.String()) + require.Nil(t, err) + newTime, err := UnmarshalTime(str) + require.Nil(t, err) + + require.True(t, initialTime.Equal(newTime), "expected times %v and %v to equal", initialTime, newTime) + }) +} From 43b56cbaf3f1de1d1ad379055ab1de157592cf38 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Wed, 15 Sep 2021 14:18:27 -0700 Subject: [PATCH 02/12] Forward `go mod tidy` stdout/stderr This is a command that can fail (in my case I think for stupid reasons in a hell of my own construction, but nonetheless). Right now we just get ``` $ go run github.com/Khan/webapp/dev/cmd/gqlgen tidy failed: go mod tidy failed: exit status 1 exit status 3 ``` which is not the most informative. Now, instead, we'll forward its output to our own stdout/stderr rather than devnull. --- internal/code/packages.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/code/packages.go b/internal/code/packages.go index 39ba374eab..17d59f42c9 100644 --- a/internal/code/packages.go +++ b/internal/code/packages.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "os" "os/exec" "path/filepath" @@ -155,6 +156,8 @@ func (p *Packages) Evict(importPath string) { func (p *Packages) ModTidy() error { p.packages = nil tidyCmd := exec.Command("go", "mod", "tidy") + tidyCmd.Stdout = os.Stdout + tidyCmd.Stderr = os.Stdout if err := tidyCmd.Run(); err != nil { return fmt.Errorf("go mod tidy failed: %w", err) } From 47ce074a3c30a981bbeef8f3465fca4330aba783 Mon Sep 17 00:00:00 2001 From: minus Date: Sat, 2 Oct 2021 16:28:06 +0200 Subject: [PATCH 03/12] Fix example run instructions Making ./example a separate Go module [1] broke the `go run` invocations listed in a few example readmes [2]. Using relative paths from the respective example directory should be clear enough. [1]: commit f93fb2489285eef0542c4aa7b11341a8479b606a / issue/PR #1607 [2]: example/todo/server/server.go:10:2: no required module provides package github.com/99designs/gqlgen/example/todo; to add it: go get github.com/99designs/gqlgen/example/todo --- example/chat/readme.md | 3 +-- example/fileupload/readme.md | 2 +- example/selection/readme.md | 2 +- example/starwars/readme.md | 2 +- example/todo/readme.md | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/example/chat/readme.md b/example/chat/readme.md index be1925cbb4..95b944fd82 100644 --- a/example/chat/readme.md +++ b/example/chat/readme.md @@ -4,12 +4,11 @@ Example app using subscriptions to build a chat room. to run this server ```bash -go run ./example/chat/server/server.go +go run ./server/server.go ``` to run the react app ```bash -cd ./example/chat npm install npm run start ``` diff --git a/example/fileupload/readme.md b/example/fileupload/readme.md index ae0eb4d095..9b859be69c 100644 --- a/example/fileupload/readme.md +++ b/example/fileupload/readme.md @@ -4,7 +4,7 @@ This server demonstrates how to handle file upload to run this server ```bash -go run ./example/fileupload/server/server.go +go run ./server/server.go ``` and open http://localhost:8087 in your browser diff --git a/example/selection/readme.md b/example/selection/readme.md index f37be6eb6a..6380de00f9 100644 --- a/example/selection/readme.md +++ b/example/selection/readme.md @@ -4,7 +4,7 @@ This is the simplest example of a graphql server. to run this server ```bash -go run ./example/selection/server/server.go +go run ./server/server.go ``` and open http://localhost:8086 in your browser diff --git a/example/starwars/readme.md b/example/starwars/readme.md index 921d2f01cf..e615c8889a 100644 --- a/example/starwars/readme.md +++ b/example/starwars/readme.md @@ -8,7 +8,7 @@ This server demonstrates a few advanced features of graphql: to run this server ```bash -go run ./example/starwars/server/server.go +go run ./server/server.go ``` and open http://localhost:8080 in your browser diff --git a/example/todo/readme.md b/example/todo/readme.md index e8dc2e8e18..bc94297ca9 100644 --- a/example/todo/readme.md +++ b/example/todo/readme.md @@ -4,7 +4,7 @@ This is the simplest example of a graphql server. to run this server ```bash -go run ./example/todo/server/server.go +go run ./server/server.go ``` and open http://localhost:8081 in your browser From 210c1aa6edf8b34d6e2f35e8c66b3d404cf7e8eb Mon Sep 17 00:00:00 2001 From: wilhelm Date: Thu, 7 Oct 2021 22:21:57 +1100 Subject: [PATCH 04/12] Appropriately Handle Falsy Default Field Values (#1623) --- codegen/input.gotpl | 2 +- codegen/testserver/defaults.graphql | 20 ++ codegen/testserver/defaults_test.go | 68 ++++++ codegen/testserver/generated.go | 365 +++++++++++++++++++++++++++- codegen/testserver/models-gen.go | 10 + codegen/testserver/resolver.go | 8 + codegen/testserver/schema.graphql | 20 +- codegen/testserver/stub.go | 8 + 8 files changed, 486 insertions(+), 15 deletions(-) create mode 100644 codegen/testserver/defaults.graphql create mode 100644 codegen/testserver/defaults_test.go diff --git a/codegen/input.gotpl b/codegen/input.gotpl index c6eac4d47b..64681b61da 100644 --- a/codegen/input.gotpl +++ b/codegen/input.gotpl @@ -7,7 +7,7 @@ asMap[k] = v } {{ range $field := .Fields}} - {{- if $field.Default}} + {{- if notNil "Default" $field }} if _, present := asMap[{{$field.Name|quote}}] ; !present { asMap[{{$field.Name|quote}}] = {{ $field.Default | dump }} } diff --git a/codegen/testserver/defaults.graphql b/codegen/testserver/defaults.graphql new file mode 100644 index 0000000000..1c9a9ebf03 --- /dev/null +++ b/codegen/testserver/defaults.graphql @@ -0,0 +1,20 @@ +extend type Query { + defaultParameters( + falsyBoolean: Boolean = false + truthyBoolean: Boolean = true + ): DefaultParametersMirror! +} + +extend type Mutation { + defaultInput(input: DefaultInput!): DefaultParametersMirror! +} + +input DefaultInput { + falsyBoolean: Boolean = false + truthyBoolean: Boolean = true +} + +type DefaultParametersMirror { + falsyBoolean: Boolean + truthyBoolean: Boolean +} diff --git a/codegen/testserver/defaults_test.go b/codegen/testserver/defaults_test.go new file mode 100644 index 0000000000..2bb1f85de6 --- /dev/null +++ b/codegen/testserver/defaults_test.go @@ -0,0 +1,68 @@ +package testserver + +import ( + "context" + "testing" + + "github.com/99designs/gqlgen/client" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/stretchr/testify/require" +) + +func assertDefaults(t *testing.T, ret *DefaultParametersMirror) { + require.NotNil(t, ret) + require.NotNil(t, ret.FalsyBoolean) + require.Equal(t, *ret.FalsyBoolean, false) + require.NotNil(t, ret.TruthyBoolean) + require.Equal(t, *ret.TruthyBoolean, true) +} + +func TestDefaults(t *testing.T) { + resolvers := &Stub{} + srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers})) + c := client.New(srv) + + t.Run("default field parameters", func(t *testing.T) { + resolvers.QueryResolver.DefaultParameters = func( + ctx context.Context, + falsyBoolean, truthyBoolean *bool, + ) (*DefaultParametersMirror, error) { + return &DefaultParametersMirror{ + FalsyBoolean: falsyBoolean, + TruthyBoolean: truthyBoolean, + }, nil + } + + var resp struct{ DefaultParameters *DefaultParametersMirror } + err := c.Post(`query { + defaultParameters { + falsyBoolean + truthyBoolean + } + }`, &resp) + require.NoError(t, err) + assertDefaults(t, resp.DefaultParameters) + }) + + t.Run("default input fields", func(t *testing.T) { + resolvers.MutationResolver.DefaultInput = func( + ctx context.Context, + input DefaultInput, + ) (*DefaultParametersMirror, error) { + return &DefaultParametersMirror{ + FalsyBoolean: input.FalsyBoolean, + TruthyBoolean: input.TruthyBoolean, + }, nil + } + + var resp struct{ DefaultInput *DefaultParametersMirror } + err := c.Post(`mutation { + defaultInput(input: {}) { + falsyBoolean + truthyBoolean + } + }`, &resp) + require.NoError(t, err) + assertDefaults(t, resp.DefaultInput) + }) +} diff --git a/codegen/testserver/generated.go b/codegen/testserver/generated.go index 7995011c09..b03dc9769e 100644 --- a/codegen/testserver/generated.go +++ b/codegen/testserver/generated.go @@ -136,6 +136,11 @@ type ComplexityRoot struct { Foo func(childComplexity int) int } + DefaultParametersMirror struct { + FalsyBoolean func(childComplexity int) int + TruthyBoolean func(childComplexity int) int + } + Dog struct { DogBreed func(childComplexity int) int Species func(childComplexity int) int @@ -217,6 +222,7 @@ type ComplexityRoot struct { } Mutation struct { + DefaultInput func(childComplexity int, input DefaultInput) int UpdateSomething func(childComplexity int, input SpecialInput) int } @@ -265,6 +271,7 @@ type ComplexityRoot struct { Animal func(childComplexity int) int Autobind func(childComplexity int) int Collision func(childComplexity int) int + DefaultParameters func(childComplexity int, falsyBoolean *bool, truthyBoolean *bool) int DefaultScalar func(childComplexity int, arg string) int DeprecatedField func(childComplexity int) int DirectiveArg func(childComplexity int, arg string) int @@ -416,6 +423,7 @@ type ModelMethodsResolver interface { ResolverField(ctx context.Context, obj *ModelMethods) (bool, error) } type MutationResolver interface { + DefaultInput(ctx context.Context, input DefaultInput) (*DefaultParametersMirror, error) UpdateSomething(ctx context.Context, input SpecialInput) (string, error) } type OverlappingFieldsResolver interface { @@ -450,6 +458,7 @@ type QueryResolver interface { Autobind(ctx context.Context) (*Autobind, error) DeprecatedField(ctx context.Context) (string, error) Overlapping(ctx context.Context) (*OverlappingFields, error) + DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) DirectiveArg(ctx context.Context, arg string) (*string, error) DirectiveNullableArg(ctx context.Context, arg *int, arg2 *int, arg3 *string) (*string, error) DirectiveInputNullable(ctx context.Context, arg *InputDirectives) (*string, error) @@ -698,6 +707,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ContentUser.Foo(childComplexity), true + case "DefaultParametersMirror.falsyBoolean": + if e.complexity.DefaultParametersMirror.FalsyBoolean == nil { + break + } + + return e.complexity.DefaultParametersMirror.FalsyBoolean(childComplexity), true + + case "DefaultParametersMirror.truthyBoolean": + if e.complexity.DefaultParametersMirror.TruthyBoolean == nil { + break + } + + return e.complexity.DefaultParametersMirror.TruthyBoolean(childComplexity), true + case "Dog.dogBreed": if e.complexity.Dog.DogBreed == nil { break @@ -901,6 +924,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ModelMethods.WithContext(childComplexity), true + case "Mutation.defaultInput": + if e.complexity.Mutation.DefaultInput == nil { + break + } + + args, err := ec.field_Mutation_defaultInput_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DefaultInput(childComplexity, args["input"].(DefaultInput)), true + case "Mutation.updateSomething": if e.complexity.Mutation.UpdateSomething == nil { break @@ -1063,6 +1098,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Collision(childComplexity), true + case "Query.defaultParameters": + if e.complexity.Query.DefaultParameters == nil { + break + } + + args, err := ec.field_Query_defaultParameters_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.DefaultParameters(childComplexity, args["falsyBoolean"].(*bool), args["truthyBoolean"].(*bool)), true + case "Query.defaultScalar": if e.complexity.Query.DefaultScalar == nil { break @@ -1907,6 +1954,27 @@ type OverlappingFields { newFoo: Int! new_foo: Int! } +`, BuiltIn: false}, + {Name: "defaults.graphql", Input: `extend type Query { + defaultParameters( + falsyBoolean: Boolean = false + truthyBoolean: Boolean = true + ): DefaultParametersMirror! +} + +extend type Mutation { + defaultInput(input: DefaultInput!): DefaultParametersMirror! +} + +input DefaultInput { + falsyBoolean: Boolean = false + truthyBoolean: Boolean = true +} + +type DefaultParametersMirror { + falsyBoolean: Boolean + truthyBoolean: Boolean +} `, BuiltIn: false}, {Name: "directive.graphql", Input: `directive @length(min: Int!, max: Int, message: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | FIELD_DEFINITION directive @range(min: Int = 0, max: Int) on ARGUMENT_DEFINITION @@ -2188,15 +2256,21 @@ type EmbeddedDefaultScalar { value: DefaultScalarImplementation } `, BuiltIn: false}, - {Name: "schema.graphql", Input: `directive @goModel(model: String, models: [String!]) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION -directive @goField(forceResolver: Boolean, name: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION + {Name: "schema.graphql", Input: `directive @goModel( + model: String + models: [String!] +) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION +directive @goField( + forceResolver: Boolean + name: String +) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION type Query { invalidIdentifier: InvalidIdentifier collision: It mapInput(input: Changes): Boolean recursive(input: RecursiveInputSlice): Boolean - nestedInputs(input: [[OuterInput]] = [[{inner: {id: 1}}]]): Boolean + nestedInputs(input: [[OuterInput]] = [[{ inner: { id: 1 } }]]): Boolean nestedOutputs: [[OuterObject]] modelMethods: ModelMethods user(id: Int!): User! @@ -2243,7 +2317,7 @@ type It { id: ID! } -input Changes @goModel(model:"map[string]interface{}") { +input Changes @goModel(model: "map[string]interface{}") { a: Int b: Int } @@ -2253,14 +2327,14 @@ input RecursiveInputSlice { } input InnerInput { - id:Int! + id: Int! } input OuterInput { inner: InnerInput! } -scalar ThirdParty @goModel(model:"testserver.ThirdParty") +scalar ThirdParty @goModel(model: "testserver.ThirdParty") type OuterObject { inner: InnerObject! @@ -2274,7 +2348,7 @@ type ForcedResolver { field: Circle @goField(forceResolver: true) } -type EmbeddedPointer @goModel(model:"testserver.EmbeddedPointerModel") { +type EmbeddedPointer @goModel(model: "testserver.EmbeddedPointerModel") { ID: String Title: String } @@ -2550,6 +2624,21 @@ func (ec *executionContext) dir_range_args(ctx context.Context, rawArgs map[stri return args, nil } +func (ec *executionContext) field_Mutation_defaultInput_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 DefaultInput + if tmp, ok := rawArgs["input"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + arg0, err = ec.unmarshalNDefaultInput2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐDefaultInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateSomething_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2610,6 +2699,30 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_defaultParameters_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["falsyBoolean"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("falsyBoolean")) + arg0, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["falsyBoolean"] = arg0 + var arg1 *bool + if tmp, ok := rawArgs["truthyBoolean"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("truthyBoolean")) + arg1, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["truthyBoolean"] = arg1 + return args, nil +} + func (ec *executionContext) field_Query_defaultScalar_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -4221,6 +4334,64 @@ func (ec *executionContext) _Content_User_foo(ctx context.Context, field graphql return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } +func (ec *executionContext) _DefaultParametersMirror_falsyBoolean(ctx context.Context, field graphql.CollectedField, obj *DefaultParametersMirror) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DefaultParametersMirror", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.FalsyBoolean, nil + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) _DefaultParametersMirror_truthyBoolean(ctx context.Context, field graphql.CollectedField, obj *DefaultParametersMirror) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DefaultParametersMirror", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.TruthyBoolean, nil + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + func (ec *executionContext) _Dog_species(ctx context.Context, field graphql.CollectedField, obj *Dog) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5146,6 +5317,45 @@ func (ec *executionContext) _ModelMethods_withContext(ctx context.Context, field return ec.marshalNBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_defaultInput(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_defaultInput_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DefaultInput(rctx, args["input"].(DefaultInput)) + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*DefaultParametersMirror) + fc.Result = res + return ec.marshalNDefaultParametersMirror2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐDefaultParametersMirror(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_updateSomething(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6369,6 +6579,45 @@ func (ec *executionContext) _Query_overlapping(ctx context.Context, field graphq return ec.marshalOOverlappingFields2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐOverlappingFields(ctx, field.Selections, res) } +func (ec *executionContext) _Query_defaultParameters(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_defaultParameters_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().DefaultParameters(rctx, args["falsyBoolean"].(*bool), args["truthyBoolean"].(*bool)) + }) + + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*DefaultParametersMirror) + fc.Result = res + return ec.marshalNDefaultParametersMirror2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐDefaultParametersMirror(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_directiveArg(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -10217,6 +10466,44 @@ func (ec *executionContext) _iIt_id(ctx context.Context, field graphql.Collected // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputDefaultInput(ctx context.Context, obj interface{}) (DefaultInput, error) { + var it DefaultInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + if _, present := asMap["falsyBoolean"]; !present { + asMap["falsyBoolean"] = false + } + if _, present := asMap["truthyBoolean"]; !present { + asMap["truthyBoolean"] = true + } + + for k, v := range asMap { + switch k { + case "falsyBoolean": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("falsyBoolean")) + it.FalsyBoolean, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + case "truthyBoolean": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("truthyBoolean")) + it.TruthyBoolean, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputInnerDirectives(ctx context.Context, obj interface{}) (InnerDirectives, error) { var it InnerDirectives asMap := map[string]interface{}{} @@ -11357,6 +11644,32 @@ func (ec *executionContext) _Content_User(ctx context.Context, sel ast.Selection return out } +var defaultParametersMirrorImplementors = []string{"DefaultParametersMirror"} + +func (ec *executionContext) _DefaultParametersMirror(ctx context.Context, sel ast.SelectionSet, obj *DefaultParametersMirror) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, defaultParametersMirrorImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DefaultParametersMirror") + case "falsyBoolean": + out.Values[i] = ec._DefaultParametersMirror_falsyBoolean(ctx, field, obj) + case "truthyBoolean": + out.Values[i] = ec._DefaultParametersMirror_truthyBoolean(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var dogImplementors = []string{"Dog", "Animal"} func (ec *executionContext) _Dog(ctx context.Context, sel ast.SelectionSet, obj *Dog) graphql.Marshaler { @@ -11942,6 +12255,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Mutation") + case "defaultInput": + out.Values[i] = ec._Mutation_defaultInput(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "updateSomething": out.Values[i] = ec._Mutation_updateSomething(ctx, field) if out.Values[i] == graphql.Null { @@ -12478,6 +12796,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_overlapping(ctx, field) return res }) + case "defaultParameters": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_defaultParameters(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "directiveArg": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -13769,6 +14101,25 @@ func (ec *executionContext) marshalNCheckIssue8962ᚖgithubᚗcomᚋ99designsᚋ return ec._CheckIssue896(ctx, sel, v) } +func (ec *executionContext) unmarshalNDefaultInput2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐDefaultInput(ctx context.Context, v interface{}) (DefaultInput, error) { + res, err := ec.unmarshalInputDefaultInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDefaultParametersMirror2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐDefaultParametersMirror(ctx context.Context, sel ast.SelectionSet, v DefaultParametersMirror) graphql.Marshaler { + return ec._DefaultParametersMirror(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDefaultParametersMirror2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐDefaultParametersMirror(ctx context.Context, sel ast.SelectionSet, v *DefaultParametersMirror) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._DefaultParametersMirror(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDefaultScalarImplementation2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/codegen/testserver/models-gen.go b/codegen/testserver/models-gen.go index 3bae2a74c6..9dc8fd02fb 100644 --- a/codegen/testserver/models-gen.go +++ b/codegen/testserver/models-gen.go @@ -64,6 +64,16 @@ type ContentUser struct { func (ContentUser) IsContentChild() {} +type DefaultInput struct { + FalsyBoolean *bool `json:"falsyBoolean"` + TruthyBoolean *bool `json:"truthyBoolean"` +} + +type DefaultParametersMirror struct { + FalsyBoolean *bool `json:"falsyBoolean"` + TruthyBoolean *bool `json:"truthyBoolean"` +} + type Dog struct { Species string `json:"species"` DogBreed string `json:"dogBreed"` diff --git a/codegen/testserver/resolver.go b/codegen/testserver/resolver.go index ed813bf511..20f8a7b726 100644 --- a/codegen/testserver/resolver.go +++ b/codegen/testserver/resolver.go @@ -44,6 +44,10 @@ func (r *modelMethodsResolver) ResolverField(ctx context.Context, obj *ModelMeth panic("not implemented") } +func (r *mutationResolver) DefaultInput(ctx context.Context, input DefaultInput) (*DefaultParametersMirror, error) { + panic("not implemented") +} + func (r *mutationResolver) UpdateSomething(ctx context.Context, input SpecialInput) (string, error) { panic("not implemented") } @@ -132,6 +136,10 @@ func (r *queryResolver) Overlapping(ctx context.Context) (*OverlappingFields, er panic("not implemented") } +func (r *queryResolver) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) { + panic("not implemented") +} + func (r *queryResolver) DirectiveArg(ctx context.Context, arg string) (*string, error) { panic("not implemented") } diff --git a/codegen/testserver/schema.graphql b/codegen/testserver/schema.graphql index 1672d7be8a..ebc67e129e 100644 --- a/codegen/testserver/schema.graphql +++ b/codegen/testserver/schema.graphql @@ -1,12 +1,18 @@ -directive @goModel(model: String, models: [String!]) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION -directive @goField(forceResolver: Boolean, name: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION +directive @goModel( + model: String + models: [String!] +) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION +directive @goField( + forceResolver: Boolean + name: String +) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION type Query { invalidIdentifier: InvalidIdentifier collision: It mapInput(input: Changes): Boolean recursive(input: RecursiveInputSlice): Boolean - nestedInputs(input: [[OuterInput]] = [[{inner: {id: 1}}]]): Boolean + nestedInputs(input: [[OuterInput]] = [[{ inner: { id: 1 } }]]): Boolean nestedOutputs: [[OuterObject]] modelMethods: ModelMethods user(id: Int!): User! @@ -53,7 +59,7 @@ type It { id: ID! } -input Changes @goModel(model:"map[string]interface{}") { +input Changes @goModel(model: "map[string]interface{}") { a: Int b: Int } @@ -63,14 +69,14 @@ input RecursiveInputSlice { } input InnerInput { - id:Int! + id: Int! } input OuterInput { inner: InnerInput! } -scalar ThirdParty @goModel(model:"testserver.ThirdParty") +scalar ThirdParty @goModel(model: "testserver.ThirdParty") type OuterObject { inner: InnerObject! @@ -84,7 +90,7 @@ type ForcedResolver { field: Circle @goField(forceResolver: true) } -type EmbeddedPointer @goModel(model:"testserver.EmbeddedPointerModel") { +type EmbeddedPointer @goModel(model: "testserver.EmbeddedPointerModel") { ID: String Title: String } diff --git a/codegen/testserver/stub.go b/codegen/testserver/stub.go index 8b6d7671a2..310de5587d 100644 --- a/codegen/testserver/stub.go +++ b/codegen/testserver/stub.go @@ -28,6 +28,7 @@ type Stub struct { ResolverField func(ctx context.Context, obj *ModelMethods) (bool, error) } MutationResolver struct { + DefaultInput func(ctx context.Context, input DefaultInput) (*DefaultParametersMirror, error) UpdateSomething func(ctx context.Context, input SpecialInput) (string, error) } OverlappingFieldsResolver struct { @@ -60,6 +61,7 @@ type Stub struct { Autobind func(ctx context.Context) (*Autobind, error) DeprecatedField func(ctx context.Context) (string, error) Overlapping func(ctx context.Context) (*OverlappingFields, error) + DefaultParameters func(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) DirectiveArg func(ctx context.Context, arg string) (*string, error) DirectiveNullableArg func(ctx context.Context, arg *int, arg2 *int, arg3 *string) (*string, error) DirectiveInputNullable func(ctx context.Context, arg *InputDirectives) (*string, error) @@ -207,6 +209,9 @@ func (r *stubModelMethods) ResolverField(ctx context.Context, obj *ModelMethods) type stubMutation struct{ *Stub } +func (r *stubMutation) DefaultInput(ctx context.Context, input DefaultInput) (*DefaultParametersMirror, error) { + return r.MutationResolver.DefaultInput(ctx, input) +} func (r *stubMutation) UpdateSomething(ctx context.Context, input SpecialInput) (string, error) { return r.MutationResolver.UpdateSomething(ctx, input) } @@ -288,6 +293,9 @@ func (r *stubQuery) DeprecatedField(ctx context.Context) (string, error) { func (r *stubQuery) Overlapping(ctx context.Context) (*OverlappingFields, error) { return r.QueryResolver.Overlapping(ctx) } +func (r *stubQuery) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) { + return r.QueryResolver.DefaultParameters(ctx, falsyBoolean, truthyBoolean) +} func (r *stubQuery) DirectiveArg(ctx context.Context, arg string) (*string, error) { return r.QueryResolver.DirectiveArg(ctx, arg) } From a3d9e8ce9689533ab8c3ab4b3b4cd22df3cbfa03 Mon Sep 17 00:00:00 2001 From: Ashish Malik <87957768+ash99d@users.noreply.github.com> Date: Thu, 7 Oct 2021 22:37:05 +1100 Subject: [PATCH 05/12] Remove redundant favicon (#1638) --- graphql/playground/playground.go | 1 - 1 file changed, 1 deletion(-) diff --git a/graphql/playground/playground.go b/graphql/playground/playground.go index 12f1030a8d..9809499199 100644 --- a/graphql/playground/playground.go +++ b/graphql/playground/playground.go @@ -10,7 +10,6 @@ var page = template.Must(template.New("graphiql").Parse(` - Date: Sun, 10 Oct 2021 22:00:40 +0900 Subject: [PATCH 06/12] Update time format for `Time` scalar (#1648) * Use more precise time format * update test * update docs * Apply suggestions from code review * Update scalars.md --- codegen/testserver/time_test.go | 8 ++++---- docs/content/reference/scalars.md | 2 +- graphql/time.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/codegen/testserver/time_test.go b/codegen/testserver/time_test.go index 291567e7a1..012625283d 100644 --- a/codegen/testserver/time_test.go +++ b/codegen/testserver/time_test.go @@ -45,9 +45,9 @@ func TestTime(t *testing.T) { t.Run("with values", func(t *testing.T) { resolvers.QueryResolver.User = func(ctx context.Context, id int) (user *User, e error) { - updated := time.Date(2010, 1, 1, 0, 0, 20, 0, time.UTC) + updated := time.Date(2010, 1, 1, 0, 0, 20, 1, time.UTC) return &User{ - Created: time.Date(2010, 1, 1, 0, 0, 10, 0, time.UTC), + Created: time.Date(2010, 1, 1, 0, 0, 10, 1, time.UTC), Updated: &updated, }, nil } @@ -62,7 +62,7 @@ func TestTime(t *testing.T) { err := c.Post(`query { user(id: 1) { created, updated } }`, &resp) require.NoError(t, err) - require.Equal(t, "2010-01-01T00:00:10Z", resp.User.Created) - require.Equal(t, "2010-01-01T00:00:20Z", resp.User.Updated) + require.Equal(t, "2010-01-01T00:00:10.000000001Z", resp.User.Created) + require.Equal(t, "2010-01-01T00:00:20.000000001Z", resp.User.Updated) }) } diff --git a/docs/content/reference/scalars.md b/docs/content/reference/scalars.md index 9bda307f55..10e835d42c 100644 --- a/docs/content/reference/scalars.md +++ b/docs/content/reference/scalars.md @@ -15,7 +15,7 @@ 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. +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. ### Map diff --git a/graphql/time.go b/graphql/time.go index bd7b2e6080..ef3d17da32 100644 --- a/graphql/time.go +++ b/graphql/time.go @@ -19,7 +19,7 @@ func MarshalTime(t time.Time) Marshaler { func UnmarshalTime(v interface{}) (time.Time, error) { if tmpStr, ok := v.(string); ok { - return time.Parse(time.RFC3339, tmpStr) + return time.Parse(time.RFC3339Nano, tmpStr) } - return time.Time{}, errors.New("time should be RFC3339 formatted string") + return time.Time{}, errors.New("time should be RFC3339Nano formatted string") } From 5287e4e5f30548d233f58111d128787c673c7f01 Mon Sep 17 00:00:00 2001 From: Richard Lindhout Date: Sun, 10 Oct 2021 16:02:15 +0200 Subject: [PATCH 07/12] Add QR and KVK to common initialisms (#1419) * Add QR and KVK to common initialisms * Update templates.go * Sort commonInitialisms Co-authored-by: Steve Coffman --- codegen/templates/templates.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 3518a48d02..77bac84736 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -458,9 +458,11 @@ var commonInitialisms = map[string]bool{ "ID": true, "IP": true, "JSON": true, + "KVK": true, "LHS": true, "PGP": true, "QPS": true, + "QR": true, "RAM": true, "RHS": true, "RPC": true, @@ -468,16 +470,17 @@ var commonInitialisms = map[string]bool{ "SMTP": true, "SQL": true, "SSH": true, + "SVG": true, "TCP": true, "TLS": true, "TTL": true, "UDP": true, "UI": true, "UID": true, - "UUID": true, "URI": true, "URL": true, "UTF8": true, + "UUID": true, "VM": true, "XML": true, "XMPP": true, From 7081dedb0efc6ed650118c7fce65ca3bdb33b8de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:02:58 -0400 Subject: [PATCH 08/12] Bump tmpl from 1.0.4 to 1.0.5 in /integration (#1627) Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5. - [Release notes](https://github.com/daaku/nodejs-tmpl/releases) - [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5) --- updated-dependencies: - dependency-name: tmpl dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/package-lock.json b/integration/package-lock.json index 00e48730d7..cc5129bc42 100644 --- a/integration/package-lock.json +++ b/integration/package-lock.json @@ -10336,9 +10336,9 @@ } }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, "to-fast-properties": { From 50f6a2aa603842fcdc158ab135fa117d1716d7e2 Mon Sep 17 00:00:00 2001 From: Steve Coffman Date: Mon, 11 Oct 2021 09:16:01 -0400 Subject: [PATCH 09/12] Fixes #1653: update docs and wrap error if not *gqlerror.Error (#1654) Signed-off-by: Steve Coffman --- docs/content/reference/errors.md | 2 +- graphql/error.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/content/reference/errors.md b/docs/content/reference/errors.md index fa1a7f8950..69acbf8971 100644 --- a/docs/content/reference/errors.md +++ b/docs/content/reference/errors.md @@ -114,7 +114,7 @@ server := handler.NewDefaultServer(MakeExecutableSchema(resolvers) server.SetRecoverFunc(func(ctx context.Context, err interface{}) error { // notify bug tracker... - return errors.New("Internal server error!") + return gqlerror.Errorf("Internal server error!") }) ``` diff --git a/graphql/error.go b/graphql/error.go index 9e38fe4237..4fe520b285 100644 --- a/graphql/error.go +++ b/graphql/error.go @@ -10,19 +10,23 @@ import ( type ErrorPresenterFunc func(ctx context.Context, err error) *gqlerror.Error func DefaultErrorPresenter(ctx context.Context, err error) *gqlerror.Error { - return err.(*gqlerror.Error) + var gqlErr *gqlerror.Error + if errors.As(err, &gqlErr) { + return gqlErr + } + return gqlerror.WrapPath(GetPath(ctx), err) } func ErrorOnPath(ctx context.Context, err error) error { if err == nil { return nil } - var gqlerr *gqlerror.Error - if errors.As(err, &gqlerr) { - if gqlerr.Path == nil { - gqlerr.Path = GetPath(ctx) + var gqlErr *gqlerror.Error + if errors.As(err, &gqlErr) { + if gqlErr.Path == nil { + gqlErr.Path = GetPath(ctx) } - return gqlerr + return gqlErr } return gqlerror.WrapPath(GetPath(ctx), err) } From 589a774290cfaf8f39d6099650e930c6f10cd670 Mon Sep 17 00:00:00 2001 From: Tim Kuhlman Date: Mon, 11 Oct 2021 17:28:12 -0600 Subject: [PATCH 10/12] Enable lowercase type names in GraphQL schema to properly render (#1359) The difficulty with lowercased type names is that in go code any lowercased name is not exported. This change makes the names title case for go code while preserving the proper case when interacting with the GraphQL schema. Co-authored-by: Liam Murphy --- codegen/field.go | 2 +- codegen/generated!.gotpl | 6 +- codegen/object.go | 2 +- example/config/generated.go | 144 ++++++++++++++++++++++++++++++ example/config/model.go | 5 ++ example/config/user.graphql | 6 ++ example/config/user.resolvers.go | 20 +++++ plugin/resolvergen/resolver.go | 2 +- plugin/resolvergen/resolver.gotpl | 4 +- 9 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 example/config/user.resolvers.go diff --git a/codegen/field.go b/codegen/field.go index 86fee340d4..0833c1ebfb 100644 --- a/codegen/field.go +++ b/codegen/field.go @@ -461,7 +461,7 @@ func (f *Field) GoNameUnexported() string { } func (f *Field) ShortInvocation() string { - return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.Name, f.GoFieldName, f.CallArgs()) + return fmt.Sprintf("%s().%s(%s)", strings.Title(f.Object.Definition.Name), f.GoFieldName, f.CallArgs()) } func (f *Field) ArgsFunc() string { diff --git a/codegen/generated!.gotpl b/codegen/generated!.gotpl index 864d15deb5..e3304d1d8b 100644 --- a/codegen/generated!.gotpl +++ b/codegen/generated!.gotpl @@ -32,7 +32,7 @@ type Config struct { type ResolverRoot interface { {{- range $object := .Objects -}} {{ if $object.HasResolvers -}} - {{$object.Name}}() {{$object.Name}}Resolver + {{ucFirst $object.Name}}() {{ucFirst $object.Name}}Resolver {{ end }} {{- end }} } @@ -46,7 +46,7 @@ type DirectiveRoot struct { type ComplexityRoot struct { {{ range $object := .Objects }} {{ if not $object.IsReserved -}} - {{ $object.Name|go }} struct { + {{ucFirst $object.Name|go }} struct { {{ range $_, $fields := $object.UniqueFields }} {{- $field := index $fields 0 -}} {{ if not $field.IsReserved -}} @@ -60,7 +60,7 @@ type ComplexityRoot struct { {{ range $object := .Objects -}} {{ if $object.HasResolvers }} - type {{$object.Name}}Resolver interface { + type {{ucFirst $object.Name}}Resolver interface { {{ range $field := $object.Fields -}} {{- if $field.IsResolver }} {{- $field.GoFieldName}}{{ $field.ShortResolverDeclaration }} diff --git a/codegen/object.go b/codegen/object.go index f730a7e1e3..6cf922d47c 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -46,7 +46,7 @@ func (b *builder) buildObject(typ *ast.Definition) (*Object, error) { Stream: typ == b.Schema.Subscription, Directives: dirs, ResolverInterface: types.NewNamed( - types.NewTypeName(0, b.Config.Exec.Pkg(), typ.Name+"Resolver", nil), + types.NewTypeName(0, b.Config.Exec.Pkg(), strings.Title(typ.Name)+"Resolver", nil), nil, nil, ), diff --git a/example/config/generated.go b/example/config/generated.go index 5c1c8afad4..192997e0b7 100644 --- a/example/config/generated.go +++ b/example/config/generated.go @@ -37,6 +37,7 @@ type ResolverRoot interface { Mutation() MutationResolver Query() QueryResolver Todo() TodoResolver + Role() RoleResolver } type DirectiveRoot struct { @@ -62,6 +63,11 @@ type ComplexityRoot struct { User struct { FullName func(childComplexity int) int ID func(childComplexity int) int + Role func(childComplexity int) int + } + + Role struct { + Name func(childComplexity int) int } } @@ -74,6 +80,9 @@ type QueryResolver interface { type TodoResolver interface { ID(ctx context.Context, obj *Todo) (string, error) } +type RoleResolver interface { + Name(ctx context.Context, obj *UserRole) (string, error) +} type executableSchema struct { resolvers ResolverRoot @@ -158,6 +167,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.ID(childComplexity), true + case "User.role": + if e.complexity.User.Role == nil { + break + } + + return e.complexity.User.Role(childComplexity), true + + case "role.name": + if e.complexity.Role.Name == nil { + break + } + + return e.complexity.Role.Name(childComplexity), true + } return 0, false } @@ -250,6 +273,12 @@ input NewTodo { @goModel(model:"github.com/99designs/gqlgen/example/config.User") { id: ID! name: String! @goField(name:"FullName") + role: role! +} + +type role +@goModel(model:"github.com/99designs/gqlgen/example/config.UserRole") { + name: String! } `, BuiltIn: false}, } @@ -720,6 +749,41 @@ func (ec *executionContext) _User_name(ctx context.Context, field graphql.Collec return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _User_role(ctx context.Context, field graphql.CollectedField, obj *User) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "User", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Role, 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.(UserRole) + fc.Result = res + return ec.marshalNrole2githubᚗcomᚋ99designsᚋgqlgenᚋexampleᚋconfigᚐUserRole(ctx, field.Selections, res) +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -1838,6 +1902,41 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) } +func (ec *executionContext) _role_name(ctx context.Context, field graphql.CollectedField, obj *UserRole) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "role", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Role().Name(rctx, obj) + }) + 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) +} + // endregion **************************** field.gotpl ***************************** // region **************************** input.gotpl ***************************** @@ -2033,6 +2132,11 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { invalids++ } + case "role": + out.Values[i] = ec._User_role(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -2290,6 +2394,42 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o return out } +var roleImplementors = []string{"role"} + +func (ec *executionContext) _role(ctx context.Context, sel ast.SelectionSet, obj *UserRole) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, roleImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("role") + case "name": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._role_name(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + // endregion **************************** object.gotpl **************************** // region ***************************** type.gotpl ***************************** @@ -2684,6 +2824,10 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } +func (ec *executionContext) marshalNrole2githubᚗcomᚋ99designsᚋgqlgenᚋexampleᚋconfigᚐUserRole(ctx context.Context, sel ast.SelectionSet, v UserRole) graphql.Marshaler { + return ec._role(ctx, sel, &v) +} + 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/example/config/model.go b/example/config/model.go index 46038be7f8..4632c4eade 100644 --- a/example/config/model.go +++ b/example/config/model.go @@ -5,8 +5,13 @@ import "fmt" type User struct { ID string FirstName, LastName string + Role UserRole } func (user *User) FullName() string { return fmt.Sprintf("%s %s", user.FirstName, user.LastName) } + +type UserRole struct { + RoleName string +} diff --git a/example/config/user.graphql b/example/config/user.graphql index 52c688bd33..5fb3772fc2 100644 --- a/example/config/user.graphql +++ b/example/config/user.graphql @@ -2,4 +2,10 @@ type User @goModel(model:"github.com/99designs/gqlgen/example/config.User") { id: ID! name: String! @goField(name:"FullName") + role: role! +} + +type role +@goModel(model:"github.com/99designs/gqlgen/example/config.UserRole") { + name: String! } diff --git a/example/config/user.resolvers.go b/example/config/user.resolvers.go new file mode 100644 index 0000000000..1d819f9943 --- /dev/null +++ b/example/config/user.resolvers.go @@ -0,0 +1,20 @@ +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. + +import ( + "context" +) + +func (r *roleResolver) Name(ctx context.Context, obj *UserRole) (string, error) { + if obj == nil { + return "", nil + } + return obj.RoleName, nil +} + +// Role returns RoleResolver implementation. +func (r *Resolver) Role() RoleResolver { return &roleResolver{r} } + +type roleResolver struct{ *Resolver } diff --git a/plugin/resolvergen/resolver.go b/plugin/resolvergen/resolver.go index b498542624..857f5ea31d 100644 --- a/plugin/resolvergen/resolver.go +++ b/plugin/resolvergen/resolver.go @@ -95,7 +95,7 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error { } rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)) - rewriter.GetMethodBody(data.Config.Resolver.Type, o.Name) + rewriter.GetMethodBody(data.Config.Resolver.Type, strings.Title(o.Name)) files[fn].Objects = append(files[fn].Objects, o) } for _, f := range o.Fields { diff --git a/plugin/resolvergen/resolver.gotpl b/plugin/resolvergen/resolver.gotpl index 543bf136e8..16c684d628 100644 --- a/plugin/resolvergen/resolver.gotpl +++ b/plugin/resolvergen/resolver.gotpl @@ -26,8 +26,8 @@ {{ end }} {{ range $object := .Objects -}} - // {{$object.Name}} returns {{ $object.ResolverInterface | ref }} implementation. - func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} { return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r} } + // {{ucFirst $object.Name}} returns {{ $object.ResolverInterface | ref }} implementation. + func (r *{{$.ResolverType}}) {{ucFirst $object.Name}}() {{ $object.ResolverInterface | ref }} { return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r} } {{ end }} {{ range $object := .Objects -}} From fd1bd7c9b3b3804ce1b90b786cd3fb9281918882 Mon Sep 17 00:00:00 2001 From: Suraj Chafle <5341407+schafle@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:31:10 -0700 Subject: [PATCH 11/12] adding support for sending extension with gqlgen client (#1633) Co-authored-by: Suraj Chafle --- client/client.go | 1 + client/client_test.go | 27 +++++++++++++++++++++++++++ client/options.go | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/client/client.go b/client/client.go index 9c5f56c8fc..7a2825c952 100644 --- a/client/client.go +++ b/client/client.go @@ -30,6 +30,7 @@ type ( Query string `json:"query"` Variables map[string]interface{} `json:"variables,omitempty"` OperationName string `json:"operationName,omitempty"` + Extensions map[string]interface{} `json:"extensions,omitempty"` HTTP *http.Request `json:"-"` } diff --git a/client/client_test.go b/client/client_test.go index 64a75a30c6..569151cd8a 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -101,3 +101,30 @@ func TestAddCookie(t *testing.T) { client.AddCookie(&http.Cookie{Name: "foo", Value: "value"}), ) } + +func TestAddExtensions(t *testing.T) { + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + require.Equal(t, `{"query":"user(id:1){name}","extensions":{"persistedQuery":{"sha256Hash":"ceec2897e2da519612279e63f24658c3e91194cbb2974744fa9007a7e1e9f9e7","version":1}}}`, string(b)) + err = json.NewEncoder(w).Encode(map[string]interface{}{ + "data": map[string]interface{}{ + "Name": "Bob", + }, + }) + if err != nil { + panic(err) + } + }) + + c := client.New(h) + + var resp struct { + Name string + } + c.MustPost("user(id:1){name}", &resp, + client.Extensions(map[string]interface{}{"persistedQuery": map[string]interface{}{"version": 1, "sha256Hash": "ceec2897e2da519612279e63f24658c3e91194cbb2974744fa9007a7e1e9f9e7"}}), + ) +} diff --git a/client/options.go b/client/options.go index e600f38285..bf4280ac71 100644 --- a/client/options.go +++ b/client/options.go @@ -20,6 +20,13 @@ func Operation(name string) Option { } } +// Extensions sets the extensions to be sent with the outgoing request +func Extensions(extensions map[string]interface{}) Option { + return func(bd *Request) { + bd.Extensions = extensions + } +} + // Path sets the url that this request will be made against, useful if you are mounting your entire router // and need to specify the url to the graphql endpoint. func Path(url string) Option { From 393f755421ae42d207655984dbe6b8b990440384 Mon Sep 17 00:00:00 2001 From: dylanhuang Date: Tue, 12 Oct 2021 07:46:42 +0800 Subject: [PATCH 12/12] add extraTag directive (#1173) --- codegen/config/config.go | 36 +++++++++++++++++++++++++++++------- docs/content/config.md | 5 ++++- plugin/modelgen/models.go | 7 ++++++- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/codegen/config/config.go b/codegen/config/config.go index 29161a622a..b9292bc182 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -231,6 +231,10 @@ func (c *Config) injectTypesFromSchema() error { SkipRuntime: true, } + c.Directives["extraTag"] = DirectiveConfig{ + SkipRuntime: true, + } + for _, schemaType := range c.Schema.Types { if schemaType == c.Schema.Query || schemaType == c.Schema.Mutation || schemaType == c.Schema.Subscription { continue @@ -253,9 +257,15 @@ func (c *Config) injectTypesFromSchema() error { if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject { for _, field := range schemaType.Fields { + typeMapField := TypeMapField{ + ExtraTag: c.Models[schemaType.Name].Fields[field.Name].ExtraTag, + FieldName: c.Models[schemaType.Name].Fields[field.Name].FieldName, + Resolver: c.Models[schemaType.Name].Fields[field.Name].Resolver, + } + directive := false if fd := field.Directives.ForName("goField"); fd != nil { - forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver - fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName + forceResolver := typeMapField.Resolver + fieldName := typeMapField.FieldName if ra := fd.Arguments.ForName("forceResolver"); ra != nil { if fr, err := ra.Value.Value(nil); err == nil { @@ -269,17 +279,28 @@ func (c *Config) injectTypesFromSchema() error { } } + typeMapField.FieldName = fieldName + typeMapField.Resolver = forceResolver + directive = true + } + + if ex := field.Directives.ForName("extraTag"); ex != nil { + args := []string{} + for _, arg := range ex.Arguments { + args = append(args, arg.Name+`:"`+arg.Value.Raw+`"`) + } + typeMapField.ExtraTag = strings.Join(args, " ") + directive = true + } + + if directive { if c.Models[schemaType.Name].Fields == nil { c.Models[schemaType.Name] = TypeMapEntry{ Model: c.Models[schemaType.Name].Model, Fields: map[string]TypeMapField{}, } } - - c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{ - FieldName: fieldName, - Resolver: forceResolver, - } + c.Models[schemaType.Name].Fields[field.Name] = typeMapField } } } @@ -296,6 +317,7 @@ type TypeMapEntry struct { type TypeMapField struct { Resolver bool `yaml:"resolver"` FieldName string `yaml:"fieldName"` + ExtraTag string `yaml:"extraTag"` GeneratedMethod string `yaml:"-"` } diff --git a/docs/content/config.md b/docs/content/config.md index f4e16b3a08..21cfa328ff 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -89,6 +89,9 @@ directive @goModel(model: String, models: [String!]) on OBJECT directive @goField(forceResolver: Boolean, name: String) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION + +directive @extraTag on INPUT_FIELD_DEFINITION + | FIELD_DEFINITION ``` > Here be dragons @@ -101,6 +104,6 @@ Now you can use these directives when defining types in your schema: ```graphql type User @goModel(model: "github.com/my/app/models.User") { id: ID! @goField(name: "todoId") - name: String! @goField(forceResolver: true) + name: String! @goField(forceResolver: true) @extraTag(xorm: "-") } ``` diff --git a/plugin/modelgen/models.go b/plugin/modelgen/models.go index e0ca186632..eca6a3064d 100644 --- a/plugin/modelgen/models.go +++ b/plugin/modelgen/models.go @@ -162,11 +162,16 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { typ = types.NewPointer(typ) } + tag := `json:"` + field.Name + `"` + if extraTag := cfg.Models[schemaType.Name].Fields[field.Name].ExtraTag; extraTag != "" { + tag = tag + " " + extraTag + } + it.Fields = append(it.Fields, &Field{ Name: name, Type: typ, Description: field.Description, - Tag: `json:"` + field.Name + `"`, + Tag: tag, }) }