diff --git a/executor.go b/executor.go index fdf1ed8e..73ca0985 100644 --- a/executor.go +++ b/executor.go @@ -728,18 +728,24 @@ func completeThunkValueCatchingError(eCtx *executionContext, returnType Type, fi } }() - propertyFn, ok := result.(func() interface{}) + propertyFn, ok := result.(func() (interface{}, error)) if !ok { - err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` signature") + err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() (interface{}, error)` signature") + panic(gqlerrors.FormatError(err)) + } + fnResult, err := propertyFn() + if err != nil { panic(gqlerrors.FormatError(err)) } - result = propertyFn() + + result = fnResult if returnType, ok := returnType.(*NonNull); ok { completed := completeValue(eCtx, returnType, fieldASTs, info, path, result) return completed } completed = completeValue(eCtx, returnType, fieldASTs, info, path, result) + return completed } diff --git a/executor_test.go b/executor_test.go index b4b09a50..71e3b262 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1846,7 +1846,7 @@ func TestThunkResultsProcessedCorrectly(t *testing.T) { bar.BazA = "A" bar.BazB = "B" - thunk := func() interface{} { return &bar } + thunk := func() (interface{}, error) { return &bar, nil } return thunk, nil }, }, @@ -1907,6 +1907,111 @@ func TestThunkResultsProcessedCorrectly(t *testing.T) { } } +func TestThunkErrorsAreHandledCorrectly(t *testing.T) { + var bazCError = errors.New("barC error") + barType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Bar", + Fields: graphql.Fields{ + "bazA": &graphql.Field{ + Type: graphql.String, + }, + "bazB": &graphql.Field{ + Type: graphql.String, + }, + "bazC": &graphql.Field{ + Type: graphql.String, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + thunk := func() (interface{}, error) { + return nil, bazCError + } + return thunk, nil + }, + }, + }, + }) + + fooType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Foo", + Fields: graphql.Fields{ + "bar": &graphql.Field{ + Type: barType, + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + var bar struct { + BazA string + BazB string + } + bar.BazA = "A" + bar.BazB = "B" + + thunk := func() (interface{}, error) { + return &bar, nil + } + return thunk, nil + }, + }, + }, + }) + + queryType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ + "foo": &graphql.Field{ + Type: fooType, + Resolve: func(params graphql.ResolveParams) (interface{}, error) { + var foo struct{} + return foo, nil + }, + }, + }, + }) + + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: queryType, + }) + + if err != nil { + t.Fatalf("unexpected error, got: %v", err) + } + + query := "{ foo { bar { bazA bazB bazC } } }" + result := graphql.Do(graphql.Params{ + Schema: schema, + RequestString: query, + }) + + foo := result.Data.(map[string]interface{})["foo"].(map[string]interface{}) + bar, ok := foo["bar"].(map[string]interface{}) + + if !ok { + t.Errorf("expected bar to be a map[string]interface{}: actual = %v", reflect.TypeOf(foo["bar"])) + } else { + if got, want := bar["bazA"], "A"; got != want { + t.Errorf("foo.bar.bazA: got=%v, want=%v", got, want) + } + if got, want := bar["bazB"], "B"; got != want { + t.Errorf("foo.bar.bazB: got=%v, want=%v", got, want) + } + if got := bar["bazC"]; got != nil { + t.Errorf("foo.bar.bazC: got=%v, want=nil", got) + } + var errs = result.Errors + if len(errs) != 1 { + t.Fatalf("expected 1 error, got %v", result.Errors) + } + if got, want := errs[0].Message, bazCError.Error(); got != want { + t.Errorf("expected error: got=%v, want=%v", got, want) + } + } + + if t.Failed() { + b, err := json.Marshal(result.Data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log(string(b)) + } +} + func assertJSON(t *testing.T, expected string, actual interface{}) { var e interface{} if err := json.Unmarshal([]byte(expected), &e); err != nil { diff --git a/lists_test.go b/lists_test.go index 6d1b02f7..9d9c6bb5 100644 --- a/lists_test.go +++ b/lists_test.go @@ -169,13 +169,13 @@ func TestLists_ListOfNullableArrayOfFuncContainsValues(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -193,16 +193,16 @@ func TestLists_ListOfNullableArrayOfFuncContainsNulls(t *testing.T) { ttype := graphql.NewList(graphql.Int) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return nil + func() (interface{}, error) { + return nil, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -354,13 +354,13 @@ func TestLists_NonNullListOfNullableArrayOfFunc_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -378,16 +378,16 @@ func TestLists_NonNullListOfNullableArrayOfFunc_ContainsNulls(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return nil + func() (interface{}, error) { + return nil, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -544,11 +544,11 @@ func TestLists_NullableListOfNonNullArrayOfFunc_ContainsValues(t *testing.T) { // `data` is a slice of functions that return values // Note that its uses the expected signature `func() interface{} {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -566,16 +566,16 @@ func TestLists_NullableListOfNonNullArrayOfFunc_ContainsNulls(t *testing.T) { ttype := graphql.NewList(graphql.NewNonNull(graphql.Int)) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error){...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return nil + func() (interface{}, error) { + return nil, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -773,13 +773,13 @@ func TestLists_NonNullListOfNonNullArrayOfFunc_ContainsValues(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{ @@ -797,16 +797,16 @@ func TestLists_NonNullListOfNonNullArrayOfFunc_ContainsNulls(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) // `data` is a slice of functions that return values - // Note that its uses the expected signature `func() interface{} {...}` + // Note that its uses the expected signature `func() (interface{}, error) {...}` data := []interface{}{ - func() interface{} { - return 1 + func() (interface{}, error) { + return 1, nil }, - func() interface{} { - return nil + func() (interface{}, error) { + return nil, nil }, - func() interface{} { - return 2 + func() (interface{}, error) { + return 2, nil }, } expected := &graphql.Result{