Skip to content

Commit

Permalink
[RFC] Add explicit context arg to graphql execution
Browse files Browse the repository at this point in the history
This **BREAKING** change introduces a new argument to the GraphQL execution API which is presented to resolution functions: `context`.

Removed `Schema` from `ResolveParams`, already available in `ResolveParams.Info`.

`IsTypeOf` and `ResolveType` params are now struct, similar to `FieldResolveFn`.

`context` is now available for resolving types in `IsTypeOf` and `ResolveType`, similar to `FieldResolveFn`.

Commit:
d7cc6f9aed462588291bc821238650c98ad53580 [d7cc6f9]
Parents:
576b6a15d1
Author:
Lee Byron <[email protected]>
Date:
23 March 2016 at 10:30:13 AM SGT
Commit Date:
25 March 2016 at 8:20:10 AM SGT
  • Loading branch information
sogko committed May 31, 2016
1 parent 79f48da commit 1e33c35
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 107 deletions.
32 changes: 16 additions & 16 deletions abstract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForInterface(t *testing.T) {
Interfaces: []*graphql.Interface{
petType,
},
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
_, ok := value.(*testDog)
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
_, ok := p.Value.(*testDog)
return ok
},
Fields: graphql.Fields{
Expand Down Expand Up @@ -72,8 +72,8 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForInterface(t *testing.T) {
Interfaces: []*graphql.Interface{
petType,
},
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
_, ok := value.(*testCat)
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
_, ok := p.Value.(*testCat)
return ok
},
Fields: graphql.Fields{
Expand Down Expand Up @@ -162,8 +162,8 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForUnion(t *testing.T) {

dogType := graphql.NewObject(graphql.ObjectConfig{
Name: "Dog",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
_, ok := value.(*testDog)
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
_, ok := p.Value.(*testDog)
return ok
},
Fields: graphql.Fields{
Expand All @@ -177,8 +177,8 @@ func TestIsTypeOfUsedToResolveRuntimeTypeForUnion(t *testing.T) {
})
catType := graphql.NewObject(graphql.ObjectConfig{
Name: "Cat",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
_, ok := value.(*testCat)
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
_, ok := p.Value.(*testCat)
return ok
},
Fields: graphql.Fields{
Expand Down Expand Up @@ -270,14 +270,14 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) {
Type: graphql.String,
},
},
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
if _, ok := value.(*testCat); ok {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
if _, ok := p.Value.(*testCat); ok {
return catType
}
if _, ok := value.(*testDog); ok {
if _, ok := p.Value.(*testDog); ok {
return dogType
}
if _, ok := value.(*testHuman); ok {
if _, ok := p.Value.(*testHuman); ok {
return humanType
}
return nil
Expand Down Expand Up @@ -425,14 +425,14 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) {
Types: []*graphql.Object{
dogType, catType,
},
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
if _, ok := value.(*testCat); ok {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
if _, ok := p.Value.(*testCat); ok {
return catType
}
if _, ok := value.(*testDog); ok {
if _, ok := p.Value.(*testDog); ok {
return dogType
}
if _, ok := value.(*testHuman); ok {
if _, ok := p.Value.(*testHuman); ok {
return humanType
}
return nil
Expand Down
53 changes: 44 additions & 9 deletions definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,22 @@ type Object struct {
err error
}

type IsTypeOfFn func(value interface{}, info ResolveInfo) bool
// IsTypeOfParams Params for IsTypeOfFn()
type IsTypeOfParams struct {
// Value that needs to be resolve.
// Use this to decide which GraphQLObject this value maps to.
Value interface{}

// Info is a collection of information about the current execution state.
Info ResolveInfo

// Context argument is a context value that is provided to every resolve function within an execution.
// It is commonly
// used to represent an authenticated user, or request-specific caches.
Context context.Context
}

type IsTypeOfFn func(p IsTypeOfParams) bool

type InterfacesThunk func() []*Interface

Expand Down Expand Up @@ -560,14 +575,19 @@ func defineFieldMap(ttype Named, fields Fields) (FieldDefinitionMap, error) {
}

// ResolveParams Params for FieldResolveFn()
// TODO: clean up GQLFRParams fields
type ResolveParams struct {
// Source is the source value
Source interface{}
Args map[string]interface{}
Info ResolveInfo
Schema Schema
//This can be used to provide per-request state
//from the application.

// Args is a map of arguments for current GraphQL request
Args map[string]interface{}

// Info is a collection of information about the current execution state.
Info ResolveInfo

// Context argument is a context value that is provided to every resolve function within an execution.
// It is commonly
// used to represent an authenticated user, or request-specific caches.
Context context.Context
}

Expand Down Expand Up @@ -666,7 +686,7 @@ type Interface struct {

typeConfig InterfaceConfig
fields FieldDefinitionMap
err error
err error
}
type InterfaceConfig struct {
Name string `json:"name"`
Expand All @@ -675,7 +695,22 @@ type InterfaceConfig struct {
Description string `json:"description"`
}

type ResolveTypeFn func(value interface{}, info ResolveInfo) *Object
// ResolveTypeParams Params for ResolveTypeFn()
type ResolveTypeParams struct {
// Value that needs to be resolve.
// Use this to decide which GraphQLObject this value maps to.
Value interface{}

// Info is a collection of information about the current execution state.
Info ResolveInfo

// Context argument is a context value that is provided to every resolve function within an execution.
// It is commonly
// used to represent an authenticated user, or request-specific caches.
Context context.Context
}

type ResolveTypeFn func(p ResolveTypeParams) *Object

func NewInterface(config InterfaceConfig) *Interface {
it := &Interface{}
Expand Down
6 changes: 3 additions & 3 deletions definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ var blogSubscription = graphql.NewObject(graphql.ObjectConfig{

var objectType = graphql.NewObject(graphql.ObjectConfig{
Name: "Object",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
})
Expand Down Expand Up @@ -352,7 +352,7 @@ func TestTypeSystem_DefinitionExample_IncludesInterfacesSubTypesInTheTypeMap(t *
},
},
Interfaces: []*graphql.Interface{someInterface},
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
})
Expand Down Expand Up @@ -396,7 +396,7 @@ func TestTypeSystem_DefinitionExample_IncludesInterfacesThunkSubtypesInTheTypeMa
Interfaces: (graphql.InterfacesThunk)(func() []*graphql.Interface {
return []*graphql.Interface{someInterface}
}),
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
})
Expand Down
40 changes: 27 additions & 13 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,6 @@ func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}
// TODO: find a way to memoize, in case this field is within a List type.
args, _ := getArgumentValues(fieldDef.Args, fieldAST.Arguments, eCtx.VariableValues)

// The resolve function's optional third argument is a collection of
// information about the current execution state.
info := ResolveInfo{
FieldName: fieldName,
FieldASTs: fieldASTs,
Expand All @@ -545,7 +543,6 @@ func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}
result, resolveFnError = resolveFn(ResolveParams{
Source: source,
Args: args,
Schema: eCtx.Schema,
Info: info,
Context: eCtx.Context,
})
Expand Down Expand Up @@ -664,12 +661,17 @@ func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldAST

var runtimeType *Object

resolveTypeParams := ResolveTypeParams{
Value: result,
Info: info,
Context: eCtx.Context,
}
if unionReturnType, ok := returnType.(*Union); ok && unionReturnType.ResolveType != nil {
runtimeType = unionReturnType.ResolveType(result, info)
runtimeType = unionReturnType.ResolveType(resolveTypeParams)
} else if interfaceReturnType, ok := returnType.(*Interface); ok && interfaceReturnType.ResolveType != nil {
runtimeType = interfaceReturnType.ResolveType(result, info)
runtimeType = interfaceReturnType.ResolveType(resolveTypeParams)
} else {
runtimeType = defaultResolveTypeFn(result, info, returnType)
runtimeType = defaultResolveTypeFn(resolveTypeParams, returnType)
}

if runtimeType == nil {
Expand All @@ -692,10 +694,17 @@ func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs [
// If there is an isTypeOf predicate function, call it with the
// current result. If isTypeOf returns false, then raise an error rather
// than continuing execution.
if returnType.IsTypeOf != nil && !returnType.IsTypeOf(result, info) {
panic(gqlerrors.NewFormattedError(
fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, result),
))
if returnType.IsTypeOf != nil {
p := IsTypeOfParams{
Value: result,
Info: info,
Context: eCtx.Context,
}
if !returnType.IsTypeOf(p) {
panic(gqlerrors.NewFormattedError(
fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, result),
))
}
}

// Collect sub-fields to execute to complete this value.
Expand Down Expand Up @@ -767,13 +776,18 @@ func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*as
// defaultResolveTypeFn If a resolveType function is not given, then a default resolve behavior is
// used which tests each possible type for the abstract type by calling
// isTypeOf for the object being coerced, returning the first type that matches.
func defaultResolveTypeFn(value interface{}, info ResolveInfo, abstractType Abstract) *Object {
possibleTypes := info.Schema.PossibleTypes(abstractType)
func defaultResolveTypeFn(p ResolveTypeParams, abstractType Abstract) *Object {
possibleTypes := p.Info.Schema.PossibleTypes(abstractType)
for _, possibleType := range possibleTypes {
if possibleType.IsTypeOf == nil {
continue
}
if res := possibleType.IsTypeOf(value, info); res {
isTypeOfParams := IsTypeOfParams{
Value: p.Value,
Info: p.Info,
Context: p.Context,
}
if res := possibleType.IsTypeOf(isTypeOfParams); res {
return possibleType
}
}
Expand Down
4 changes: 2 additions & 2 deletions executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1261,8 +1261,8 @@ func TestFailsWhenAnIsTypeOfCheckIsNotMet(t *testing.T) {

specialType := graphql.NewObject(graphql.ObjectConfig{
Name: "SpecialType",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
if _, ok := value.(testSpecialType); ok {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
if _, ok := p.Value.(testSpecialType); ok {
return true
}
return false
Expand Down
4 changes: 2 additions & 2 deletions introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,9 @@ func init() {
Resolve: func(p ResolveParams) (interface{}, error) {
switch ttype := p.Source.(type) {
case *Interface:
return p.Schema.PossibleTypes(ttype), nil
return p.Info.Schema.PossibleTypes(ttype), nil
case *Union:
return p.Schema.PossibleTypes(ttype), nil
return p.Info.Schema.PossibleTypes(ttype), nil
}
return nil, nil
},
Expand Down
6 changes: 3 additions & 3 deletions rules_overlapping_fields_can_be_merged_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ var schema graphql.Schema
func init() {
someBoxInterface = graphql.NewInterface(graphql.InterfaceConfig{
Name: "SomeBox",
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
return stringBoxObject
},
Fields: graphql.Fields{
Expand Down Expand Up @@ -330,7 +330,7 @@ func init() {
})
var nonNullStringBox1Interface = graphql.NewInterface(graphql.InterfaceConfig{
Name: "NonNullStringBox1",
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
return stringBoxObject
},
Fields: graphql.Fields{
Expand All @@ -355,7 +355,7 @@ func init() {
})
var nonNullStringBox2Interface = graphql.NewInterface(graphql.InterfaceConfig{
Name: "NonNullStringBox2",
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
return stringBoxObject
},
Fields: graphql.Fields{
Expand Down
14 changes: 7 additions & 7 deletions testutil/rules_test_harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func init() {
})
var dogType = graphql.NewObject(graphql.ObjectConfig{
Name: "Dog",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
Fields: graphql.Fields{
Expand Down Expand Up @@ -146,7 +146,7 @@ func init() {

var catType = graphql.NewObject(graphql.ObjectConfig{
Name: "Cat",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
Fields: graphql.Fields{
Expand Down Expand Up @@ -182,7 +182,7 @@ func init() {
dogType,
catType,
},
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
// not used for validation
return nil
},
Expand All @@ -198,7 +198,7 @@ func init() {

var humanType = graphql.NewObject(graphql.ObjectConfig{
Name: "Human",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
Interfaces: []*graphql.Interface{
Expand Down Expand Up @@ -229,7 +229,7 @@ func init() {

var alienType = graphql.NewObject(graphql.ObjectConfig{
Name: "Alien",
IsTypeOf: func(value interface{}, info graphql.ResolveInfo) bool {
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return true
},
Interfaces: []*graphql.Interface{
Expand Down Expand Up @@ -259,7 +259,7 @@ func init() {
dogType,
humanType,
},
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
// not used for validation
return nil
},
Expand All @@ -270,7 +270,7 @@ func init() {
alienType,
humanType,
},
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
// not used for validation
return nil
},
Expand Down
4 changes: 2 additions & 2 deletions testutil/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ func init() {
Description: "Which movies they appear in.",
},
},
ResolveType: func(value interface{}, info graphql.ResolveInfo) *graphql.Object {
if character, ok := value.(StarWarsChar); ok {
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
if character, ok := p.Value.(StarWarsChar); ok {
id, _ := strconv.Atoi(character.ID)
human := GetHuman(id)
if human.ID != "" {
Expand Down
Loading

0 comments on commit 1e33c35

Please sign in to comment.