From 1cd80c4a529688a8a713a9f5755c678e14db0e8c Mon Sep 17 00:00:00 2001 From: Adam Scarr Date: Sat, 28 Apr 2018 13:37:58 +1000 Subject: [PATCH] Default values for input unmarshalers --- codegen/enum_build.go | 3 +- codegen/input_build.go | 4 ++ codegen/object.go | 1 + codegen/templates/data.go | 2 +- codegen/templates/input.gotpl | 10 ++- codegen/templates/templates.go | 9 ++- codegen/templates/templates_test.go | 15 ++++ example/scalars/generated.go | 3 +- example/starwars/generated.go | 3 +- example/todo/generated.go | 3 +- test/generated.go | 108 ++++++++++++++++++++++++++++ test/models/generated.go | 55 ++++++++++++++ test/resolvers_test.go | 35 ++++++++- test/schema.graphql | 16 +++++ 14 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 codegen/templates/templates_test.go create mode 100644 test/models/generated.go diff --git a/codegen/enum_build.go b/codegen/enum_build.go index e16a757520..f2e6f63cb4 100644 --- a/codegen/enum_build.go +++ b/codegen/enum_build.go @@ -4,6 +4,7 @@ import ( "sort" "strings" + "github.com/vektah/gqlgen/codegen/templates" "github.com/vektah/gqlgen/neelance/schema" ) @@ -26,7 +27,7 @@ func (cfg *Config) buildEnums(types NamedTypes) []Enum { NamedType: namedType, Values: values, } - enum.GoType = ucFirst(enum.GQLType) + enum.GoType = templates.ToCamel(enum.GQLType) enums = append(enums, enum) } diff --git a/codegen/input_build.go b/codegen/input_build.go index 72817d77c2..be1ebbd8ea 100644 --- a/codegen/input_build.go +++ b/codegen/input_build.go @@ -54,6 +54,10 @@ func buildInput(types NamedTypes, typ *schema.InputObject) (*Object, error) { Object: obj, } + if field.Default != nil { + newField.Default = field.Default.Value(nil) + } + if !newField.Type.IsInput && !newField.Type.IsScalar { return nil, errors.Errorf("%s cannot be used as a field of %s. only input and scalar types are allowed", newField.GQLType, obj.GQLType) } diff --git a/codegen/object.go b/codegen/object.go index 5a4de009a6..f1aafbaba0 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -28,6 +28,7 @@ type Field struct { Args []FieldArgument // A list of arguments to be passed to this field NoErr bool // If this is bound to a go method, does that method have an error as the second argument Object *Object // A link back to the parent object + Default interface{} // The default value } type FieldArgument struct { diff --git a/codegen/templates/data.go b/codegen/templates/data.go index 72f211f265..ceaa273d57 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -4,7 +4,7 @@ var data = map[string]string{ "args.gotpl": "\t{{- if . }}args := map[string]interface{}{} {{end}}\n\t{{- range $i, $arg := . }}\n\t\tvar arg{{$i}} {{$arg.Signature }}\n\t\tif tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {\n\t\t\tvar err error\n\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\tif err != nil {\n\t\t\t\tec.Error(ctx, err)\n\t\t\t\t{{- if $arg.Object.Stream }}\n\t\t\t\t\treturn nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t{{- end }}\n\t\t\t}\n\t\t} {{ if $arg.Default }} else {\n\t\t\tvar tmp interface{} = {{ $arg.Default | dump }}\n\t\t\tvar err error\n\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\tif err != nil {\n\t\t\t\tec.Error(ctx, err)\n\t\t\t\t{{- if $arg.Object.Stream }}\n\t\t\t\t\treturn nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\t\t{{end }}\n\t\targs[{{$arg.GQLName|quote}}] = arg{{$i}}\n\t{{- end -}}\n", "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(ctx context.Context, field graphql.CollectedField) func() graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{Field: field})\n\t\tresults, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\tif err != nil {\n\t\t\tec.Error(ctx, err)\n\t\t\treturn nil\n\t\t}\n\t\treturn func() graphql.Marshaler {\n\t\t\tres, ok := <-results\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvar out graphql.OrderedMap\n\t\t\tout.Add(field.Alias, func() graphql.Marshaler { {{ $field.WriteJson }} }())\n\t\t\treturn &out\n\t\t}\n\t}\n{{ else }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(ctx context.Context, field graphql.CollectedField, {{if not $object.Root}}obj *{{$object.FullName}}{{end}}) graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\n\t\t{{- if $field.IsConcurrent }}\n\t\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\t\t\tObject: {{$object.GQLType|quote}},\n\t\t\t\tArgs: {{if $field.Args }}args{{else}}nil{{end}},\n\t\t\t\tField: field,\n\t\t\t})\n\t\t\treturn graphql.Defer(func() (ret graphql.Marshaler) {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\tuserErr := ec.Recover(ctx, r)\n\t\t\t\t\t\tec.Error(ctx, userErr)\n\t\t\t\t\t\tret = graphql.Null\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t{{ else }}\n\t\t\trctx := graphql.GetResolverContext(ctx)\n\t\t\trctx.Object = {{$object.GQLType|quote}}\n\t\t\trctx.Args = {{if $field.Args }}args{{else}}nil{{end}}\n\t\t\trctx.Field = field\n\t\t\trctx.PushField(field.Alias)\n\t\t\tdefer rctx.Pop()\n\t\t{{- end }}\n\n\t\t\t{{- if $field.GoVarName }}\n\t\t\t\tres := obj.{{$field.GoVarName}}\n\t\t\t{{- else if $field.GoMethodName }}\n\t\t\t\t{{- if $field.NoErr }}\n\t\t\t\t\tres := {{$field.GoMethodName}}({{ $field.CallArgs }})\n\t\t\t\t{{- else }}\n\t\t\t\t\tres, err := {{$field.GoMethodName}}({{ $field.CallArgs }})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tec.Error(ctx, err)\n\t\t\t\t\t\treturn graphql.Null\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t{{- else }}\n\n\t\t\t\tresTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {\n\t\t\t\t\treturn ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tec.Error(ctx, err)\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\n\t\t\t\tif resTmp == nil {\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\n\t\t\t\tres := resTmp.({{$field.Signature}})\n\t\t\t{{- end }}\n\t\t\t{{ $field.WriteJson }}\n\t\t{{- if $field.IsConcurrent }}\n\t\t\t})\n\t\t{{- end }}\n\t}\n{{ end }}\n", "generated.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\nfunc MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema {\n\treturn &executableSchema{resolvers: resolvers}\n}\n\ntype Resolvers interface {\n{{- range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{ $field.ResolverDeclaration }}\n\t{{ end }}\n{{- end }}\n}\n\ntype executableSchema struct {\n\tresolvers Resolvers\n}\n\nfunc (e *executableSchema) Schema() *schema.Schema {\n\treturn parsedSchema\n}\n\nfunc (e *executableSchema) Query(ctx context.Context, op *query.Operation) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.QueryRoot.GQLType}}(ctx, op.Selections)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"queries are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, op *query.Operation) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.MutationRoot.GQLType}}(ctx, op.Selections)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"mutations are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, op *query.Operation) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e.resolvers}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(ctx, op.Selections)\n\t\tif ec.Errors != nil {\n\t\t\treturn graphql.OneShot(&graphql.Response{Data: []byte(\"null\"), Errors: ec.Errors})\n\t\t}\n\n\t\tvar buf bytes.Buffer\n\t\treturn func() *graphql.Response {\n\t\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\t\tbuf.Reset()\n\t\t\t\tdata := next()\n\n\t\t\t\tif data == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tdata.MarshalGQL(&buf)\n\t\t\t\treturn buf.Bytes()\n\t\t\t})\n\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf,\n\t\t\t\tErrors: ec.Errors,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(graphql.ErrorResponse(ctx, \"subscriptions are not supported\"))\n\t{{- end }}\n}\n\ntype executionContext struct {\n\t*graphql.RequestContext\n\n\tresolvers Resolvers\n}\n\n{{- range $object := .Objects }}\n\t{{ template \"object.gotpl\" $object }}\n\n\t{{- range $field := $object.Fields }}\n\t\t{{ template \"field.gotpl\" $field }}\n\t{{ end }}\n{{- end}}\n\n{{- range $interface := .Interfaces }}\n\t{{ template \"interface.gotpl\" $interface }}\n{{- end }}\n\n{{- range $input := .Inputs }}\n\t{{ template \"input.gotpl\" $input }}\n{{- end }}\n\nfunc (ec *executionContext) introspectSchema() *introspection.Schema {\n\treturn introspection.WrapSchema(parsedSchema)\n}\n\nfunc (ec *executionContext) introspectType(name string) *introspection.Type {\n\tt := parsedSchema.Resolve(name)\n\tif t == nil {\n\t\treturn nil\n\t}\n\treturn introspection.WrapType(t)\n}\n\nvar parsedSchema = schema.MustParse({{.SchemaRaw|rawQuote}})\n", - "input.gotpl": "\t{{- if .IsMarshaled }}\n\tfunc Unmarshal{{ .GQLType }}(v interface{}) ({{.FullName}}, error) {\n\t\tvar it {{.FullName}}\n\n\t\tfor k, v := range v.(map[string]interface{}) {\n\t\t\tswitch k {\n\t\t\t{{- range $field := .Fields }}\n\t\t\tcase {{$field.GQLName|quote}}:\n\t\t\t\tvar err error\n\t\t\t\t{{ $field.Unmarshal (print \"it.\" $field.GoVarName) \"v\" }}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn it, err\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\n\t\treturn it, nil\n\t}\n\t{{- end }}\n", + "input.gotpl": "\t{{- if .IsMarshaled }}\n\tfunc Unmarshal{{ .GQLType }}(v interface{}) ({{.FullName}}, error) {\n\t\tvar it {{.FullName}}\n\t\tvar asMap = v.(map[string]interface{})\n\t\t{{ range $field := .Fields}}\n\t\t\t{{- if $field.Default}}\n\t\t\t\tif _, present := asMap[{{$field.GQLName|quote}}] ; !present {\n\t\t\t\t\tasMap[{{$field.GQLName|quote}}] = {{ $field.Default | dump }}\n\t\t\t\t}\n\t\t\t{{- end}}\n\t\t{{- end }}\n\n\t\tfor k, v := range asMap {\n\t\t\tswitch k {\n\t\t\t{{- range $field := .Fields }}\n\t\t\tcase {{$field.GQLName|quote}}:\n\t\t\t\tvar err error\n\t\t\t\t{{ $field.Unmarshal (print \"it.\" $field.GoVarName) \"v\" }}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn it, err\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\n\t\treturn it, nil\n\t}\n\t{{- end }}\n", "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.GQLType}}(ctx context.Context, sel []query.Selection, obj *{{$interface.FullName}}) graphql.Marshaler {\n\tswitch obj := (*obj).(type) {\n\tcase nil:\n\t\treturn graphql.Null\n\t{{- range $implementor := $interface.Implementors }}\n\t\t{{- if $implementor.ValueReceiver }}\n\t\t\tcase {{$implementor.FullName}}:\n\t\t\t\treturn ec._{{$implementor.GQLType}}(ctx, sel, &obj)\n\t\t{{- end}}\n\t\tcase *{{$implementor.FullName}}:\n\t\t\treturn ec._{{$implementor.GQLType}}(ctx, sel, obj)\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", "models.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\n{{ range $model := .Models }}\n\t{{- if .IsInterface }}\n\t\ttype {{.GoType}} interface {}\n\t{{- else }}\n\t\ttype {{.GoType}} struct {\n\t\t\t{{- range $field := .Fields }}\n\t\t\t\t{{- if $field.GoVarName }}\n\t\t\t\t\t{{ $field.GoVarName }} {{$field.Signature}}\n\t\t\t\t{{- else }}\n\t\t\t\t\t{{ $field.GoFKName }} {{$field.GoFKType}}\n\t\t\t\t{{- end }}\n\t\t\t{{- end }}\n\t\t}\n\t{{- end }}\n{{- end}}\n\n{{ range $enum := .Enums }}\n\ttype {{.GoType}} string\n\tconst (\n\t{{ range $value := .Values }}\n\t\t{{$enum.GoType}}{{ .Name|toCamel }} {{$enum.GoType}} = {{.Name|quote}} {{with .Description}} // {{.}} {{end}}\n\t{{- end }}\n\t)\n\n\tfunc (e {{.GoType}}) IsValid() bool {\n\t\tswitch e {\n\t\tcase {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.GoType }}{{ $element.Name|toCamel }}{{end}}:\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tfunc (e {{.GoType}}) String() string {\n\t\treturn string(e)\n\t}\n\n\tfunc (e *{{.GoType}}) UnmarshalGQL(v interface{}) error {\n\t\tstr, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"enums must be strings\")\n\t\t}\n\n\t\t*e = {{.GoType}}(str)\n\t\tif !e.IsValid() {\n\t\t\treturn fmt.Errorf(\"%s is not a valid {{.GQLType}}\", str)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfunc (e {{.GoType}}) MarshalGQL(w io.Writer) {\n\t\tfmt.Fprint(w, strconv.Quote(e.String()))\n\t}\n\n{{- end }}\n", "object.gotpl": "{{ $object := . }}\n\nvar {{ $object.GQLType|lcFirst}}Implementors = {{$object.Implementors}}\n\n// nolint: gocyclo, errcheck, gas, goconst\n{{- if .Stream }}\nfunc (ec *executionContext) _{{$object.GQLType}}(ctx context.Context, sel []query.Selection) func() graphql.Marshaler {\n\tfields := graphql.CollectFields(ec.Doc, sel, {{$object.GQLType|lcFirst}}Implementors, ec.Variables)\n\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\tObject: {{$object.GQLType|quote}},\n\t})\n\tif len(fields) != 1 {\n\t\tec.Errorf(ctx, \"must subscribe to exactly one stream\")\n\t\treturn nil\n\t}\n\n\tswitch fields[0].Name {\n\t{{- range $field := $object.Fields }}\n\tcase \"{{$field.GQLName}}\":\n\t\treturn ec._{{$object.GQLType}}_{{$field.GQLName}}(ctx, fields[0])\n\t{{- end }}\n\tdefault:\n\t\tpanic(\"unknown field \" + strconv.Quote(fields[0].Name))\n\t}\n}\n{{- else }}\nfunc (ec *executionContext) _{{$object.GQLType}}(ctx context.Context, sel []query.Selection{{if not $object.Root}}, obj *{{$object.FullName}} {{end}}) graphql.Marshaler {\n\tfields := graphql.CollectFields(ec.Doc, sel, {{$object.GQLType|lcFirst}}Implementors, ec.Variables)\n\t{{if $object.Root}}\n\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\t\tObject: {{$object.GQLType|quote}},\n\t\t})\n\t{{end}}\n\tout := graphql.NewOrderedMap(len(fields))\n\tfor i, field := range fields {\n\t\tout.Keys[i] = field.Alias\n\n\t\tswitch field.Name {\n\t\tcase \"__typename\":\n\t\t\tout.Values[i] = graphql.MarshalString({{$object.GQLType|quote}})\n\t\t{{- range $field := $object.Fields }}\n\t\tcase \"{{$field.GQLName}}\":\n\t\t\tout.Values[i] = ec._{{$object.GQLType}}_{{$field.GQLName}}(ctx, field{{if not $object.Root}}, obj{{end}})\n\t\t{{- end }}\n\t\tdefault:\n\t\t\tpanic(\"unknown field \" + strconv.Quote(field.Name))\n\t\t}\n\t}\n\n\treturn out\n}\n{{- end }}\n", diff --git a/codegen/templates/input.gotpl b/codegen/templates/input.gotpl index 2a32b5325f..6073daf4ee 100644 --- a/codegen/templates/input.gotpl +++ b/codegen/templates/input.gotpl @@ -1,8 +1,16 @@ {{- if .IsMarshaled }} func Unmarshal{{ .GQLType }}(v interface{}) ({{.FullName}}, error) { var it {{.FullName}} + var asMap = v.(map[string]interface{}) + {{ range $field := .Fields}} + {{- if $field.Default}} + if _, present := asMap[{{$field.GQLName|quote}}] ; !present { + asMap[{{$field.GQLName|quote}}] = {{ $field.Default | dump }} + } + {{- end}} + {{- end }} - for k, v := range v.(map[string]interface{}) { + for k, v := range asMap { switch k { {{- range $field := .Fields }} case {{$field.GQLName|quote}}: diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index e79e8ce001..d6ae6a95b8 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -17,7 +17,7 @@ func Run(name string, tpldata interface{}) (*bytes.Buffer, error) { "lcFirst": lcFirst, "quote": strconv.Quote, "rawQuote": rawQuote, - "toCamel": toCamel, + "toCamel": ToCamel, "dump": dump, }) @@ -60,15 +60,19 @@ func isDelimiter(c rune) bool { return c == '-' || c == '_' || unicode.IsSpace(c) } -func toCamel(s string) string { +func ToCamel(s string) string { buffer := make([]rune, 0, len(s)) upper := true + lastWasUpper := false for _, c := range s { if isDelimiter(c) { upper = true continue } + if !lastWasUpper && unicode.IsUpper(c) { + upper = true + } if upper { buffer = append(buffer, unicode.ToUpper(c)) @@ -76,6 +80,7 @@ func toCamel(s string) string { buffer = append(buffer, unicode.ToLower(c)) } upper = false + lastWasUpper = unicode.IsUpper(c) } return string(buffer) diff --git a/codegen/templates/templates_test.go b/codegen/templates/templates_test.go new file mode 100644 index 0000000000..dce5fffd6b --- /dev/null +++ b/codegen/templates/templates_test.go @@ -0,0 +1,15 @@ +package templates + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestToUpper(t *testing.T) { + require.Equal(t, "ToCamel", ToCamel("TO_CAMEL")) + require.Equal(t, "ToCamel", ToCamel("to_camel")) + require.Equal(t, "ToCamel", ToCamel("toCamel")) + require.Equal(t, "ToCamel", ToCamel("ToCamel")) + require.Equal(t, "ToCamel", ToCamel("to-camel")) +} diff --git a/example/scalars/generated.go b/example/scalars/generated.go index c40a132422..f730d007e1 100644 --- a/example/scalars/generated.go +++ b/example/scalars/generated.go @@ -1181,8 +1181,9 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co func UnmarshalSearchArgs(v interface{}) (SearchArgs, error) { var it SearchArgs + var asMap = v.(map[string]interface{}) - for k, v := range v.(map[string]interface{}) { + for k, v := range asMap { switch k { case "location": var err error diff --git a/example/starwars/generated.go b/example/starwars/generated.go index 000b94b48e..acd581f32c 100644 --- a/example/starwars/generated.go +++ b/example/starwars/generated.go @@ -2206,8 +2206,9 @@ func (ec *executionContext) _SearchResult(ctx context.Context, sel []query.Selec func UnmarshalReviewInput(v interface{}) (Review, error) { var it Review + var asMap = v.(map[string]interface{}) - for k, v := range v.(map[string]interface{}) { + for k, v := range asMap { switch k { case "stars": var err error diff --git a/example/todo/generated.go b/example/todo/generated.go index 4506eabca9..c7b50e73bc 100644 --- a/example/todo/generated.go +++ b/example/todo/generated.go @@ -1161,8 +1161,9 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co func UnmarshalTodoInput(v interface{}) (TodoInput, error) { var it TodoInput + var asMap = v.(map[string]interface{}) - for k, v := range v.(map[string]interface{}) { + for k, v := range asMap { switch k { case "text": var err error diff --git a/test/generated.go b/test/generated.go index 182d41e4a7..0635070730 100644 --- a/test/generated.go +++ b/test/generated.go @@ -11,6 +11,7 @@ import ( introspection "github.com/vektah/gqlgen/neelance/introspection" query "github.com/vektah/gqlgen/neelance/query" schema "github.com/vektah/gqlgen/neelance/schema" + models "github.com/vektah/gqlgen/test/models" ) func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { @@ -21,6 +22,7 @@ type Resolvers interface { Element_child(ctx context.Context, obj *Element) (Element, error) Element_error(ctx context.Context, obj *Element) (bool, error) Query_path(ctx context.Context) ([]Element, error) + Query_date(ctx context.Context, filter models.DateFilter) (bool, error) } type executableSchema struct { @@ -165,6 +167,8 @@ func (ec *executionContext) _Query(ctx context.Context, sel []query.Selection) g out.Values[i] = graphql.MarshalString("Query") case "path": out.Values[i] = ec._Query_path(ctx, field) + case "date": + out.Values[i] = ec._Query_date(ctx, field) case "__schema": out.Values[i] = ec._Query___schema(ctx, field) case "__type": @@ -216,6 +220,47 @@ func (ec *executionContext) _Query_path(ctx context.Context, field graphql.Colle }) } +func (ec *executionContext) _Query_date(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { + args := map[string]interface{}{} + var arg0 models.DateFilter + if tmp, ok := field.Args["filter"]; ok { + var err error + arg0, err = UnmarshalDateFilter(tmp) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + } + args["filter"] = arg0 + ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ + Object: "Query", + Args: args, + Field: field, + }) + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.Recover(ctx, r) + ec.Error(ctx, userErr) + ret = graphql.Null + } + }() + + resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) { + return ec.resolvers.Query_date(ctx, args["filter"].(models.DateFilter)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(bool) + return graphql.MarshalBoolean(res) + }) +} + func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { rctx := graphql.GetResolverContext(ctx) rctx.Object = "Query" @@ -981,6 +1026,53 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co return ec.___Type(ctx, field.Selections, res) } +func UnmarshalDateFilter(v interface{}) (models.DateFilter, error) { + var it models.DateFilter + var asMap = v.(map[string]interface{}) + + if _, present := asMap["timezone"]; !present { + asMap["timezone"] = "UTC" + } + if _, present := asMap["op"]; !present { + asMap["op"] = "EQ" + } + + for k, v := range asMap { + switch k { + case "value": + var err error + it.Value, err = graphql.UnmarshalString(v) + if err != nil { + return it, err + } + case "timezone": + var err error + var ptr1 string + if v != nil { + ptr1, err = graphql.UnmarshalString(v) + it.Timezone = &ptr1 + } + + if err != nil { + return it, err + } + case "op": + var err error + var ptr1 models.DateFilterOp + if v != nil { + err = (&ptr1).UnmarshalGQL(v) + it.Op = &ptr1 + } + + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) introspectSchema() *introspection.Schema { return introspection.WrapSchema(parsedSchema) } @@ -998,7 +1090,23 @@ var parsedSchema = schema.MustParse(`type Element { error: Boolean! } +enum DATE_FILTER_OP { + EQ + NEQ + GT + GTE + LT + LTE +} + +input DateFilter { + value: String! + timezone: String = "UTC" + op: DATE_FILTER_OP = EQ +} + type Query { path: [Element] + date(filter: DateFilter!): Boolean! } `) diff --git a/test/models/generated.go b/test/models/generated.go new file mode 100644 index 0000000000..3551acf657 --- /dev/null +++ b/test/models/generated.go @@ -0,0 +1,55 @@ +// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT + +package models + +import ( + fmt "fmt" + io "io" + strconv "strconv" +) + +type DateFilter struct { + Value string + Timezone *string + Op *DateFilterOp +} + +type DateFilterOp string + +const ( + DateFilterOpEq DateFilterOp = "EQ" + DateFilterOpNeq DateFilterOp = "NEQ" + DateFilterOpGt DateFilterOp = "GT" + DateFilterOpGte DateFilterOp = "GTE" + DateFilterOpLt DateFilterOp = "LT" + DateFilterOpLte DateFilterOp = "LTE" +) + +func (e DateFilterOp) IsValid() bool { + switch e { + case DateFilterOpEq, DateFilterOpNeq, DateFilterOpGt, DateFilterOpGte, DateFilterOpLt, DateFilterOpLte: + return true + } + return false +} + +func (e DateFilterOp) String() string { + return string(e) +} + +func (e *DateFilterOp) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = DateFilterOp(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid DATE_FILTER_OP", str) + } + return nil +} + +func (e DateFilterOp) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/test/resolvers_test.go b/test/resolvers_test.go index d0c64f2cb3..05e355a238 100644 --- a/test/resolvers_test.go +++ b/test/resolvers_test.go @@ -11,9 +11,11 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vektah/gqlgen/client" "github.com/vektah/gqlgen/graphql" "github.com/vektah/gqlgen/handler" + models "github.com/vektah/gqlgen/test/models" ) func TestCustomErrorPresenter(t *testing.T) { @@ -45,7 +47,7 @@ func TestCustomErrorPresenter(t *testing.T) { } func TestErrorPath(t *testing.T) { - srv := httptest.NewServer(handler.GraphQL(MakeExecutableSchema(&testResolvers{fmt.Errorf("boom")}))) + srv := httptest.NewServer(handler.GraphQL(MakeExecutableSchema(&testResolvers{err: fmt.Errorf("boom")}))) c := client.New(srv.URL) var resp struct{} @@ -54,8 +56,37 @@ func TestErrorPath(t *testing.T) { assert.EqualError(t, err, `[{"message":"boom","path":["path",0,"cc","error"]},{"message":"boom","path":["path",1,"cc","error"]},{"message":"boom","path":["path",2,"cc","error"]},{"message":"boom","path":["path",3,"cc","error"]}]`) } +func TestInputDefaults(t *testing.T) { + called := false + srv := httptest.NewServer(handler.GraphQL(MakeExecutableSchema(&testResolvers{ + queryDate: func(ctx context.Context, filter models.DateFilter) (bool, error) { + assert.Equal(t, "asdf", filter.Value) + assert.Equal(t, "UTC", *filter.Timezone) + assert.Equal(t, models.DateFilterOpEq, *filter.Op) + called = true + + return false, nil + }, + }))) + c := client.New(srv.URL) + + var resp struct { + Date bool + } + + err := c.Post(`{ date(filter:{value: "asdf"}) }`, &resp) + + require.NoError(t, err) + require.True(t, called) +} + type testResolvers struct { - err error + err error + queryDate func(ctx context.Context, filter models.DateFilter) (bool, error) +} + +func (r *testResolvers) Query_date(ctx context.Context, filter models.DateFilter) (bool, error) { + return r.queryDate(ctx, filter) } func (r *testResolvers) Query_path(ctx context.Context) ([]Element, error) { diff --git a/test/schema.graphql b/test/schema.graphql index 878f8eac8f..f506271313 100644 --- a/test/schema.graphql +++ b/test/schema.graphql @@ -3,6 +3,22 @@ type Element { error: Boolean! } +enum DATE_FILTER_OP { + EQ + NEQ + GT + GTE + LT + LTE +} + +input DateFilter { + value: String! + timezone: String = "UTC" + op: DATE_FILTER_OP = EQ +} + type Query { path: [Element] + date(filter: DateFilter!): Boolean! }