Skip to content

Commit

Permalink
Add documentation for scalad error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
bowd committed Mar 20, 2020
1 parent 1aa20f2 commit 61fa990
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 140 deletions.
1 change: 0 additions & 1 deletion codegen/generated!.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

{{ reserveImport "github.com/vektah/gqlparser/v2" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/gqlerror" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }}

Expand Down
6 changes: 3 additions & 3 deletions codegen/testserver/directive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ func TestDirectives(t *testing.T) {

err := c.Post(`query { directiveInputNullable(arg: {text:"invalid text",inner:{message:"123"}}) }`, &resp)

require.EqualError(t, err, `[{"message":"not valid","path":["directiveInputNullable"]}]`)
require.EqualError(t, err, `[{"message":"not valid","path":["directiveInputNullable","arg"]}]`)
require.Nil(t, resp.DirectiveInputNullable)
})
t.Run("when function errors on inner directives", func(t *testing.T) {
Expand All @@ -311,7 +311,7 @@ func TestDirectives(t *testing.T) {

err := c.Post(`query { directiveInputNullable(arg: {text:"2",inner:{message:""}}) }`, &resp)

require.EqualError(t, err, `[{"message":"not valid","path":["directiveInputNullable"]}]`)
require.EqualError(t, err, `[{"message":"not valid","path":["directiveInputNullable","arg","inner"]}]`)
require.Nil(t, resp.DirectiveInputNullable)
})
t.Run("when function errors on nullable inner directives", func(t *testing.T) {
Expand All @@ -321,7 +321,7 @@ func TestDirectives(t *testing.T) {

err := c.Post(`query { directiveInputNullable(arg: {text:"success",inner:{message:"1"},innerNullable:{message:""}}) }`, &resp)

require.EqualError(t, err, `[{"message":"not valid","path":["directiveInputNullable"]}]`)
require.EqualError(t, err, `[{"message":"not valid","path":["directiveInputNullable","arg","innerNullable"]}]`)
require.Nil(t, resp.DirectiveInputNullable)
})
t.Run("when function success", func(t *testing.T) {
Expand Down
219 changes: 115 additions & 104 deletions codegen/testserver/generated.go

Large diffs are not rendered by default.

16 changes: 1 addition & 15 deletions codegen/testserver/mutation_with_custom_scalar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package testserver

import (
"context"
"encoding/json"
"github.com/99designs/gqlgen/client"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -45,19 +44,6 @@ func TestErrorInsideMutationArgument(t *testing.T) {
&resp,
client.Var("input", input),
)
jsonErr, ok := err.(client.RawJsonError)
require.True(t, ok)
var errDetails []map[string]interface{}
err = json.Unmarshal(jsonErr.RawMessage, &errDetails)
require.NoError(t, err)
require.Len(t, errDetails, 1)
firstErr := errDetails[0]
path, ok := firstErr["path"].([]interface{})
require.Equal(t, path, []interface{}{
"updateSomething",
"input",
"nesting",
"field",
})
require.EqualError(t, err, `[{"message":"invalid email format","path":["updateSomething","input","nesting","field"]}]`)
})
}
27 changes: 10 additions & 17 deletions codegen/type.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{{- end }}
{{- if $type.IsPtr }}
res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v)
return &res, err
return &res, graphql.WrapErrorWithInputPath(ctx, err)
{{- else if $type.IsSlice }}
var vSlice []interface{}
if v != nil {
Expand All @@ -19,38 +19,31 @@
var err error
res := make([]{{$type.GO.Elem | ref}}, len(vSlice))
for i := range vSlice {
res[i], err = ec.{{ $type.Elem.UnmarshalFunc }}(ctx, vSlice[i])
nctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithIndex(i))
res[i], err = ec.{{ $type.Elem.UnmarshalFunc }}(nctx, vSlice[i])
if err != nil {
return nil, err
return nil, graphql.WrapErrorWithInputPath(nctx, err)
}
}
return res, nil
{{- else }}
{{- if $type.Unmarshaler }}
{{- if $type.CastType }}
tmp, err := {{ $type.Unmarshaler | call }}(v)
return {{ $type.GO | ref }}(tmp), err
return {{ $type.GO | ref }}(tmp), graphql.WrapErrorWithInputPath(ctx, err)
{{- else}}
return {{ $type.Unmarshaler | call }}(v)
res, err := {{ $type.Unmarshaler | call }}(v)
return res, graphql.WrapErrorWithInputPath(ctx, err)
{{- end }}
{{- else if eq ($type.GO | ref) "map[string]interface{}" }}
return v.(map[string]interface{}), nil
{{- else if $type.IsMarshaler }}
var res {{ $type.GO | ref }}
err := res.UnmarshalGQL(v)
if err != nil {
fic := graphql.GetFieldInputContext(ctx)
path := fic.Path()
if gerr, ok := err.(*gqlerror.Error); ok {
gerr.Path = path
return res, gerr
} else {
return res, gqlerror.WrapPath(path, err)
}
}
return res, nil
return res, graphql.WrapErrorWithInputPath(ctx, err)
{{- else }}
return ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v)
res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v)
return res, graphql.WrapErrorWithInputPath(ctx, err)
{{- end }}
{{- end }}
}
Expand Down
54 changes: 54 additions & 0 deletions docs/content/reference/scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,57 @@ models:
```
See the [example/scalars](https://github.com/99designs/gqlgen/tree/master/example/scalars) package for more examples.
## Unmarshaling Errors
The errors that occur as part of custom scalar unmarshaling will return a full path to the field.
For example, given the following schema ...
```graphql
extend type Mutation{
updateUser(userInput: UserInput!): User!
}

input UserInput {
name: String!
primaryContactDetails: ContactDetailsInput!
secondaryContactDetails: ContactDetailsInput!
}

scalar Email
input ContactDetailsInput {
email: Email!
}
```

... and the following variables:

```json

{
"userInput": {
"name": "George",
"primaryContactDetails": {
"email": "not-an-email"
},
"secondaryContactDetails": {
"email": "george@gmail.com"
}
}
}
```

... and an unmarshal function that returns an error if the email is invalid. The mutation will return an error containing the full path:
```json
{
"message": "email invalid",
"path": [
"updateUser",
"userInput",
"primaryContactDetails",
"email"
]
}
```


18 changes: 18 additions & 0 deletions graphql/context_field_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package graphql
import (
"context"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/gqlerror"
)

const fieldInputCtx key = "field_input_context"
Expand Down Expand Up @@ -64,3 +65,20 @@ func GetFieldInputContext(ctx context.Context) *FieldInputContext {
}
return nil
}

func WrapErrorWithInputPath(ctx context.Context, err error) error {
if err == nil {
return nil
}

inputContext := GetFieldInputContext(ctx)
path := inputContext.Path()
if gerr, ok := err.(*gqlerror.Error); ok {
if gerr.Path == nil {
gerr.Path = path
}
return gerr
} else {
return gqlerror.WrapPath(path, err)
}
}

0 comments on commit 61fa990

Please sign in to comment.