diff --git a/codegen/config/config.go b/codegen/config/config.go index 7b175be4d6c..7a7232b58d9 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -342,8 +342,6 @@ func (c *Config) InjectBuiltins(s *ast.Schema) { "Float": {Model: StringList{"github.com/99designs/gqlgen/graphql.Float"}}, "String": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}}, "Boolean": {Model: StringList{"github.com/99designs/gqlgen/graphql.Boolean"}}, - "Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}}, - "Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}}, "Int": {Model: StringList{ "github.com/99designs/gqlgen/graphql.Int", "github.com/99designs/gqlgen/graphql.Int32", @@ -362,6 +360,18 @@ func (c *Config) InjectBuiltins(s *ast.Schema) { c.Models[typeName] = entry } } + + // These are additional types that are injected if defined in the schema as scalars. + extraBuiltins := TypeMap{ + "Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}}, + "Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}}, + } + + for typeName, entry := range extraBuiltins { + if t, ok := s.Types[typeName]; ok && t.Kind == ast.Scalar { + c.Models[typeName] = entry + } + } } func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) { diff --git a/codegen/testserver/builtinscalar.graphql b/codegen/testserver/builtinscalar.graphql new file mode 100644 index 00000000000..deb8a9f6242 --- /dev/null +++ b/codegen/testserver/builtinscalar.graphql @@ -0,0 +1,8 @@ + +""" +Since gqlgen defines default implementation for a Map scalar, this tests that the builtin is _not_ +added to the TypeMap +""" +type Map { + id: ID! +} diff --git a/codegen/testserver/generated.go b/codegen/testserver/generated.go index caa65855d79..f7b527d82fa 100644 --- a/codegen/testserver/generated.go +++ b/codegen/testserver/generated.go @@ -117,6 +117,10 @@ type ComplexityRoot struct { ID func(childComplexity int) int } + Map struct { + ID func(childComplexity int) int + } + MapStringInterfaceType struct { A func(childComplexity int) int B func(childComplexity int) int @@ -445,6 +449,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.It.ID(childComplexity), true + case "Map.ID": + if e.complexity.Map.ID == nil { + break + } + + return e.complexity.Map.ID(childComplexity), true + case "MapStringInterfaceType.A": if e.complexity.MapStringInterfaceType.A == nil { break @@ -1088,6 +1099,15 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var parsedSchema = gqlparser.MustLoadSchema( + &ast.Source{Name: "builtinscalar.graphql", Input: ` +""" +Since gqlgen defines default implementation for a Map scalar, this tests that the builtin is _not_ +added to the TypeMap +""" +type Map { + id: ID! +} +`}, &ast.Source{Name: "complexity.graphql", Input: `extend type Query { overlapping: OverlappingFields } @@ -2543,6 +2563,33 @@ func (ec *executionContext) _It_id(ctx context.Context, field graphql.CollectedF return ec.marshalNID2string(ctx, field.Selections, res) } +func (ec *executionContext) _Map_id(ctx context.Context, field graphql.CollectedField, obj *Map) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "Map", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNID2string(ctx, field.Selections, res) +} + func (ec *executionContext) _MapStringInterfaceType_a(ctx context.Context, field graphql.CollectedField, obj map[string]interface{}) graphql.Marshaler { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { ec.Tracer.EndFieldExecution(ctx) }() @@ -5927,6 +5974,33 @@ func (ec *executionContext) _It(ctx context.Context, sel ast.SelectionSet, obj * return out } +var mapImplementors = []string{"Map"} + +func (ec *executionContext) _Map(ctx context.Context, sel ast.SelectionSet, obj *Map) graphql.Marshaler { + fields := graphql.CollectFields(ctx, sel, mapImplementors) + + out := graphql.NewFieldSet(fields) + invalid := false + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Map") + case "id": + out.Values[i] = ec._Map_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalid = true + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalid { + return graphql.Null + } + return out +} + var mapStringInterfaceTypeImplementors = []string{"MapStringInterfaceType"} func (ec *executionContext) _MapStringInterfaceType(ctx context.Context, sel ast.SelectionSet, obj map[string]interface{}) graphql.Marshaler { diff --git a/codegen/testserver/models-gen.go b/codegen/testserver/models-gen.go index 25f8522713b..c1331c1dba3 100644 --- a/codegen/testserver/models-gen.go +++ b/codegen/testserver/models-gen.go @@ -56,6 +56,12 @@ type InputDirectives struct { ThirdParty *ThirdParty `json:"thirdParty"` } +// Since gqlgen defines default implementation for a Map scalar, this tests that the builtin is _not_ +// added to the TypeMap +type Map struct { + ID string `json:"id"` +} + type OuterInput struct { Inner InnerInput `json:"inner"` } diff --git a/docs/content/reference/scalars.md b/docs/content/reference/scalars.md index 961cd748c07..ec9a9fcf0b6 100644 --- a/docs/content/reference/scalars.md +++ b/docs/content/reference/scalars.md @@ -1,14 +1,32 @@ --- -linkTitle: Custom Scalars -title: Using custom graphql types in golang -description: Defining custom GraphQL scalar types using gqlgen +linkTitle: Scalars +title: Mapping GraphQL scalar types to Go types +description: Mapping GraphQL scalar types to Go types menu: { main: { parent: 'reference' } } --- -There are two different ways to implement scalars in gqlgen, depending on your need. +## Built-in helpers +gqlgen ships with two built-in helpers for common custom scalar use-cases, `Time` and `Map`. Adding either of these to a schema will automatically add the marshalling behaviour to Go types. + +### Time + +```graphql +scalar Time +``` + +Maps a `Time` GraphQL scalar to a Go `time.Time` struct. + +### Map + +```graphql +scalar Map +``` + +Maps an arbitrary GraphQL value to a `map[string]{interface}` Go type. + +## Custom scalars with user defined types -## With user defined types For user defined types you can implement the graphql.Marshal and graphql.Unmarshal interfaces and they will be called. ```go @@ -55,7 +73,7 @@ models: ``` -## Custom scalars for types you don't control +## Custom scalars with third party types Sometimes you cant add methods to a type because its in another repo, part of the standard library (eg string or time.Time). To do this we can build an external marshaler: