Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errors: adds OriginalError support #423

Merged
merged 3 commits into from Dec 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 19 additions & 16 deletions abstract_test.go
Expand Up @@ -491,6 +491,7 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) {
}
}`

originalError := gqlerrors.NewFormattedError(`Runtime Object type "Human" is not a possible type for "Pet".`)
expected := &graphql.Result{
Data: map[string]interface{}{
"pets": []interface{}{
Expand All @@ -505,27 +506,27 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) {
nil,
},
},
Errors: []gqlerrors.FormattedError{
{
Message: `Runtime Object type "Human" is not a possible type for "Pet".`,
Locations: []location.SourceLocation{
{
Line: 2,
Column: 7,
},
},
Path: []interface{}{
"pets",
2,
Errors: []gqlerrors.FormattedError{gqlerrors.FormatError(gqlerrors.Error{
Message: originalError.Message,
Locations: []location.SourceLocation{
{
Line: 2,
Column: 7,
},
},
},
Path: []interface{}{
"pets",
2,
},
OriginalError: originalError,
})},
}

result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})

if len(result.Errors) == 0 {
t.Fatalf("wrong result, expected errors: %v, got: %v", len(expected.Errors), len(result.Errors))
}
Expand Down Expand Up @@ -618,6 +619,7 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) {
}
}`

originalError := gqlerrors.NewFormattedError(`Runtime Object type "Human" is not a possible type for "Pet".`)
expected := &graphql.Result{
Data: map[string]interface{}{
"pets": []interface{}{
Expand All @@ -633,8 +635,8 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) {
},
},
Errors: []gqlerrors.FormattedError{
{
Message: `Runtime Object type "Human" is not a possible type for "Pet".`,
gqlerrors.FormatError(gqlerrors.Error{
Message: originalError.Message,
Locations: []location.SourceLocation{
{
Line: 2,
Expand All @@ -645,7 +647,8 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) {
"pets",
2,
},
},
OriginalError: originalError,
}),
},
}

Expand Down
127 changes: 100 additions & 27 deletions executor_test.go
Expand Up @@ -515,18 +515,19 @@ func TestNullsOutErrorSubtrees(t *testing.T) {
"sync": "sync",
"syncError": nil,
}
expectedErrors := []gqlerrors.FormattedError{
{
Message: "Error getting syncError",
Locations: []location.SourceLocation{
{
Line: 3, Column: 7,
},
},
Path: []interface{}{
"syncError",
originalError := errors.New("Error getting syncError")
expectedErrors := []gqlerrors.FormattedError{gqlerrors.FormatError(gqlerrors.Error{
Message: originalError.Error(),
Locations: []location.SourceLocation{
{
Line: 3, Column: 7,
},
},
Path: []interface{}{
"syncError",
},
OriginalError: originalError,
}),
}

data := map[string]interface{}{
Expand Down Expand Up @@ -1296,6 +1297,7 @@ func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) {
},
}

originalError := gqlerrors.NewFormattedError(`Expected value of type "SpecialType" but got: graphql_test.testNotSpecialType.`)
expected := &graphql.Result{
Data: map[string]interface{}{
"specials": []interface{}{
Expand All @@ -1305,21 +1307,20 @@ func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) {
nil,
},
},
Errors: []gqlerrors.FormattedError{
{
Message: `Expected value of type "SpecialType" but got: graphql_test.testNotSpecialType.`,
Locations: []location.SourceLocation{
{
Line: 1,
Column: 3,
},
},
Path: []interface{}{
"specials",
1,
Errors: []gqlerrors.FormattedError{gqlerrors.FormatError(gqlerrors.Error{
Message: originalError.Message,
Locations: []location.SourceLocation{
{
Line: 1,
Column: 3,
},
},
},
Path: []interface{}{
"specials",
1,
},
OriginalError: originalError,
})},
}

specialType := graphql.NewObject(graphql.ObjectConfig{
Expand Down Expand Up @@ -2045,7 +2046,7 @@ func (err extendedError) Extensions() map[string]interface{} {

var _ gqlerrors.ExtendedError = &extendedError{}

func testErrors(t *testing.T, nameType graphql.Output, extensions map[string]interface{}) *graphql.Result {
func testErrors(t *testing.T, nameType graphql.Output, extensions map[string]interface{}, formatErrorFn func(err error) error) *graphql.Result {
type Hero struct {
Id string `graphql:"id"`
Name string
Expand All @@ -2072,7 +2073,12 @@ func testErrors(t *testing.T, nameType graphql.Output, extensions map[string]int
if hero.Name != "" {
return hero.Name, nil
}

err := fmt.Errorf("Name for character with ID %v could not be fetched.", hero.Id)
if formatErrorFn != nil {
err = formatErrorFn(err)
}

if extensions != nil {
return nil, &extendedError{
error: err,
Expand Down Expand Up @@ -2133,7 +2139,7 @@ func testErrors(t *testing.T, nameType graphql.Output, extensions map[string]int

// http://facebook.github.io/graphql/June2018/#example-bc485
func TestQuery_ErrorPath(t *testing.T) {
result := testErrors(t, graphql.String, nil)
result := testErrors(t, graphql.String, nil, nil)

assertJSON(t, `{
"errors": [
Expand Down Expand Up @@ -2167,7 +2173,7 @@ func TestQuery_ErrorPath(t *testing.T) {

// http://facebook.github.io/graphql/June2018/#example-08b62
func TestQuery_ErrorPathForNonNullField(t *testing.T) {
result := testErrors(t, graphql.NewNonNull(graphql.String), nil)
result := testErrors(t, graphql.NewNonNull(graphql.String), nil, nil)

assertJSON(t, `{
"errors": [
Expand Down Expand Up @@ -2201,7 +2207,7 @@ func TestQuery_ErrorExtensions(t *testing.T) {
result := testErrors(t, graphql.NewNonNull(graphql.String), map[string]interface{}{
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018",
})
}, nil)

assertJSON(t, `{
"errors": [
Expand Down Expand Up @@ -2232,3 +2238,70 @@ func TestQuery_ErrorExtensions(t *testing.T) {
}
}`, result)
}

func TestQuery_OriginalErrorBuiltin(t *testing.T) {
result := testErrors(t, graphql.String, nil, nil)
originalError := result.Errors[0].OriginalError()
switch originalError.(type) {
case error:
default:
t.Fatalf("unexpected error: %v", reflect.TypeOf(originalError))
}
}

func TestQuery_OriginalErrorExtended(t *testing.T) {
result := testErrors(t, graphql.String, map[string]interface{}{
"code": "CAN_NOT_FETCH_BY_ID",
}, nil)
originalError := result.Errors[0].OriginalError()
switch originalError.(type) {
case *extendedError:
case extendedError:
default:
t.Fatalf("unexpected error: %v", reflect.TypeOf(originalError))
}
}

type customError struct {
error
}

func (e customError) Error() string {
return e.error.Error()
}

func TestQuery_OriginalErrorCustom(t *testing.T) {
result := testErrors(t, graphql.String, nil, func(err error) error {
return customError{error: err}
})
originalError := result.Errors[0].OriginalError()
switch originalError.(type) {
case customError:
default:
t.Fatalf("unexpected error: %v", reflect.TypeOf(originalError))
}
}

func TestQuery_OriginalErrorCustomPtr(t *testing.T) {
result := testErrors(t, graphql.String, nil, func(err error) error {
return &customError{error: err}
})
originalError := result.Errors[0].OriginalError()
switch originalError.(type) {
case *customError:
default:
t.Fatalf("unexpected error: %v", reflect.TypeOf(originalError))
}
}

func TestQuery_OriginalErrorPanic(t *testing.T) {
result := testErrors(t, graphql.String, nil, func(err error) error {
panic(errors.New("panic error"))
})
originalError := result.Errors[0].OriginalError()
switch originalError.(type) {
case error:
default:
t.Fatalf("unexpected error: %v", reflect.TypeOf(originalError))
}
}
20 changes: 13 additions & 7 deletions gqlerrors/formatted.go
Expand Up @@ -12,10 +12,15 @@ type ExtendedError interface {
}

type FormattedError struct {
Message string `json:"message"`
Locations []location.SourceLocation `json:"locations"`
Path []interface{} `json:"path,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
Message string `json:"message"`
Locations []location.SourceLocation `json:"locations"`
Path []interface{} `json:"path,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
originalError error
}

func (g FormattedError) OriginalError() error {
return g.originalError
}

func (g FormattedError) Error() string {
Expand All @@ -33,9 +38,10 @@ func FormatError(err error) FormattedError {
return err
case *Error:
ret := FormattedError{
Message: err.Error(),
Locations: err.Locations,
Path: err.Path,
Message: err.Error(),
Locations: err.Locations,
Path: err.Path,
originalError: err.OriginalError,
}
if err := err.OriginalError; err != nil {
if extended, ok := err.(ExtendedError); ok {
Expand Down