diff --git a/codegen/templates/data.go b/codegen/templates/data.go index a39e8130425..21476ff6838 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -2,8 +2,8 @@ package templates var data = map[string]string{ "args.gotpl": "\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(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(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{{- end -}}\n", - "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(field graphql.CollectedField) func() graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\t\tresults, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\tif err != nil {\n\t\t\tec.Error(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}}(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\treturn graphql.Defer(func() graphql.Marshaler {\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(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\t\t\t\tres, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\t\t\tif err != nil {\n\t\t\t\t\tec.Error(err)\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\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}\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, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tdata := ec._{{.QueryRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"queries are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tdata := ec._{{.MutationRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"mutations are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(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.Reset()\n\t\t\tdata := next()\n\t\t\tif data == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdata.MarshalGQL(&buf)\n\n\t\t\terrs := ec.Errors\n\t\t\tec.Errors = nil\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf.Bytes(),\n\t\t\t\tErrors: errs,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{ {Message: \"subscriptions are not supported\"} }})\n\t{{- end }}\n}\n\ntype executionContext struct {\n\terrors.Builder\n\tresolvers Resolvers\n\tvariables map[string]interface{}\n\tdoc *query.Document\n\tctx context.Context\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\nvar parsedSchema = schema.MustParse({{.SchemaRaw|quote}})\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", + "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(field graphql.CollectedField) func() graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\t\tresults, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\tif err != nil {\n\t\t\tec.Error(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}}(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\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(r)\n\t\t\t\t\t\tec.Error(userErr)\n\t\t\t\t\t\tret = graphql.Null\n\t\t\t\t\t}\n\t\t\t\t}()\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(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\t\t\t\tres, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\t\t\tif err != nil {\n\t\t\t\t\tec.Error(err)\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\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}\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, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover}\n\n\t\tdata := ec._{{.QueryRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"queries are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover}\n\n\t\tdata := ec._{{.MutationRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"mutations are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(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.Reset()\n\t\t\tdata := next()\n\t\t\tif data == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdata.MarshalGQL(&buf)\n\n\t\t\terrs := ec.Errors\n\t\t\tec.Errors = nil\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf.Bytes(),\n\t\t\t\tErrors: errs,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{ {Message: \"subscriptions are not supported\"} }})\n\t{{- end }}\n}\n\ntype executionContext struct {\n\terrors.Builder\n\tresolvers Resolvers\n\tvariables map[string]interface{}\n\tdoc *query.Document\n\tctx context.Context\n\trecover graphql.RecoverFunc\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\nvar parsedSchema = schema.MustParse({{.SchemaRaw|quote}})\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", "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", "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.GQLType}}(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\tcase {{$implementor.FullName}}:\n\t\treturn ec._{{$implementor.GQLType}}(sel, &obj)\n\n\tcase *{{$implementor.FullName}}:\n\t\treturn ec._{{$implementor.GQLType}}(sel, obj)\n\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\ttype {{.GoType}} struct {\n\t\t{{- range $field := .Fields }}\n\t\t\t{{- if $field.GoVarName }}\n\t\t\t\t{{ $field.GoVarName }} {{$field.Signature}}\n\t\t\t{{- else }}\n\t\t\t\t{{ $field.GoFKName }} {{$field.GoFKType}}\n\t\t\t{{- end }}\n\t\t{{- end }}\n\t}\n{{- end}}\n", diff --git a/codegen/templates/field.gotpl b/codegen/templates/field.gotpl index 23747c24a89..f9a5e7bd3da 100644 --- a/codegen/templates/field.gotpl +++ b/codegen/templates/field.gotpl @@ -24,7 +24,14 @@ {{- template "args.gotpl" $field.Args }} {{- if $field.IsConcurrent }} - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() {{- end }} {{- if $field.GoVarName }} diff --git a/codegen/templates/generated.gotpl b/codegen/templates/generated.gotpl index fd6d123e115..cb9854c1c1d 100644 --- a/codegen/templates/generated.gotpl +++ b/codegen/templates/generated.gotpl @@ -28,9 +28,9 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { {{- if .QueryRoot }} - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._{{.QueryRoot.GQLType}}(op.Selections) var buf bytes.Buffer @@ -45,9 +45,9 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia {{- end }} } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { {{- if .MutationRoot }} - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._{{.MutationRoot.GQLType}}(op.Selections) var buf bytes.Buffer @@ -62,9 +62,9 @@ func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, va {{- end }} } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { {{- if .SubscriptionRoot }} - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} next := ec._{{.SubscriptionRoot.GQLType}}(op.Selections) if ec.Errors != nil { @@ -98,6 +98,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } {{- range $object := .Objects }} diff --git a/example/chat/generated.go b/example/chat/generated.go index 7467abdb627..7f756e9562d 100644 --- a/example/chat/generated.go +++ b/example/chat/generated.go @@ -33,8 +33,8 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Query(op.Selections) var buf bytes.Buffer @@ -46,8 +46,8 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia } } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Mutation(op.Selections) var buf bytes.Buffer @@ -59,8 +59,8 @@ func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, va } } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} next := ec._Subscription(op.Selections) if ec.Errors != nil { @@ -91,6 +91,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } var chatroomImplementors = []string{"Chatroom"} @@ -277,7 +278,14 @@ func (ec *executionContext) _Query_room(field graphql.CollectedField) graphql.Ma return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_room(ec.ctx, arg0) if err != nil { ec.Error(err) diff --git a/example/dataloader/generated.go b/example/dataloader/generated.go index 188d4ef43e8..45c6262ba4c 100644 --- a/example/dataloader/generated.go +++ b/example/dataloader/generated.go @@ -35,8 +35,8 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Query(op.Selections) var buf bytes.Buffer @@ -48,11 +48,11 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia } } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { return &graphql.Response{Errors: []*errors.QueryError{{Message: "mutations are not supported"}}} } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { return graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{{Message: "subscriptions are not supported"}}}) } @@ -62,6 +62,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } var addressImplementors = []string{"Address"} @@ -144,7 +145,14 @@ func (ec *executionContext) _Customer_name(field graphql.CollectedField, obj *Cu } func (ec *executionContext) _Customer_address(field graphql.CollectedField, obj *Customer) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Customer_address(ec.ctx, obj) if err != nil { ec.Error(err) @@ -158,7 +166,14 @@ func (ec *executionContext) _Customer_address(field graphql.CollectedField, obj } func (ec *executionContext) _Customer_orders(field graphql.CollectedField, obj *Customer) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Customer_orders(ec.ctx, obj) if err != nil { ec.Error(err) @@ -243,7 +258,14 @@ func (ec *executionContext) _Order_amount(field graphql.CollectedField, obj *Ord } func (ec *executionContext) _Order_items(field graphql.CollectedField, obj *Order) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Order_items(ec.ctx, obj) if err != nil { ec.Error(err) @@ -286,7 +308,14 @@ func (ec *executionContext) _Query(sel []query.Selection) graphql.Marshaler { } func (ec *executionContext) _Query_customers(field graphql.CollectedField) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_customers(ec.ctx) if err != nil { ec.Error(err) @@ -319,7 +348,14 @@ func (ec *executionContext) _Query_torture(field graphql.CollectedField) graphql return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_torture(ec.ctx, arg0) if err != nil { ec.Error(err) diff --git a/example/scalars/generated.go b/example/scalars/generated.go index 8e16de6bab5..f0052d0c252 100644 --- a/example/scalars/generated.go +++ b/example/scalars/generated.go @@ -32,8 +32,8 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Query(op.Selections) var buf bytes.Buffer @@ -45,11 +45,11 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia } } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { return &graphql.Response{Errors: []*errors.QueryError{{Message: "mutations are not supported"}}} } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { return graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{{Message: "subscriptions are not supported"}}}) } @@ -59,6 +59,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } var queryImplementors = []string{"Query"} @@ -100,7 +101,14 @@ func (ec *executionContext) _Query_user(field graphql.CollectedField) graphql.Ma return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_user(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -134,7 +142,14 @@ func (ec *executionContext) _Query_search(field graphql.CollectedField) graphql. } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_search(ec.ctx, arg0) if err != nil { ec.Error(err) diff --git a/example/starwars/generated.go b/example/starwars/generated.go index 94a352a4683..ca510ec6bcc 100644 --- a/example/starwars/generated.go +++ b/example/starwars/generated.go @@ -50,8 +50,8 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Query(op.Selections) var buf bytes.Buffer @@ -63,8 +63,8 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia } } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Mutation(op.Selections) var buf bytes.Buffer @@ -76,7 +76,7 @@ func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, va } } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { return graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{{Message: "subscriptions are not supported"}}}) } @@ -86,6 +86,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } var droidImplementors = []string{"Droid", "Character"} @@ -131,7 +132,14 @@ func (ec *executionContext) _Droid_name(field graphql.CollectedField, obj *Droid } func (ec *executionContext) _Droid_friends(field graphql.CollectedField, obj *Droid) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Droid_friends(ec.ctx, obj) if err != nil { ec.Error(err) @@ -170,7 +178,14 @@ func (ec *executionContext) _Droid_friendsConnection(field graphql.CollectedFiel return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Droid_friendsConnection(ec.ctx, obj, arg0, arg1) if err != nil { ec.Error(err) @@ -228,7 +243,14 @@ func (ec *executionContext) _FriendsConnection_totalCount(field graphql.Collecte } func (ec *executionContext) _FriendsConnection_edges(field graphql.CollectedField, obj *FriendsConnection) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.FriendsConnection_edges(ec.ctx, obj) if err != nil { ec.Error(err) @@ -243,7 +265,14 @@ func (ec *executionContext) _FriendsConnection_edges(field graphql.CollectedFiel } func (ec *executionContext) _FriendsConnection_friends(field graphql.CollectedField, obj *FriendsConnection) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.FriendsConnection_friends(ec.ctx, obj) if err != nil { ec.Error(err) @@ -373,7 +402,14 @@ func (ec *executionContext) _Human_mass(field graphql.CollectedField, obj *Human } func (ec *executionContext) _Human_friends(field graphql.CollectedField, obj *Human) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Human_friends(ec.ctx, obj) if err != nil { ec.Error(err) @@ -412,7 +448,14 @@ func (ec *executionContext) _Human_friendsConnection(field graphql.CollectedFiel return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Human_friendsConnection(ec.ctx, obj, arg0, arg1) if err != nil { ec.Error(err) @@ -432,7 +475,14 @@ func (ec *executionContext) _Human_appearsIn(field graphql.CollectedField, obj * } func (ec *executionContext) _Human_starships(field graphql.CollectedField, obj *Human) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Human_starships(ec.ctx, obj) if err != nil { ec.Error(err) @@ -600,7 +650,14 @@ func (ec *executionContext) _Query_hero(field graphql.CollectedField) graphql.Ma } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_hero(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -633,7 +690,14 @@ func (ec *executionContext) _Query_reviews(field graphql.CollectedField) graphql return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_reviews(ec.ctx, arg0, arg1) if err != nil { ec.Error(err) @@ -658,7 +722,14 @@ func (ec *executionContext) _Query_search(field graphql.CollectedField) graphql. return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_search(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -683,7 +754,14 @@ func (ec *executionContext) _Query_character(field graphql.CollectedField) graph return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_character(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -704,7 +782,14 @@ func (ec *executionContext) _Query_droid(field graphql.CollectedField) graphql.M return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_droid(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -728,7 +813,14 @@ func (ec *executionContext) _Query_human(field graphql.CollectedField) graphql.M return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_human(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -752,7 +844,14 @@ func (ec *executionContext) _Query_starship(field graphql.CollectedField) graphq return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_starship(ec.ctx, arg0) if err != nil { ec.Error(err) diff --git a/example/todo/generated.go b/example/todo/generated.go index f37b2764241..b0da0413009 100644 --- a/example/todo/generated.go +++ b/example/todo/generated.go @@ -34,8 +34,8 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._MyQuery(op.Selections) var buf bytes.Buffer @@ -47,8 +47,8 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia } } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._MyMutation(op.Selections) var buf bytes.Buffer @@ -60,7 +60,7 @@ func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, va } } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { return graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{{Message: "subscriptions are not supported"}}}) } @@ -70,6 +70,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } var myMutationImplementors = []string{"MyMutation"} @@ -188,7 +189,14 @@ func (ec *executionContext) _MyQuery_todo(field graphql.CollectedField) graphql. return graphql.Null } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.MyQuery_todo(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -202,7 +210,14 @@ func (ec *executionContext) _MyQuery_todo(field graphql.CollectedField) graphql. } func (ec *executionContext) _MyQuery_lastTodo(field graphql.CollectedField) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.MyQuery_lastTodo(ec.ctx) if err != nil { ec.Error(err) @@ -216,7 +231,14 @@ func (ec *executionContext) _MyQuery_lastTodo(field graphql.CollectedField) grap } func (ec *executionContext) _MyQuery_todos(field graphql.CollectedField) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.MyQuery_todos(ec.ctx) if err != nil { ec.Error(err) diff --git a/example/todo/server/server.go b/example/todo/server/server.go index f570e3ef1a1..fa2e0f7d53b 100644 --- a/example/todo/server/server.go +++ b/example/todo/server/server.go @@ -1,8 +1,10 @@ package main import ( + "errors" "log" "net/http" + "runtime/debug" "github.com/vektah/gqlgen/example/todo" "github.com/vektah/gqlgen/handler" @@ -10,6 +12,13 @@ import ( func main() { http.Handle("/", handler.Playground("Todo", "/query")) - http.Handle("/query", handler.GraphQL(todo.MakeExecutableSchema(todo.New()))) + http.Handle("/query", handler.GraphQL( + todo.MakeExecutableSchema(todo.New()), + handler.RecoverFunc(func(err interface{}) error { + log.Printf("send this panic somewhere") + debug.PrintStack() + return errors.New("user message on panic") + }), + )) log.Fatal(http.ListenAndServe(":8081", nil)) } diff --git a/example/todo/todo.go b/example/todo/todo.go index 5ae218b917e..25028035692 100644 --- a/example/todo/todo.go +++ b/example/todo/todo.go @@ -28,6 +28,11 @@ func New() *todoResolver { func (r *todoResolver) MyQuery_todo(ctx context.Context, id int) (*Todo, error) { time.Sleep(220 * time.Millisecond) + + if id == 666 { + panic("critical failure") + } + for _, todo := range r.todos { if todo.ID == id { return &todo, nil diff --git a/example/todo/todo_test.go b/example/todo/todo_test.go index ad6c27452c4..e479ea36663 100644 --- a/example/todo/todo_test.go +++ b/example/todo/todo_test.go @@ -74,6 +74,15 @@ func TestTodo(t *testing.T) { require.Nil(t, resp.Todo) }) + t.Run("test panic", func(t *testing.T) { + var resp struct { + Todo *struct{ Text string } + } + err := c.Post(`{ todo(id:666) { text } }`, &resp) + + require.EqualError(t, err, "errors: [graphql: internal system error]") + }) + t.Run("select all", func(t *testing.T) { var resp struct { Todo struct { diff --git a/graphql/exec.go b/graphql/exec.go index 93ddb80f09e..dc7166ecafa 100644 --- a/graphql/exec.go +++ b/graphql/exec.go @@ -11,9 +11,9 @@ import ( type ExecutableSchema interface { Schema() *schema.Schema - Query(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation) *Response - Mutation(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation) *Response - Subscription(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation) func() *Response + Query(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation, recover RecoverFunc) *Response + Mutation(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation, recover RecoverFunc) *Response + Subscription(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation, recover RecoverFunc) func() *Response } func CollectFields(doc *query.Document, selSet []query.Selection, satisfies []string, variables map[string]interface{}) []CollectedField { diff --git a/graphql/recovery.go b/graphql/recovery.go new file mode 100644 index 00000000000..547041e1cb9 --- /dev/null +++ b/graphql/recovery.go @@ -0,0 +1,18 @@ +package graphql + +import ( + "errors" + "fmt" + "os" + "runtime/debug" +) + +type RecoverFunc func(err interface{}) (userMessage error) + +func DefaultRecoverFunc(err interface{}) error { + fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr) + debug.PrintStack() + + return errors.New("internal system error") +} diff --git a/handler/graphql.go b/handler/graphql.go index 94fb87ef1cb..4b22a716eaf 100644 --- a/handler/graphql.go +++ b/handler/graphql.go @@ -22,6 +22,7 @@ type params struct { type Config struct { upgrader websocket.Upgrader + recover graphql.RecoverFunc } type Option func(cfg *Config) @@ -32,8 +33,15 @@ func WebsocketUpgrader(upgrader websocket.Upgrader) Option { } } +func RecoverFunc(recover graphql.RecoverFunc) Option { + return func(cfg *Config) { + cfg.recover = recover + } +} + func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc { cfg := Config{ + recover: graphql.DefaultRecoverFunc, upgrader: websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -46,7 +54,7 @@ func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.Header.Get("Upgrade"), "websocket") { - connectWs(exec, w, r, cfg.upgrader) + connectWs(exec, w, r, cfg.upgrader, cfg.recover) return } @@ -90,13 +98,13 @@ func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc switch op.Type { case query.Query: - b, err := json.Marshal(exec.Query(r.Context(), doc, reqParams.Variables, op)) + b, err := json.Marshal(exec.Query(r.Context(), doc, reqParams.Variables, op, cfg.recover)) if err != nil { panic(err) } w.Write(b) case query.Mutation: - b, err := json.Marshal(exec.Mutation(r.Context(), doc, reqParams.Variables, op)) + b, err := json.Marshal(exec.Mutation(r.Context(), doc, reqParams.Variables, op, cfg.recover)) if err != nil { panic(err) } diff --git a/handler/stub.go b/handler/stub.go index e3d445e8fc6..93fe855fba6 100644 --- a/handler/stub.go +++ b/handler/stub.go @@ -23,17 +23,17 @@ func (e *executableSchemaStub) Schema() *schema.Schema { `) } -func (e *executableSchemaStub) Query(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchemaStub) Query(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { return &graphql.Response{Data: []byte(`{"name":"test"}`)} } -func (e *executableSchemaStub) Mutation(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchemaStub) Mutation(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { return &graphql.Response{ Errors: []*errors.QueryError{{Message: "mutations are not supported"}}, } } -func (e *executableSchemaStub) Subscription(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchemaStub) Subscription(ctx context.Context, document *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { return func() *graphql.Response { time.Sleep(20 * time.Millisecond) select { diff --git a/handler/websocket.go b/handler/websocket.go index e85bced1584..ef71a240778 100644 --- a/handler/websocket.go +++ b/handler/websocket.go @@ -36,14 +36,15 @@ type operationMessage struct { } type wsConnection struct { - ctx context.Context - conn *websocket.Conn - exec graphql.ExecutableSchema - active map[string]context.CancelFunc - mu sync.Mutex + ctx context.Context + conn *websocket.Conn + exec graphql.ExecutableSchema + active map[string]context.CancelFunc + mu sync.Mutex + recover graphql.RecoverFunc } -func connectWs(exec graphql.ExecutableSchema, w http.ResponseWriter, r *http.Request, upgrader websocket.Upgrader) { +func connectWs(exec graphql.ExecutableSchema, w http.ResponseWriter, r *http.Request, upgrader websocket.Upgrader, recover graphql.RecoverFunc) { ws, err := upgrader.Upgrade(w, r, http.Header{ "Sec-Websocket-Protocol": []string{"graphql-ws"}, }) @@ -54,10 +55,11 @@ func connectWs(exec graphql.ExecutableSchema, w http.ResponseWriter, r *http.Req } conn := wsConnection{ - active: map[string]context.CancelFunc{}, - exec: exec, - conn: ws, - ctx: r.Context(), + active: map[string]context.CancelFunc{}, + exec: exec, + conn: ws, + ctx: r.Context(), + recover: recover, } if !conn.init() { @@ -156,9 +158,9 @@ func (c *wsConnection) subscribe(message *operationMessage) bool { if op.Type != query.Subscription { var result *graphql.Response if op.Type == query.Query { - result = c.exec.Query(c.ctx, doc, reqParams.Variables, op) + result = c.exec.Query(c.ctx, doc, reqParams.Variables, op, c.recover) } else { - result = c.exec.Mutation(c.ctx, doc, reqParams.Variables, op) + result = c.exec.Mutation(c.ctx, doc, reqParams.Variables, op, c.recover) } c.sendData(message.ID, result) @@ -171,7 +173,13 @@ func (c *wsConnection) subscribe(message *operationMessage) bool { c.active[message.ID] = cancel c.mu.Unlock() go func() { - next := c.exec.Subscription(ctx, doc, reqParams.Variables, op) + defer func() { + if r := recover(); r != nil { + userErr := c.recover(r) + c.sendError(message.ID, &errors.QueryError{Message: userErr.Error()}) + } + }() + next := c.exec.Subscription(ctx, doc, reqParams.Variables, op, c.recover) for result := next(); result != nil; result = next() { fmt.Println(result) c.sendData(message.ID, result) diff --git a/test/generated.go b/test/generated.go index afdbfd65a62..3c17333773d 100644 --- a/test/generated.go +++ b/test/generated.go @@ -32,8 +32,8 @@ func (e *executableSchema) Schema() *schema.Schema { return parsedSchema } -func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { - ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx} +func (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { + ec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx, recover: recover} data := ec._Query(op.Selections) var buf bytes.Buffer @@ -45,11 +45,11 @@ func (e *executableSchema) Query(ctx context.Context, doc *query.Document, varia } } -func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response { +func (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) *graphql.Response { return &graphql.Response{Errors: []*errors.QueryError{{Message: "mutations are not supported"}}} } -func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response { +func (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation, recover graphql.RecoverFunc) func() *graphql.Response { return graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{{Message: "subscriptions are not supported"}}}) } @@ -59,6 +59,7 @@ type executionContext struct { variables map[string]interface{} doc *query.Document ctx context.Context + recover graphql.RecoverFunc } var innerObjectImplementors = []string{"InnerObject"} @@ -111,7 +112,14 @@ func (ec *executionContext) _OuterObject(sel []query.Selection, obj *OuterObject } func (ec *executionContext) _OuterObject_inner(field graphql.CollectedField, obj *OuterObject) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.OuterObject_inner(ec.ctx, obj) if err != nil { ec.Error(err) @@ -186,7 +194,14 @@ func (ec *executionContext) _Query_nestedInputs(field graphql.CollectedField) gr } } - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_nestedInputs(ec.ctx, arg0) if err != nil { ec.Error(err) @@ -200,7 +215,14 @@ func (ec *executionContext) _Query_nestedInputs(field graphql.CollectedField) gr } func (ec *executionContext) _Query_nestedOutputs(field graphql.CollectedField) graphql.Marshaler { - return graphql.Defer(func() graphql.Marshaler { + return graphql.Defer(func() (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + userErr := ec.recover(r) + ec.Error(userErr) + ret = graphql.Null + } + }() res, err := ec.resolvers.Query_nestedOutputs(ec.ctx) if err != nil { ec.Error(err)