diff --git a/abstract_test.go b/abstract_test.go index bab35751..20ebd9d3 100644 --- a/abstract_test.go +++ b/abstract_test.go @@ -355,19 +355,7 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) { }` expected := &graphql.Result{ - Data: map[string]interface{}{ - "pets": []interface{}{ - map[string]interface{}{ - "name": "Odie", - "woofs": bool(true), - }, - map[string]interface{}{ - "name": "Garfield", - "meows": bool(false), - }, - nil, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Runtime Object type "Human" is not a possible type for "Pet".`, @@ -473,19 +461,7 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) { }` expected := &graphql.Result{ - Data: map[string]interface{}{ - "pets": []interface{}{ - map[string]interface{}{ - "name": "Odie", - "woofs": bool(true), - }, - map[string]interface{}{ - "name": "Garfield", - "meows": bool(false), - }, - nil, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Runtime Object type "Human" is not a possible type for "Pet".`, diff --git a/executor.go b/executor.go index 80d6d261..deb30a45 100644 --- a/executor.go +++ b/executor.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "reflect" + "sort" "strings" "github.com/flynn/graphql/gqlerrors" @@ -32,7 +33,6 @@ func Execute(p ExecuteParams) (result *Result) { AST: p.AST, OperationName: p.OperationName, Args: p.Args, - Errors: nil, Result: result, Context: p.Context, }) @@ -42,17 +42,6 @@ func Execute(p ExecuteParams) (result *Result) { return } - defer func() { - if r := recover(); r != nil { - var err error - if r, ok := r.(error); ok { - err = gqlerrors.FormatError(r) - } - exeContext.Errors = append(exeContext.Errors, gqlerrors.FormatError(err)) - result.Errors = exeContext.Errors - } - }() - return executeOperation(ExecuteOperationParams{ ExecutionContext: exeContext, Root: p.Root, @@ -66,7 +55,6 @@ type BuildExecutionCtxParams struct { AST *ast.Document OperationName string Args map[string]interface{} - Errors []gqlerrors.FormattedError Result *Result Context context.Context } @@ -76,7 +64,6 @@ type ExecutionContext struct { Root interface{} Operation ast.Definition VariableValues map[string]interface{} - Errors []gqlerrors.FormattedError Context context.Context } @@ -122,7 +109,6 @@ func buildExecutionContext(p BuildExecutionCtxParams) (*ExecutionContext, error) eCtx.Root = p.Root eCtx.Operation = operation eCtx.VariableValues = variableValues - eCtx.Errors = p.Errors eCtx.Context = p.Context return eCtx, nil } @@ -215,26 +201,7 @@ type ExecuteFieldsParams struct { // Implements the "Evaluating selection sets" section of the spec for "write" mode. func executeFieldsSerially(p ExecuteFieldsParams) *Result { - if p.Source == nil { - p.Source = map[string]interface{}{} - } - if p.Fields == nil { - p.Fields = map[string][]*ast.Field{} - } - - finalResults := map[string]interface{}{} - for responseName, fieldASTs := range p.Fields { - resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs) - if state.hasNoFieldDefs { - continue - } - finalResults[responseName] = resolved - } - - return &Result{ - Data: finalResults, - Errors: p.ExecutionContext.Errors, - } + return executeFields(p) } // Implements the "Evaluating selection sets" section of the spec for "read" mode. @@ -246,9 +213,22 @@ func executeFields(p ExecuteFieldsParams) *Result { p.Fields = map[string][]*ast.Field{} } + // deterministic execution order + fieldNames := make([]string, 0, len(p.Fields)) + for responseName := range p.Fields { + fieldNames = append(fieldNames, responseName) + } + sort.Strings(fieldNames) + finalResults := map[string]interface{}{} - for responseName, fieldASTs := range p.Fields { - resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs) + for _, responseName := range fieldNames { + fieldASTs := p.Fields[responseName] + resolved, state, err := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs) + if err != nil { + return &Result{ + Errors: []gqlerrors.FormattedError{gqlerrors.FormatError(err)}, + } + } if state.hasNoFieldDefs { continue } @@ -256,8 +236,7 @@ func executeFields(p ExecuteFieldsParams) *Result { } return &Result{ - Data: finalResults, - Errors: p.ExecutionContext.Errors, + Data: finalResults, } } @@ -475,44 +454,21 @@ type resolveFieldResultState struct { // figures out the value that the field returns by calling its resolve function, // then calls completeValue to complete promises, serialize scalars, or execute // the sub-selection-set for objects. -func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field) (result interface{}, resultState resolveFieldResultState) { - // catch panic from resolveFn - var returnType Output - defer func() (interface{}, resolveFieldResultState) { - if r := recover(); r != nil { - - var err error - if r, ok := r.(string); ok { - err = NewLocatedError( - fmt.Sprintf("%v", r), - FieldASTsToNodeASTs(fieldASTs), - ) - } - if r, ok := r.(error); ok { - err = gqlerrors.FormatError(r) - } - // send panic upstream - if _, ok := returnType.(*NonNull); ok { - panic(gqlerrors.FormatError(err)) - } - eCtx.Errors = append(eCtx.Errors, gqlerrors.FormatError(err)) - return result, resultState - } - return result, resultState - }() - +func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field) (interface{}, resolveFieldResultState, error) { fieldAST := fieldASTs[0] fieldName := "" if fieldAST.Name != nil { fieldName = fieldAST.Name.Value } + resultState := resolveFieldResultState{} + fieldDef := getFieldDef(eCtx.Schema, parentType, fieldName) if fieldDef == nil { resultState.hasNoFieldDefs = true - return nil, resultState + return nil, resultState, nil } - returnType = fieldDef.Type + returnType := fieldDef.Type resolveFn := fieldDef.Resolve if resolveFn == nil { resolveFn = defaultResolveFn @@ -535,75 +491,72 @@ func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{} VariableValues: eCtx.VariableValues, } - var resolveFnError error - - result, resolveFnError = resolveFn(ResolveParams{ + result, err := resolveFn(ResolveParams{ Source: source, Args: args, Info: info, Context: eCtx.Context, }) - if resolveFnError != nil { - panic(gqlerrors.FormatError(resolveFnError)) + if err != nil { + if _, ok := err.(*gqlerrors.Error); !ok { + err = NewLocatedError( + err.Error(), + FieldASTsToNodeASTs(fieldASTs), + ) + } + return nil, resultState, gqlerrors.FormatError(err) } - completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result) - return completed, resultState + completed, err := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result) + return completed, resultState, err } -func completeValueCatchingError(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (completed interface{}) { - // catch panic - defer func() interface{} { - if r := recover(); r != nil { - //send panic upstream - if _, ok := returnType.(*NonNull); ok { - panic(r) - } - if err, ok := r.(gqlerrors.FormattedError); ok { - eCtx.Errors = append(eCtx.Errors, err) - } - return completed - } - return completed - }() - - if returnType, ok := returnType.(*NonNull); ok { - completed := completeValue(eCtx, returnType, fieldASTs, info, result) - return completed - } - completed = completeValue(eCtx, returnType, fieldASTs, info, result) - return completed +func completeValueCatchingError(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) { + return completeValue(eCtx, returnType, fieldASTs, info, result) } -func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { +func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) { resultVal := reflect.ValueOf(result) if resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func { if propertyFn, ok := result.(func() interface{}); ok { - return propertyFn() + return propertyFn(), nil + } + if propertyFn, ok := result.(func() (interface{}, error)); ok { + r, err := propertyFn() + if err != nil { + return nil, NewLocatedError( + err.Error(), + FieldASTsToNodeASTs(fieldASTs), + ) + } + return r, nil } - err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` signature") - panic(gqlerrors.FormatError(err)) + err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` or `func() (interface{}, error)` signature") + return nil, gqlerrors.FormatError(err) } // If field type is NonNull, complete for inner type, and throw field error // if result is null. if returnType, ok := returnType.(*NonNull); ok { - completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, result) + completed, err := completeValue(eCtx, returnType.OfType, fieldASTs, info, result) + if err != nil { + return nil, err + } if completed == nil { err := NewLocatedError( fmt.Sprintf("Cannot return null for non-nullable field %v.%v.", info.ParentType, info.FieldName), FieldASTsToNodeASTs(fieldASTs), ) - panic(gqlerrors.FormatError(err)) + return nil, gqlerrors.FormatError(err) } - return completed + return completed, nil } // If result value is null-ish (null, undefined, or NaN) then return null. if isNullish(result) { - return nil + return nil, nil } // If field type is List, complete each item in the list with the inner type @@ -639,14 +592,14 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie fmt.Sprintf(`Cannot complete value of unexpected type "%v."`, returnType), ) if err != nil { - panic(gqlerrors.FormatError(err)) + return nil, gqlerrors.FormatError(err) } - return nil + return nil, nil } // completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type // of that value, then completing based on that type. -func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { +func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) { var runtimeType *Object @@ -667,21 +620,21 @@ func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldAST fmt.Sprintf(`Could not determine runtime type of value "%v" for field %v.%v.`, result, info.ParentType, info.FieldName), ) if err != nil { - panic(err) + return nil, err } if !eCtx.Schema.IsPossibleType(returnType, runtimeType) { - panic(gqlerrors.NewFormattedError( + return nil, gqlerrors.NewFormattedError( fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+ `for "%v".`, runtimeType, returnType), - )) + ) } return completeObjectValue(eCtx, runtimeType, fieldASTs, info, result) } // completeObjectValue complete an Object value by executing all sub-selections. -func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { +func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) { // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather @@ -693,9 +646,9 @@ func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs [ Context: eCtx.Context, } if !returnType.IsTypeOf(p) { - panic(gqlerrors.NewFormattedError( + return nil, gqlerrors.NewFormattedError( fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, result), - )) + ) } } @@ -726,21 +679,25 @@ func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs [ } results := executeFields(executeFieldsParams) - return results.Data + if len(results.Errors) > 0 { + return nil, results.Errors[0] + } + + return results.Data, nil } // completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible. -func completeLeafValue(returnType Leaf, result interface{}) interface{} { +func completeLeafValue(returnType Leaf, result interface{}) (interface{}, error) { serializedResult := returnType.Serialize(result) if isNullish(serializedResult) { - return nil + return nil, nil } - return serializedResult + return serializedResult, nil } // completeListValue complete a list value by completing each item in the list with the inner type -func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { +func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) { resultVal := reflect.ValueOf(result) parentTypeName := "" if info.ParentType != nil { @@ -752,17 +709,20 @@ func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*as "for field %v.%v.", parentTypeName, info.FieldName), ) if err != nil { - panic(gqlerrors.FormatError(err)) + return nil, gqlerrors.FormatError(err) } itemType := returnType.OfType completedResults := []interface{}{} for i := 0; i < resultVal.Len(); i++ { val := resultVal.Index(i).Interface() - completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val) + completedItem, err := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val) + if err != nil { + return nil, err + } completedResults = append(completedResults, completedItem) } - return completedResults + return completedResults, nil } // defaultResolveTypeFn If a resolveType function is not given, then a default resolve behavior is diff --git a/executor_test.go b/executor_test.go index 7a519c7c..0d23e555 100644 --- a/executor_test.go +++ b/executor_test.go @@ -510,17 +510,11 @@ func TestNullsOutErrorSubtrees(t *testing.T) { syncError, }` - expectedData := map[string]interface{}{ - "sync": "sync", - "syncError": nil, - } expectedErrors := []gqlerrors.FormattedError{ { Message: "Error getting syncError", Locations: []location.SourceLocation{ - { - Line: 3, Column: 7, - }, + {Line: 3, Column: 7}, }, }, } @@ -529,8 +523,8 @@ func TestNullsOutErrorSubtrees(t *testing.T) { "sync": func() interface{} { return "sync" }, - "syncError": func() interface{} { - panic("Error getting syncError") + "syncError": func() (interface{}, error) { + return nil, errors.New("Error getting syncError") }, } schema, err := graphql.NewSchema(graphql.SchemaConfig{ @@ -563,11 +557,11 @@ func TestNullsOutErrorSubtrees(t *testing.T) { if len(result.Errors) == 0 { t.Fatalf("wrong result, expected errors, got %v", len(result.Errors)) } - if !reflect.DeepEqual(expectedData, result.Data) { - t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedData, result.Data)) + if result.Data != nil { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(nil, result.Data)) } if !reflect.DeepEqual(expectedErrors, result.Errors) { - t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expectedErrors, result.Errors)) + t.Fatalf("Unexpected result, \nActual: %#v\nExpected:%#v\nDiff: %v", result.Errors, expectedErrors, testutil.Diff(expectedErrors, result.Errors)) } } @@ -1293,14 +1287,7 @@ func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) { } expected := &graphql.Result{ - Data: map[string]interface{}{ - "specials": []interface{}{ - map[string]interface{}{ - "value": "foo", - }, - nil, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Expected value of type "SpecialType" but got: graphql_test.testNotSpecialType.`, diff --git a/lists_test.go b/lists_test.go index 84ebf2d3..c3d8b18c 100644 --- a/lists_test.go +++ b/lists_test.go @@ -48,7 +48,7 @@ func checkList(t *testing.T, testType graphql.Type, testData interface{}, expect } result := testutil.TestExecute(t, ep) if len(expected.Errors) != len(result.Errors) { - t.Fatalf("wrong result, Diff: %v", testutil.Diff(expected.Errors, result.Errors)) + t.Fatalf("wrong result, Expected: %#v, Got: %#v", expected.Errors, result.Errors) } if !reflect.DeepEqual(expected, result) { t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result)) @@ -251,9 +251,7 @@ func TestLists_NonNullListOfNullableObjectsContainsNull(t *testing.T) { func TestLists_NonNullListOfNullableObjectsReturnsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.Int)) expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -321,9 +319,7 @@ func TestLists_NonNullListOfNullableFunc_ReturnsNull(t *testing.T) { return nil } expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -415,11 +411,7 @@ func TestLists_NullableListOfNonNullObjects_ContainsNull(t *testing.T) { 1, nil, 2, } expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": map[string]interface{}{ - "test": nil, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -480,11 +472,7 @@ func TestLists_NullableListOfNonNullFunc_ContainsNull(t *testing.T) { } } expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": map[string]interface{}{ - "test": nil, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -593,9 +581,7 @@ func TestLists_NonNullListOfNonNullObjects_ContainsNull(t *testing.T) { 1, nil, 2, } expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -614,9 +600,7 @@ func TestLists_NonNullListOfNonNullObjects_ReturnsNull(t *testing.T) { ttype := graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphql.Int))) expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -665,9 +649,7 @@ func TestLists_NonNullListOfNonNullFunc_ContainsNull(t *testing.T) { } } expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -691,9 +673,7 @@ func TestLists_NonNullListOfNonNullFunc_ReturnsNull(t *testing.T) { return nil } expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "Cannot return null for non-nullable field DataType.test.", @@ -766,11 +746,7 @@ func TestLists_UserErrorExpectIterableButDidNotGetOne(t *testing.T) { ttype := graphql.NewList(graphql.Int) data := "Not an iterable" expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": map[string]interface{}{ - "test": nil, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: "User Error: expected iterable, but did not find one for field DataType.test.", diff --git a/mutations_test.go b/mutations_test.go index 0fef12ca..b2a3eb69 100644 --- a/mutations_test.go +++ b/mutations_test.go @@ -1,6 +1,7 @@ package graphql_test import ( + "errors" "reflect" "testing" @@ -30,11 +31,11 @@ func (r *testRoot) ImmediatelyChangeTheNumber(newNumber int) *testNumberHolder { func (r *testRoot) PromiseToChangeTheNumber(newNumber int) *testNumberHolder { return r.ImmediatelyChangeTheNumber(newNumber) } -func (r *testRoot) FailToChangeTheNumber(newNumber int) *testNumberHolder { - panic("Cannot change the number") +func (r *testRoot) FailToChangeTheNumber(newNumber int) (*testNumberHolder, error) { + return nil, errors.New("Cannot change the number") } -func (r *testRoot) PromiseAndFailToChangeTheNumber(newNumber int) *testNumberHolder { - panic("Cannot change the number") +func (r *testRoot) PromiseAndFailToChangeTheNumber(newNumber int) (*testNumberHolder, error) { + return nil, errors.New("Cannot change the number") } // numberHolderType creates a mapping to testNumberHolder @@ -98,7 +99,7 @@ var mutationsTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ newNumber := 0 obj, _ := p.Source.(*testRoot) newNumber, _ = p.Args["newNumber"].(int) - return obj.FailToChangeTheNumber(newNumber), nil + return obj.FailToChangeTheNumber(newNumber) }, }, "promiseAndFailToChangeTheNumber": &graphql.Field{ @@ -112,7 +113,7 @@ var mutationsTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ newNumber := 0 obj, _ := p.Source.(*testRoot) newNumber, _ = p.Args["newNumber"].(int) - return obj.PromiseAndFailToChangeTheNumber(newNumber), nil + return obj.PromiseAndFailToChangeTheNumber(newNumber) }, }, }, @@ -201,22 +202,7 @@ func TestMutations_EvaluatesMutationsCorrectlyInThePresenceOfAFailedMutation(t * }` expected := &graphql.Result{ - Data: map[string]interface{}{ - "first": map[string]interface{}{ - "theNumber": 1, - }, - "second": map[string]interface{}{ - "theNumber": 2, - }, - "third": nil, - "fourth": map[string]interface{}{ - "theNumber": 4, - }, - "fifth": map[string]interface{}{ - "theNumber": 5, - }, - "sixth": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot change the number`, @@ -224,12 +210,6 @@ func TestMutations_EvaluatesMutationsCorrectlyInThePresenceOfAFailedMutation(t * {Line: 8, Column: 7}, }, }, - { - Message: `Cannot change the number`, - Locations: []location.SourceLocation{ - {Line: 17, Column: 7}, - }, - }, }, } // parse query diff --git a/nonnull_test.go b/nonnull_test.go index e884e2a5..cf0bbb49 100644 --- a/nonnull_test.go +++ b/nonnull_test.go @@ -1,6 +1,7 @@ package graphql_test import ( + "errors" "reflect" "sort" "testing" @@ -16,18 +17,18 @@ var nonNullSyncError = "nonNullSync" var promiseError = "promise" var nonNullPromiseError = "nonNullPromise" -var throwingData = map[string]interface{}{ - "sync": func() interface{} { - panic(syncError) +var erroringData = map[string]interface{}{ + "sync": func() (interface{}, error) { + return nil, errors.New(syncError) }, - "nonNullSync": func() interface{} { - panic(nonNullSyncError) + "nonNullSync": func() (interface{}, error) { + return nil, errors.New(nonNullSyncError) }, - "promise": func() interface{} { - panic(promiseError) + "promise": func() (interface{}, error) { + return nil, errors.New(promiseError) }, - "nonNullPromise": func() interface{} { - panic(nonNullPromiseError) + "nonNullPromise": func() (interface{}, error) { + return nil, errors.New(nonNullPromiseError) }, } @@ -69,17 +70,17 @@ var nonNullTestSchema, _ = graphql.NewSchema(graphql.SchemaConfig{ }) func init() { - throwingData["nest"] = func() interface{} { - return throwingData + erroringData["nest"] = func() interface{} { + return erroringData } - throwingData["nonNullNest"] = func() interface{} { - return throwingData + erroringData["nonNullNest"] = func() interface{} { + return erroringData } - throwingData["promiseNest"] = func() interface{} { - return throwingData + erroringData["promiseNest"] = func() interface{} { + return erroringData } - throwingData["nonNullPromiseNest"] = func() interface{} { - return throwingData + erroringData["nonNullPromiseNest"] = func() interface{} { + return erroringData } nullingData["nest"] = func() interface{} { @@ -117,9 +118,7 @@ func TestNonNull_NullsANullableFieldThatThrowsSynchronously(t *testing.T) { } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "sync": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: syncError, @@ -138,7 +137,7 @@ func TestNonNull_NullsANullableFieldThatThrowsSynchronously(t *testing.T) { ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -155,9 +154,7 @@ func TestNonNull_NullsANullableFieldThatThrowsInAPromise(t *testing.T) { } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "promise": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: promiseError, @@ -176,7 +173,7 @@ func TestNonNull_NullsANullableFieldThatThrowsInAPromise(t *testing.T) { ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -195,9 +192,7 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANullableFieldThat } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: nonNullSyncError, @@ -216,7 +211,7 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANullableFieldThat ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -235,9 +230,7 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: nonNullPromiseError, @@ -256,7 +249,7 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -275,9 +268,7 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "promiseNest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: nonNullSyncError, @@ -296,7 +287,7 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -315,9 +306,7 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "promiseNest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: nonNullPromiseError, @@ -336,7 +325,7 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -377,105 +366,14 @@ func TestNonNull_NullsAComplexTreeOfNullableFieldsThatThrow(t *testing.T) { } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": map[string]interface{}{ - "sync": nil, - "promise": nil, - "nest": map[string]interface{}{ - "sync": nil, - "promise": nil, - }, - "promiseNest": map[string]interface{}{ - "sync": nil, - "promise": nil, - }, - }, - "promiseNest": map[string]interface{}{ - "sync": nil, - "promise": nil, - "nest": map[string]interface{}{ - "sync": nil, - "promise": nil, - }, - "promiseNest": map[string]interface{}{ - "sync": nil, - "promise": nil, - }, - }, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ - { - Message: syncError, - Locations: []location.SourceLocation{ - {Line: 4, Column: 11}, - }, - }, - { - Message: syncError, - Locations: []location.SourceLocation{ - {Line: 7, Column: 13}, - }, - }, - { - Message: syncError, - Locations: []location.SourceLocation{ - {Line: 11, Column: 13}, - }, - }, - { - Message: syncError, - Locations: []location.SourceLocation{ - {Line: 16, Column: 11}, - }, - }, - { - Message: syncError, - Locations: []location.SourceLocation{ - {Line: 19, Column: 13}, - }, - }, - { - Message: syncError, - Locations: []location.SourceLocation{ - {Line: 23, Column: 13}, - }, - }, - { - Message: promiseError, - Locations: []location.SourceLocation{ - {Line: 5, Column: 11}, - }, - }, { Message: promiseError, Locations: []location.SourceLocation{ {Line: 8, Column: 13}, }, }, - { - Message: promiseError, - Locations: []location.SourceLocation{ - {Line: 12, Column: 13}, - }, - }, - { - Message: promiseError, - Locations: []location.SourceLocation{ - {Line: 17, Column: 11}, - }, - }, - { - Message: promiseError, - Locations: []location.SourceLocation{ - {Line: 20, Column: 13}, - }, - }, - { - Message: promiseError, - Locations: []location.SourceLocation{ - {Line: 24, Column: 13}, - }, - }, }, } // parse query @@ -485,7 +383,7 @@ func TestNonNull_NullsAComplexTreeOfNullableFieldsThatThrow(t *testing.T) { ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -550,37 +448,14 @@ func TestNonNull_NullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfField } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - "promiseNest": nil, - "anotherNest": nil, - "anotherPromiseNest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ - { - Message: nonNullSyncError, - Locations: []location.SourceLocation{ - {Line: 8, Column: 19}, - }, - }, - { - Message: nonNullSyncError, - Locations: []location.SourceLocation{ - {Line: 19, Column: 19}, - }, - }, { Message: nonNullPromiseError, Locations: []location.SourceLocation{ {Line: 30, Column: 19}, }, }, - { - Message: nonNullPromiseError, - Locations: []location.SourceLocation{ - {Line: 41, Column: 19}, - }, - }, }, } // parse query @@ -590,7 +465,7 @@ func TestNonNull_NullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfField ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -677,9 +552,7 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, @@ -715,9 +588,7 @@ func TestNonNull_NullsASynchronouslyReturnedObjectThatContainsANonNullableFieldT } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, @@ -754,9 +625,7 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "promiseNest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, @@ -792,9 +661,7 @@ func TestNonNull_NullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldTha } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "promiseNest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, @@ -948,37 +815,14 @@ func TestNonNull_NullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChainOf } ` expected := &graphql.Result{ - Data: map[string]interface{}{ - "nest": nil, - "promiseNest": nil, - "anotherNest": nil, - "anotherPromiseNest": nil, - }, + Data: nil, Errors: []gqlerrors.FormattedError{ - { - Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, - Locations: []location.SourceLocation{ - {Line: 8, Column: 19}, - }, - }, - { - Message: `Cannot return null for non-nullable field DataType.nonNullSync.`, - Locations: []location.SourceLocation{ - {Line: 19, Column: 19}, - }, - }, { Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, Locations: []location.SourceLocation{ {Line: 30, Column: 19}, }, }, - { - Message: `Cannot return null for non-nullable field DataType.nonNullPromise.`, - Locations: []location.SourceLocation{ - {Line: 41, Column: 19}, - }, - }, }, } // parse query @@ -1026,7 +870,7 @@ func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldThrows(t *testing.T) { ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { @@ -1058,7 +902,7 @@ func TestNonNull_NullsTheTopLevelIfSyncNonNullableFieldErrors(t *testing.T) { ep := graphql.ExecuteParams{ Schema: nonNullTestSchema, AST: ast, - Root: throwingData, + Root: erroringData, } result := testutil.TestExecute(t, ep) if len(result.Errors) != len(expected.Errors) { diff --git a/values.go b/values.go index 4949d3d2..af783838 100644 --- a/values.go +++ b/values.go @@ -330,7 +330,7 @@ func isNullish(value interface{}) bool { if value, ok := value.(float64); ok { return math.IsNaN(value) } - return value == nil + return !reflect.Indirect(reflect.ValueOf(value)).IsValid() } /**