Skip to content

Split up the completeValue function #311

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

Merged
merged 5 commits into from
Mar 23, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 128 additions & 43 deletions src/execution/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import {
GraphQLEnumType,
GraphQLList,
GraphQLNonNull,
GraphQLInterfaceType,
GraphQLUnionType,
isAbstractType
} from '../type/definition';
import type {
GraphQLType,
GraphQLLeafType,
GraphQLAbstractType,
GraphQLFieldDefinition,
GraphQLResolveInfo,
Expand Down Expand Up @@ -636,6 +639,9 @@ function completeValueCatchingError(
* value of the type by calling the `serialize` method of GraphQL type
* definition.
*
* If the field is an abstract type, determine the runtime type of the value
* and then complete based on that type
*
* Otherwise, the field type expects a sub-selection set, and will complete the
* value by evaluating all sub-selections.
*/
Expand Down Expand Up @@ -694,64 +700,109 @@ function completeValue(

// If field type is List, complete each item in the list with the inner type
if (returnType instanceof GraphQLList) {
invariant(
Array.isArray(result),
`User Error: expected iterable, but did not find one for field ${
info.parentType}.${info.fieldName}.`
);

// This is specified as a simple map, however we're optimizing the path
// where the list contains no Promises by avoiding creating another Promise.
const itemType = returnType.ofType;
let containsPromise = false;
const completedResults = result.map(item => {
const completedItem =
completeValueCatchingError(exeContext, itemType, fieldASTs, info, item);
if (!containsPromise && isThenable(completedItem)) {
containsPromise = true;
}
return completedItem;
});

return containsPromise ? Promise.all(completedResults) : completedResults;
return completeListValue(exeContext, returnType, fieldASTs, info, result);
}

// If field type is Scalar or Enum, serialize to a valid value, returning
// null if serialization is not possible.
if (returnType instanceof GraphQLScalarType ||
returnType instanceof GraphQLEnumType) {
invariant(returnType.serialize, 'Missing serialize method on type');
const serializedResult = returnType.serialize(result);
return isNullish(serializedResult) ? null : serializedResult;
return completeLeafValue(exeContext, returnType, fieldASTs, info, result);
}

// Field type must be Object, Interface or Union and expect sub-selections.
let runtimeType: ?GraphQLObjectType;

if (returnType instanceof GraphQLObjectType) {
runtimeType = returnType;
} else if (isAbstractType(returnType)) {
const abstractType = ((returnType: any): GraphQLAbstractType);
runtimeType = abstractType.getObjectType(result, info);
if (runtimeType && !abstractType.isPossibleType(runtimeType)) {
throw new GraphQLError(
`Runtime Object type "${runtimeType}" is not a possible type ` +
`for "${abstractType}".`,
fieldASTs
);
}
return completeObjectValue(
exeContext,
returnType,
fieldASTs,
info,
result
);
}

if (!runtimeType) {
return null;
if (returnType instanceof GraphQLInterfaceType ||
returnType instanceof GraphQLUnionType) {
return completeAbstractValue(
exeContext,
returnType,
fieldASTs,
info,
result
);
}

// Not reachable
invariant(
false,
`Cannot complete value of unexpected type "${returnType}".`
);
}

/**
* Complete a list value by completing each item in the list with the
* inner type
*/
function completeListValue(
exeContext: ExecutionContext,
returnType: GraphQLList,
fieldASTs: Array<Field>,
info: GraphQLResolveInfo,
result: mixed
): mixed {
invariant(
Array.isArray(result),
`User Error: expected iterable, but did not find one for field ${
info.parentType}.${info.fieldName}.`
);

// This is specified as a simple map, however we're optimizing the path
// where the list contains no Promises by avoiding creating another Promise.
const itemType = returnType.ofType;
let containsPromise = false;
const completedResults = result.map(item => {
const completedItem =
completeValueCatchingError(exeContext, itemType, fieldASTs, info, item);
if (!containsPromise && isThenable(completedItem)) {
containsPromise = true;
}
return completedItem;
});

return containsPromise ? Promise.all(completedResults) : completedResults;
}

/**
* Complete a Scalar or Enum by serializing to a valid value, returning
* null if serialization is not possible.
*/
function completeLeafValue(
exeContext: ExecutionContext,
returnType: GraphQLLeafType,
fieldASTs: Array<Field>,
info: GraphQLResolveInfo,
result: mixed
): mixed {
invariant(returnType.serialize, 'Missing serialize method on type');
const serializedResult = returnType.serialize(result);
return isNullish(serializedResult) ? null : serializedResult;
}

/**
* Complete an Object value by evaluating all sub-selections.
*/
function completeObjectValue(
exeContext: ExecutionContext,
returnType: GraphQLObjectType,
fieldASTs: Array<Field>,
info: GraphQLResolveInfo,
result: mixed
): mixed {
// 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 (runtimeType.isTypeOf && !runtimeType.isTypeOf(result, info)) {
if (returnType.isTypeOf && !returnType.isTypeOf(result, info)) {
throw new GraphQLError(
`Expected value of type "${runtimeType}" but got: ${result}.`,
`Expected value of type "${returnType}" but got: ${result}.`,
fieldASTs
);
}
Expand All @@ -764,15 +815,49 @@ function completeValue(
if (selectionSet) {
subFieldASTs = collectFields(
exeContext,
runtimeType,
returnType,
selectionSet,
subFieldASTs,
visitedFragmentNames
);
}
}

return executeFields(exeContext, runtimeType, result, subFieldASTs);
return executeFields(exeContext, returnType, result, subFieldASTs);
}

/**
* Complete an value of an abstract type by determining the runtime type of
* that value, then completing based on that type.
*/
function completeAbstractValue(
exeContext: ExecutionContext,
returnType: GraphQLAbstractType,
fieldASTs: Array<Field>,
info: GraphQLResolveInfo,
result: mixed
): mixed {
const abstractType = ((returnType: any): GraphQLAbstractType);
const runtimeType = abstractType.getObjectType(result, info);
if (runtimeType && !abstractType.isPossibleType(runtimeType)) {
throw new GraphQLError(
`Runtime Object type "${runtimeType}" is not a possible type ` +
`for "${abstractType}".`,
fieldASTs
);
}

if (!runtimeType) {
return null;
}

return completeObjectValue(
exeContext,
runtimeType,
fieldASTs,
info,
result
);
}

/**
Expand Down