mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-18 05:13:13 +03:00
Merge pull request #67 from mcg-web/promise
Initial implementation of promises (starting addressing N+1 problem)
This commit is contained in:
commit
f3fca81e9d
@ -13,7 +13,8 @@
|
|||||||
"ext-mbstring": "*"
|
"ext-mbstring": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8"
|
"phpunit/phpunit": "^4.8",
|
||||||
|
"react/promise": "^2.4"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bin-dir": "bin"
|
"bin-dir": "bin"
|
||||||
@ -30,5 +31,8 @@
|
|||||||
"GraphQL\\Benchmarks\\": "benchmarks/",
|
"GraphQL\\Benchmarks\\": "benchmarks/",
|
||||||
"GraphQL\\Examples\\Blog\\": "examples/01-blog/Blog/"
|
"GraphQL\\Examples\\Blog\\": "examples/01-blog/Blog/"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"react/promise": "To use ReactPhp promise adapter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,15 @@ namespace GraphQL\Executor;
|
|||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Error\InvariantViolation;
|
use GraphQL\Error\InvariantViolation;
|
||||||
|
use GraphQL\Executor\Promise\Promise;
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\AST\FieldNode;
|
use GraphQL\Language\AST\FieldNode;
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\AST\SelectionSetNode;
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
|
use GraphQL\Executor\Promise\Adapter\GenericPromiseAdapter;
|
||||||
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\AbstractType;
|
use GraphQL\Type\Definition\AbstractType;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
@ -48,6 +50,19 @@ class Executor
|
|||||||
|
|
||||||
private static $defaultFieldResolver = [__CLASS__, 'defaultFieldResolver'];
|
private static $defaultFieldResolver = [__CLASS__, 'defaultFieldResolver'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PromiseAdapter
|
||||||
|
*/
|
||||||
|
private static $promiseAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PromiseAdapter|null $promiseAdapter
|
||||||
|
*/
|
||||||
|
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
|
||||||
|
{
|
||||||
|
self::$promiseAdapter = $promiseAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom default resolve function
|
* Custom default resolve function
|
||||||
*
|
*
|
||||||
@ -66,7 +81,7 @@ class Executor
|
|||||||
* @param $contextValue
|
* @param $contextValue
|
||||||
* @param array|\ArrayAccess $variableValues
|
* @param array|\ArrayAccess $variableValues
|
||||||
* @param null $operationName
|
* @param null $operationName
|
||||||
* @return ExecutionResult
|
* @return ExecutionResult|Promise
|
||||||
*/
|
*/
|
||||||
public static function execute(Schema $schema, DocumentNode $ast, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null)
|
public static function execute(Schema $schema, DocumentNode $ast, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null)
|
||||||
{
|
{
|
||||||
@ -88,9 +103,34 @@ class Executor
|
|||||||
}
|
}
|
||||||
|
|
||||||
$exeContext = self::buildExecutionContext($schema, $ast, $rootValue, $contextValue, $variableValues, $operationName);
|
$exeContext = self::buildExecutionContext($schema, $ast, $rootValue, $contextValue, $variableValues, $operationName);
|
||||||
|
if (null === self::$promiseAdapter) {
|
||||||
|
static::setPromiseAdapter(new GenericPromiseAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$data = self::executeOperation($exeContext, $exeContext->operation, $rootValue);
|
$data = self::$promiseAdapter
|
||||||
|
->createPromise(function (callable $resolve) use ($exeContext, $rootValue) {
|
||||||
|
return $resolve(self::executeOperation($exeContext, $exeContext->operation, $rootValue));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (self::$promiseAdapter->isPromise($data)) {
|
||||||
|
// Return a Promise that will eventually resolve to the data described by
|
||||||
|
// The "Response" section of the GraphQL specification.
|
||||||
|
//
|
||||||
|
// If errors are encountered while executing a GraphQL field, only that
|
||||||
|
// field and its descendants will be omitted, and sibling fields will still
|
||||||
|
// be executed. An execution which encounters errors will still result in a
|
||||||
|
// resolved Promise.
|
||||||
|
return $data->then(null, function ($error) use ($exeContext) {
|
||||||
|
// Errors from sub-fields of a NonNull type may propagate to the top level,
|
||||||
|
// at which point we still log the error and null the parent field, which
|
||||||
|
// in this case is the entire response.
|
||||||
|
$exeContext->addError($error);
|
||||||
|
return null;
|
||||||
|
})->then(function ($data) use ($exeContext) {
|
||||||
|
return new ExecutionResult((array) $data, $exeContext->errors);
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (Error $e) {
|
} catch (Error $e) {
|
||||||
$exeContext->addError($e);
|
$exeContext->addError($e);
|
||||||
$data = null;
|
$data = null;
|
||||||
@ -102,6 +142,15 @@ class Executor
|
|||||||
/**
|
/**
|
||||||
* Constructs a ExecutionContext object from the arguments passed to
|
* Constructs a ExecutionContext object from the arguments passed to
|
||||||
* execute, which we will pass throughout the other execution methods.
|
* execute, which we will pass throughout the other execution methods.
|
||||||
|
*
|
||||||
|
* @param Schema $schema
|
||||||
|
* @param DocumentNode $documentNode
|
||||||
|
* @param $rootValue
|
||||||
|
* @param $contextValue
|
||||||
|
* @param $rawVariableValues
|
||||||
|
* @param string $operationName
|
||||||
|
* @return ExecutionContext
|
||||||
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
private static function buildExecutionContext(
|
private static function buildExecutionContext(
|
||||||
Schema $schema,
|
Schema $schema,
|
||||||
@ -160,6 +209,11 @@ class Executor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the "Evaluating operations" section of the spec.
|
* Implements the "Evaluating operations" section of the spec.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param OperationDefinitionNode $operation
|
||||||
|
* @param $rootValue
|
||||||
|
* @return Promise|\stdClass|array
|
||||||
*/
|
*/
|
||||||
private static function executeOperation(ExecutionContext $exeContext, OperationDefinitionNode $operation, $rootValue)
|
private static function executeOperation(ExecutionContext $exeContext, OperationDefinitionNode $operation, $rootValue)
|
||||||
{
|
{
|
||||||
@ -217,38 +271,111 @@ class Executor
|
|||||||
/**
|
/**
|
||||||
* Implements the "Evaluating selection sets" section of the spec
|
* Implements the "Evaluating selection sets" section of the spec
|
||||||
* for "write" mode.
|
* for "write" mode.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param ObjectType $parentType
|
||||||
|
* @param $sourceValue
|
||||||
|
* @param $path
|
||||||
|
* @param $fields
|
||||||
|
* @return Promise|\stdClass|array
|
||||||
*/
|
*/
|
||||||
private static function executeFieldsSerially(ExecutionContext $exeContext, ObjectType $parentType, $sourceValue, $path, $fields)
|
private static function executeFieldsSerially(ExecutionContext $exeContext, ObjectType $parentType, $sourceValue, $path, $fields)
|
||||||
{
|
{
|
||||||
$results = [];
|
$results = self::$promiseAdapter->createResolvedPromise([]);
|
||||||
foreach ($fields as $responseName => $fieldNodes) {
|
|
||||||
|
$process = function ($results, $responseName, $path, $exeContext, $parentType, $sourceValue, $fieldNodes) {
|
||||||
$fieldPath = $path;
|
$fieldPath = $path;
|
||||||
$fieldPath[] = $responseName;
|
$fieldPath[] = $responseName;
|
||||||
$result = self::resolveField($exeContext, $parentType, $sourceValue, $fieldNodes, $fieldPath);
|
$result = self::resolveField($exeContext, $parentType, $sourceValue, $fieldNodes, $fieldPath);
|
||||||
|
if ($result === self::$UNDEFINED) {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
if (self::$promiseAdapter->isPromise($result)) {
|
||||||
|
return $result->then(function ($resolvedResult) use ($responseName, $results) {
|
||||||
|
$results[$responseName] = $resolvedResult;
|
||||||
|
return $results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$results[$responseName] = $result;
|
||||||
|
return $results;
|
||||||
|
};
|
||||||
|
|
||||||
if ($result !== self::$UNDEFINED) {
|
foreach ($fields as $responseName => $fieldNodes) {
|
||||||
// Undefined means that field is not defined in schema
|
if (self::$promiseAdapter->isPromise($results)) {
|
||||||
$results[$responseName] = $result;
|
$results = $results->then(function ($resolvedResults) use ($process, $responseName, $path, $exeContext, $parentType, $sourceValue, $fieldNodes) {
|
||||||
|
return $process($resolvedResults, $responseName, $path, $exeContext, $parentType, $sourceValue, $fieldNodes);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$results = $process($results, $responseName, $path, $exeContext, $parentType, $sourceValue, $fieldNodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// see #59
|
|
||||||
if ([] === $results) {
|
if (self::$promiseAdapter->isPromise($results)) {
|
||||||
$results = new \stdClass();
|
return $results->then(function ($resolvedResults) {
|
||||||
|
return self::fixResultsIfEmptyArray($resolvedResults);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return $results;
|
|
||||||
|
return self::fixResultsIfEmptyArray($results);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the "Evaluating selection sets" section of the spec
|
* Implements the "Evaluating selection sets" section of the spec
|
||||||
* for "read" mode.
|
* for "read" mode.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param ObjectType $parentType
|
||||||
|
* @param $source
|
||||||
|
* @param $path
|
||||||
|
* @param $fields
|
||||||
|
* @return Promise|\stdClass|array
|
||||||
*/
|
*/
|
||||||
private static function executeFields(ExecutionContext $exeContext, ObjectType $parentType, $source, $path, $fields)
|
private static function executeFields(ExecutionContext $exeContext, ObjectType $parentType, $source, $path, $fields)
|
||||||
{
|
{
|
||||||
// Native PHP doesn't support promises.
|
$containsPromise = false;
|
||||||
// Custom executor should be built for platforms like ReactPHP
|
$finalResults = [];
|
||||||
return self::executeFieldsSerially($exeContext, $parentType, $source, $path, $fields);
|
|
||||||
|
foreach ($fields as $responseName => $fieldNodes) {
|
||||||
|
$fieldPath = $path;
|
||||||
|
$fieldPath[] = $responseName;
|
||||||
|
$result = self::resolveField($exeContext, $parentType, $source, $fieldNodes, $fieldPath);
|
||||||
|
if ($result === self::$UNDEFINED) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$containsPromise && self::$promiseAdapter->isPromise($result)) {
|
||||||
|
$containsPromise = true;
|
||||||
|
}
|
||||||
|
$finalResults[$responseName] = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no promises, we can just return the object
|
||||||
|
if (!$containsPromise) {
|
||||||
|
return self::fixResultsIfEmptyArray($finalResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, results is a map from field name to the result
|
||||||
|
// of resolving that field, which is possibly a promise. Return
|
||||||
|
// a promise that will return this same map, but with any
|
||||||
|
// promises replaced with the values they resolved to.
|
||||||
|
return self::$promiseAdapter->createPromiseAll($finalResults)->then(function ($resolvedResults) {
|
||||||
|
return self::fixResultsIfEmptyArray($resolvedResults);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/webonyx/graphql-php/issues/59
|
||||||
|
*
|
||||||
|
* @param $results
|
||||||
|
* @return \stdClass|array
|
||||||
|
*/
|
||||||
|
private static function fixResultsIfEmptyArray($results)
|
||||||
|
{
|
||||||
|
if ([] === $results) {
|
||||||
|
$results = new \stdClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a selectionSet, adds all of the fields in that selection to
|
* Given a selectionSet, adds all of the fields in that selection to
|
||||||
@ -258,6 +385,12 @@ class Executor
|
|||||||
* returns and Interface or Union type, the "runtime type" will be the actual
|
* returns and Interface or Union type, the "runtime type" will be the actual
|
||||||
* Object type returned by that field.
|
* Object type returned by that field.
|
||||||
*
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param ObjectType $runtimeType
|
||||||
|
* @param SelectionSetNode $selectionSet
|
||||||
|
* @param $fields
|
||||||
|
* @param $visitedFragmentNames
|
||||||
|
*
|
||||||
* @return \ArrayObject
|
* @return \ArrayObject
|
||||||
*/
|
*/
|
||||||
private static function collectFields(
|
private static function collectFields(
|
||||||
@ -322,6 +455,10 @@ class Executor
|
|||||||
/**
|
/**
|
||||||
* Determines if a field should be included based on the @include and @skip
|
* Determines if a field should be included based on the @include and @skip
|
||||||
* directives, where @skip has higher precedence than @include.
|
* directives, where @skip has higher precedence than @include.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param $directives
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private static function shouldIncludeNode(ExecutionContext $exeContext, $directives)
|
private static function shouldIncludeNode(ExecutionContext $exeContext, $directives)
|
||||||
{
|
{
|
||||||
@ -361,6 +498,11 @@ class Executor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a fragment is applicable to the given type.
|
* Determines if a fragment is applicable to the given type.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param $fragment
|
||||||
|
* @param ObjectType $type
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private static function doesFragmentConditionMatch(ExecutionContext $exeContext,/* FragmentDefinitionNode | InlineFragmentNode*/ $fragment, ObjectType $type)
|
private static function doesFragmentConditionMatch(ExecutionContext $exeContext,/* FragmentDefinitionNode | InlineFragmentNode*/ $fragment, ObjectType $type)
|
||||||
{
|
{
|
||||||
@ -382,6 +524,9 @@ class Executor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the logic to compute the key of a given fields entry
|
* Implements the logic to compute the key of a given fields entry
|
||||||
|
*
|
||||||
|
* @param FieldNode $node
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
private static function getFieldEntryKey(FieldNode $node)
|
private static function getFieldEntryKey(FieldNode $node)
|
||||||
{
|
{
|
||||||
@ -393,6 +538,14 @@ class Executor
|
|||||||
* figures out the value that the field returns by calling its resolve function,
|
* figures out the value that the field returns by calling its resolve function,
|
||||||
* then calls completeValue to complete promises, serialize scalars, or execute
|
* then calls completeValue to complete promises, serialize scalars, or execute
|
||||||
* the sub-selection-set for objects.
|
* the sub-selection-set for objects.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param ObjectType $parentType
|
||||||
|
* @param $source
|
||||||
|
* @param $fieldNodes
|
||||||
|
* @param $path
|
||||||
|
*
|
||||||
|
* @return array|\Exception|mixed|null
|
||||||
*/
|
*/
|
||||||
private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldNodes, $path)
|
private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldNodes, $path)
|
||||||
{
|
{
|
||||||
@ -501,7 +654,7 @@ class Executor
|
|||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param $path
|
* @param $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array|null
|
* @return array|null|Promise
|
||||||
*/
|
*/
|
||||||
private static function completeValueCatchingError(
|
private static function completeValueCatchingError(
|
||||||
ExecutionContext $exeContext,
|
ExecutionContext $exeContext,
|
||||||
@ -528,7 +681,7 @@ class Executor
|
|||||||
// Otherwise, error protection is applied, logging the error and resolving
|
// Otherwise, error protection is applied, logging the error and resolving
|
||||||
// a null value for this field if one is encountered.
|
// a null value for this field if one is encountered.
|
||||||
try {
|
try {
|
||||||
return self::completeValueWithLocatedError(
|
$completed = self::completeValueWithLocatedError(
|
||||||
$exeContext,
|
$exeContext,
|
||||||
$returnType,
|
$returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
@ -536,6 +689,13 @@ class Executor
|
|||||||
$path,
|
$path,
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
|
if (self::$promiseAdapter->isPromise($completed)) {
|
||||||
|
return $completed->then(null, function ($error) use ($exeContext) {
|
||||||
|
$exeContext->addError($error);
|
||||||
|
return self::$promiseAdapter->createResolvedPromise(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $completed;
|
||||||
} catch (Error $err) {
|
} catch (Error $err) {
|
||||||
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
|
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
|
||||||
// and return null.
|
// and return null.
|
||||||
@ -555,10 +715,10 @@ class Executor
|
|||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param $path
|
* @param $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array|null
|
* @return array|null|Promise
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
static function completeValueWithLocatedError(
|
public static function completeValueWithLocatedError(
|
||||||
ExecutionContext $exeContext,
|
ExecutionContext $exeContext,
|
||||||
Type $returnType,
|
Type $returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
@ -568,7 +728,7 @@ class Executor
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return self::completeValue(
|
$completed = self::completeValue(
|
||||||
$exeContext,
|
$exeContext,
|
||||||
$returnType,
|
$returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
@ -576,6 +736,12 @@ class Executor
|
|||||||
$path,
|
$path,
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
|
if (self::$promiseAdapter->isPromise($completed)) {
|
||||||
|
return $completed->then(null, function ($error) use ($fieldNodes, $path) {
|
||||||
|
return self::$promiseAdapter->createRejectedPromise(Error::createLocatedError($error, $fieldNodes, $path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $completed;
|
||||||
} catch (\Exception $error) {
|
} catch (\Exception $error) {
|
||||||
throw Error::createLocatedError($error, $fieldNodes, $path);
|
throw Error::createLocatedError($error, $fieldNodes, $path);
|
||||||
}
|
}
|
||||||
@ -608,7 +774,7 @@ class Executor
|
|||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param array $path
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array|null
|
* @return array|null|Promise
|
||||||
* @throws Error
|
* @throws Error
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
@ -621,6 +787,13 @@ class Executor
|
|||||||
&$result
|
&$result
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
// If result is a Promise, apply-lift over completeValue.
|
||||||
|
if (self::$promiseAdapter->isPromise($result)) {
|
||||||
|
return $result->then(function (&$resolved) use ($exeContext, $returnType, $fieldNodes, $info, $path) {
|
||||||
|
return self::completeValue($exeContext, $returnType, $fieldNodes, $info, $path, $resolved);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ($result instanceof \Exception) {
|
if ($result instanceof \Exception) {
|
||||||
throw $result;
|
throw $result;
|
||||||
}
|
}
|
||||||
@ -677,6 +850,13 @@ class Executor
|
|||||||
* which takes the property of the source object of the same name as the field
|
* which takes the property of the source object of the same name as the field
|
||||||
* and returns it as the result, or if it's a function, returns the result
|
* and returns it as the result, or if it's a function, returns the result
|
||||||
* of calling that function while passing along args and context.
|
* of calling that function while passing along args and context.
|
||||||
|
*
|
||||||
|
* @param $source
|
||||||
|
* @param $args
|
||||||
|
* @param $context
|
||||||
|
* @param ResolveInfo $info
|
||||||
|
*
|
||||||
|
* @return mixed|null
|
||||||
*/
|
*/
|
||||||
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
|
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
|
||||||
{
|
{
|
||||||
@ -705,6 +885,10 @@ class Executor
|
|||||||
* added to the query type, but that would require mutating type
|
* added to the query type, but that would require mutating type
|
||||||
* definitions, which would cause issues.
|
* definitions, which would cause issues.
|
||||||
*
|
*
|
||||||
|
* @param Schema $schema
|
||||||
|
* @param ObjectType $parentType
|
||||||
|
* @param $fieldName
|
||||||
|
*
|
||||||
* @return FieldDefinition
|
* @return FieldDefinition
|
||||||
*/
|
*/
|
||||||
private static function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName)
|
private static function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName)
|
||||||
@ -781,7 +965,7 @@ class Executor
|
|||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param array $path
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array
|
* @return array|Promise
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private static function completeListValue(ExecutionContext $exeContext, ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
private static function completeListValue(ExecutionContext $exeContext, ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
||||||
@ -791,15 +975,20 @@ class Executor
|
|||||||
is_array($result) || $result instanceof \Traversable,
|
is_array($result) || $result instanceof \Traversable,
|
||||||
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
|
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
|
||||||
);
|
);
|
||||||
|
$containsPromise = false;
|
||||||
|
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$tmp = [];
|
$completedItems = [];
|
||||||
foreach ($result as $item) {
|
foreach ($result as $item) {
|
||||||
$fieldPath = $path;
|
$fieldPath = $path;
|
||||||
$fieldPath[] = $i++;
|
$fieldPath[] = $i++;
|
||||||
$tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldNodes, $info, $fieldPath, $item);
|
$completedItem = self::completeValueCatchingError($exeContext, $itemType, $fieldNodes, $info, $fieldPath, $item);
|
||||||
|
if (!$containsPromise && self::$promiseAdapter->isPromise($completedItem)) {
|
||||||
|
$containsPromise = true;
|
||||||
|
}
|
||||||
|
$completedItems[] = $completedItem;
|
||||||
}
|
}
|
||||||
return $tmp;
|
return $containsPromise ? self::$promiseAdapter->createPromiseAll($completedItems) : $completedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -832,7 +1021,7 @@ class Executor
|
|||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param array $path
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array
|
* @return array|Promise|\stdClass
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
private static function completeObjectValue(ExecutionContext $exeContext, ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
private static function completeObjectValue(ExecutionContext $exeContext, ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
||||||
@ -888,7 +1077,13 @@ class Executor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated as of 8.0
|
* @deprecated as of v0.8.0 should use self::defaultFieldResolver method
|
||||||
|
*
|
||||||
|
* @param $source
|
||||||
|
* @param $args
|
||||||
|
* @param $context
|
||||||
|
* @param ResolveInfo $info
|
||||||
|
* @return mixed|null
|
||||||
*/
|
*/
|
||||||
public static function defaultResolveFn($source, $args, $context, ResolveInfo $info)
|
public static function defaultResolveFn($source, $args, $context, ResolveInfo $info)
|
||||||
{
|
{
|
||||||
@ -897,7 +1092,9 @@ class Executor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated as of 8.0
|
* @deprecated as of v0.8.0 should use self::setDefaultFieldResolver method
|
||||||
|
*
|
||||||
|
* @param callable $fn
|
||||||
*/
|
*/
|
||||||
public static function setDefaultResolveFn($fn)
|
public static function setDefaultResolveFn($fn)
|
||||||
{
|
{
|
||||||
|
33
src/Executor/Promise/Adapter/GenericPromiseAdapter.php
Normal file
33
src/Executor/Promise/Adapter/GenericPromiseAdapter.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GraphQL\Executor\Promise\Adapter;
|
||||||
|
|
||||||
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
|
|
||||||
|
class GenericPromiseAdapter implements PromiseAdapter
|
||||||
|
{
|
||||||
|
public function isPromise($value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPromise(callable $resolver)
|
||||||
|
{
|
||||||
|
return $resolver(function ($value) {
|
||||||
|
return $value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createResolvedPromise($promiseOrValue = null)
|
||||||
|
{
|
||||||
|
return $promiseOrValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createRejectedPromise($reason)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPromiseAll($promisesOrValues)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
68
src/Executor/Promise/Adapter/ReactPromiseAdapter.php
Normal file
68
src/Executor/Promise/Adapter/ReactPromiseAdapter.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GraphQL\Executor\Promise\Adapter;
|
||||||
|
|
||||||
|
use GraphQL\Executor\Promise\PromiseAdapter;;
|
||||||
|
use React\Promise\FulfilledPromise;
|
||||||
|
use React\Promise\Promise;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
class ReactPromiseAdapter implements PromiseAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return true if value is promise
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isPromise($value)
|
||||||
|
{
|
||||||
|
return $value instanceof PromiseInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @return PromiseInterface
|
||||||
|
*/
|
||||||
|
public function createPromise(callable $resolver)
|
||||||
|
{
|
||||||
|
$promise = new Promise($resolver);
|
||||||
|
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @return FulfilledPromise
|
||||||
|
*/
|
||||||
|
public function createResolvedPromise($promiseOrValue = null)
|
||||||
|
{
|
||||||
|
return \React\Promise\resolve($promiseOrValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @return \React\Promise\RejectedPromise
|
||||||
|
*/
|
||||||
|
public function createRejectedPromise($reason)
|
||||||
|
{
|
||||||
|
return \React\Promise\reject($reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of promises, return a promise that is fulfilled when all the
|
||||||
|
* items in the array are fulfilled.
|
||||||
|
*
|
||||||
|
* @param mixed $promisesOrValues Promises or values.
|
||||||
|
*
|
||||||
|
* @return mixed a Promise
|
||||||
|
*/
|
||||||
|
public function createPromiseAll($promisesOrValues)
|
||||||
|
{
|
||||||
|
return \React\Promise\all($promisesOrValues);
|
||||||
|
}
|
||||||
|
}
|
18
src/Executor/Promise/Promise.php
Normal file
18
src/Executor/Promise/Promise.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GraphQL\Executor\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple Promise representation
|
||||||
|
* this interface helps to document the code
|
||||||
|
*/
|
||||||
|
interface Promise
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param callable|null $onFullFilled
|
||||||
|
* @param callable|null $onRejected
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
public function then(callable $onFullFilled = null, callable $onRejected = null);
|
||||||
|
}
|
53
src/Executor/Promise/PromiseAdapter.php
Normal file
53
src/Executor/Promise/PromiseAdapter.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GraphQL\Executor\Promise;
|
||||||
|
|
||||||
|
interface PromiseAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return true if value is promise
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isPromise($value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Promise
|
||||||
|
*
|
||||||
|
* @param callable $resolver
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
public function createPromise(callable $resolver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a full filed Promise for a value if the value is not a promise.
|
||||||
|
*
|
||||||
|
* @param mixed $promiseOrValue
|
||||||
|
*
|
||||||
|
* @return Promise a full filed Promise
|
||||||
|
*/
|
||||||
|
public function createResolvedPromise($promiseOrValue = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a rejected promise for a reason if the reason is not a promise. If
|
||||||
|
* the provided reason is a promise, then it is returned as-is.
|
||||||
|
*
|
||||||
|
* @param mixed $reason
|
||||||
|
*
|
||||||
|
* @return Promise a rejected promise
|
||||||
|
*/
|
||||||
|
public function createRejectedPromise($reason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of promises, return a promise that is fulfilled when all the
|
||||||
|
* items in the array are fulfilled.
|
||||||
|
*
|
||||||
|
* @param mixed $promisesOrValues Promises or values.
|
||||||
|
*
|
||||||
|
* @return Promise equivalent to Promise.all result
|
||||||
|
*/
|
||||||
|
public function createPromiseAll($promisesOrValues);
|
||||||
|
}
|
@ -4,9 +4,11 @@ namespace GraphQL;
|
|||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Executor\ExecutionResult;
|
use GraphQL\Executor\ExecutionResult;
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
|
use GraphQL\Executor\Promise\Promise;
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
@ -19,11 +21,13 @@ class GraphQL
|
|||||||
* @param mixed $rootValue
|
* @param mixed $rootValue
|
||||||
* @param array <string, string>|null $variableValues
|
* @param array <string, string>|null $variableValues
|
||||||
* @param string|null $operationName
|
* @param string|null $operationName
|
||||||
* @return array
|
* @return Promise|array
|
||||||
*/
|
*/
|
||||||
public static function execute(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null)
|
public static function execute(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null)
|
||||||
{
|
{
|
||||||
return self::executeAndReturnResult($schema, $requestString, $rootValue, $contextValue, $variableValues, $operationName)->toArray();
|
$result = self::executeAndReturnResult($schema, $requestString, $rootValue, $contextValue, $variableValues, $operationName);
|
||||||
|
|
||||||
|
return $result instanceof ExecutionResult ? $result->toArray() : $result->then(function(ExecutionResult $executionResult) { return $executionResult->toArray(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +36,7 @@ class GraphQL
|
|||||||
* @param null $rootValue
|
* @param null $rootValue
|
||||||
* @param null $variableValues
|
* @param null $variableValues
|
||||||
* @param null $operationName
|
* @param null $operationName
|
||||||
* @return array|ExecutionResult
|
* @return ExecutionResult|Promise
|
||||||
*/
|
*/
|
||||||
public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null)
|
public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null)
|
||||||
{
|
{
|
||||||
@ -67,4 +71,12 @@ class GraphQL
|
|||||||
{
|
{
|
||||||
return array_values(Directive::getInternalDirectives());
|
return array_values(Directive::getInternalDirectives());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PromiseAdapter|null $promiseAdapter
|
||||||
|
*/
|
||||||
|
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter($promiseAdapter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@ namespace GraphQL\Tests\Executor;
|
|||||||
require_once __DIR__ . '/TestClasses.php';
|
require_once __DIR__ . '/TestClasses.php';
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Executor\ExecutionResult;
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Error\FormattedError;
|
use GraphQL\Error\FormattedError;
|
||||||
|
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
@ -16,9 +18,15 @@ use GraphQL\Type\Definition\ResolveInfo;
|
|||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
use GraphQL\Utils;
|
use GraphQL\Utils;
|
||||||
|
use React\Promise\Promise;
|
||||||
|
|
||||||
class ExecutorTest extends \PHPUnit_Framework_TestCase
|
class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute: Handles basic execution tasks
|
// Execute: Handles basic execution tasks
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +34,16 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
*/
|
*/
|
||||||
public function testExecutesArbitraryCode()
|
public function testExecutesArbitraryCode()
|
||||||
{
|
{
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
$deepData = null;
|
$deepData = null;
|
||||||
|
$data = null;
|
||||||
|
|
||||||
|
$promiseData = function () use (&$data) {
|
||||||
|
return new Promise(function (callable $resolve) use (&$data) {
|
||||||
|
return $resolve($data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'a' => function () { return 'Apple';},
|
'a' => function () { return 'Apple';},
|
||||||
'b' => function () {return 'Banana';},
|
'b' => function () {return 'Banana';},
|
||||||
@ -37,8 +54,8 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
'pic' => function ($size = 50) {
|
'pic' => function ($size = 50) {
|
||||||
return 'Pic of size: ' . $size;
|
return 'Pic of size: ' . $size;
|
||||||
},
|
},
|
||||||
'promise' => function() use (&$data) {
|
'promise' => function() use ($promiseData) {
|
||||||
return $data;
|
return $promiseData();
|
||||||
},
|
},
|
||||||
'deep' => function () use (&$deepData) {
|
'deep' => function () use (&$deepData) {
|
||||||
return $deepData;
|
return $deepData;
|
||||||
@ -51,7 +68,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
'c' => function () {
|
'c' => function () {
|
||||||
return ['Contrived', null, 'Confusing'];
|
return ['Contrived', null, 'Confusing'];
|
||||||
},
|
},
|
||||||
'deeper' => function () use ($data) {
|
'deeper' => function () use (&$data) {
|
||||||
return [$data, null, $data];
|
return [$data, null, $data];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -148,7 +165,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
]);
|
]);
|
||||||
$schema = new Schema(['query' => $dataType]);
|
$schema = new Schema(['query' => $dataType]);
|
||||||
|
|
||||||
$this->assertEquals($expected, Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray());
|
$this->assertEquals($expected, self::awaitPromise(Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,12 +359,18 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
public function testNullsOutErrorSubtrees()
|
public function testNullsOutErrorSubtrees()
|
||||||
{
|
{
|
||||||
$doc = '{
|
$doc = '{
|
||||||
sync,
|
sync
|
||||||
syncError,
|
syncError
|
||||||
syncRawError,
|
syncRawError
|
||||||
async,
|
syncReturnError
|
||||||
asyncReject,
|
syncReturnErrorList
|
||||||
|
async
|
||||||
|
asyncReject
|
||||||
|
asyncRawReject
|
||||||
|
asyncEmptyReject
|
||||||
asyncError
|
asyncError
|
||||||
|
asyncRawError
|
||||||
|
asyncReturnError
|
||||||
}';
|
}';
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
@ -360,17 +383,42 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
'syncRawError' => function() {
|
'syncRawError' => function() {
|
||||||
throw new \Exception('Error getting syncRawError');
|
throw new \Exception('Error getting syncRawError');
|
||||||
},
|
},
|
||||||
// Following are inherited from JS reference implementation, but make no sense in this PHP impl
|
// inherited from JS reference implementation, but make no sense in this PHP impl
|
||||||
// leaving them just to simplify migrations from newer js versions
|
// leaving it just to simplify migrations from newer js versions
|
||||||
|
'syncReturnError' => function() {
|
||||||
|
return new \Exception('Error getting syncReturnError');
|
||||||
|
},
|
||||||
|
'syncReturnErrorList' => function () {
|
||||||
|
return [
|
||||||
|
'sync0',
|
||||||
|
new \Exception('Error getting syncReturnErrorList1'),
|
||||||
|
'sync2',
|
||||||
|
new \Exception('Error getting syncReturnErrorList3')
|
||||||
|
];
|
||||||
|
},
|
||||||
'async' => function() {
|
'async' => function() {
|
||||||
return 'async';
|
return new Promise(function(callable $resolve) { return $resolve('async'); });
|
||||||
},
|
},
|
||||||
'asyncReject' => function() {
|
'asyncReject' => function() {
|
||||||
throw new \Exception('Error getting asyncReject');
|
return new Promise(function($_, callable $reject) { return $reject('Error getting asyncReject'); });
|
||||||
|
},
|
||||||
|
'asyncRawReject' => function () {
|
||||||
|
return \React\Promise\reject('Error getting asyncRawReject');
|
||||||
|
},
|
||||||
|
'asyncEmptyReject' => function () {
|
||||||
|
return \React\Promise\reject();
|
||||||
},
|
},
|
||||||
'asyncError' => function() {
|
'asyncError' => function() {
|
||||||
throw new \Exception('Error getting asyncError');
|
return new Promise(function() { throw new \Exception('Error getting asyncError'); });
|
||||||
}
|
},
|
||||||
|
// inherited from JS reference implementation, but make no sense in this PHP impl
|
||||||
|
// leaving it just to simplify migrations from newer js versions
|
||||||
|
'asyncRawError' => function() {
|
||||||
|
return new Promise(function() { throw new \Exception('Error getting asyncRawError'); });
|
||||||
|
},
|
||||||
|
'asyncReturnError' => function() {
|
||||||
|
return \React\Promise\resolve(new \Exception('Error getting asyncReturnError'));
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
$docAst = Parser::parse($doc);
|
$docAst = Parser::parse($doc);
|
||||||
@ -380,10 +428,16 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
'fields' => [
|
'fields' => [
|
||||||
'sync' => ['type' => Type::string()],
|
'sync' => ['type' => Type::string()],
|
||||||
'syncError' => ['type' => Type::string()],
|
'syncError' => ['type' => Type::string()],
|
||||||
'syncRawError' => [ 'type' => Type::string() ],
|
'syncRawError' => ['type' => Type::string()],
|
||||||
|
'syncReturnError' => ['type' => Type::string()],
|
||||||
|
'syncReturnErrorList' => ['type' => Type::listOf(Type::string())],
|
||||||
'async' => ['type' => Type::string()],
|
'async' => ['type' => Type::string()],
|
||||||
'asyncReject' => ['type' => Type::string() ],
|
'asyncReject' => ['type' => Type::string() ],
|
||||||
|
'asyncRawReject' => ['type' => Type::string() ],
|
||||||
|
'asyncEmptyReject' => ['type' => Type::string() ],
|
||||||
'asyncError' => ['type' => Type::string()],
|
'asyncError' => ['type' => Type::string()],
|
||||||
|
'asyncRawError' => ['type' => Type::string()],
|
||||||
|
'asyncReturnError' => ['type' => Type::string()],
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
@ -393,21 +447,79 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
'sync' => 'sync',
|
'sync' => 'sync',
|
||||||
'syncError' => null,
|
'syncError' => null,
|
||||||
'syncRawError' => null,
|
'syncRawError' => null,
|
||||||
|
'syncReturnError' => null,
|
||||||
|
'syncReturnErrorList' => ['sync0', null, 'sync2', null],
|
||||||
'async' => 'async',
|
'async' => 'async',
|
||||||
'asyncReject' => null,
|
'asyncReject' => null,
|
||||||
|
'asyncRawReject' => null,
|
||||||
|
'asyncEmptyReject' => null,
|
||||||
'asyncError' => null,
|
'asyncError' => null,
|
||||||
|
'asyncRawError' => null,
|
||||||
|
'asyncReturnError' => null,
|
||||||
],
|
],
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create('Error getting syncError', [new SourceLocation(3, 7)]),
|
[
|
||||||
FormattedError::create('Error getting syncRawError', [new SourceLocation(4, 7)]),
|
'message' => 'Error getting syncError',
|
||||||
FormattedError::create('Error getting asyncReject', [new SourceLocation(6, 7)]),
|
'locations' => [['line' => 3, 'column' => 7]],
|
||||||
FormattedError::create('Error getting asyncError', [new SourceLocation(7, 7)])
|
'path' => ['syncError']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting syncRawError',
|
||||||
|
'locations' => [ [ 'line' => 4, 'column' => 7 ] ],
|
||||||
|
'path'=> [ 'syncRawError' ]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting syncReturnError',
|
||||||
|
'locations' => [['line' => 5, 'column' => 7]],
|
||||||
|
'path' => ['syncReturnError']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting syncReturnErrorList1',
|
||||||
|
'locations' => [['line' => 6, 'column' => 7]],
|
||||||
|
'path' => ['syncReturnErrorList', 1]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting syncReturnErrorList3',
|
||||||
|
'locations' => [['line' => 6, 'column' => 7]],
|
||||||
|
'path' => ['syncReturnErrorList', 3]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting asyncReject',
|
||||||
|
'locations' => [['line' => 8, 'column' => 7]],
|
||||||
|
'path' => ['asyncReject']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting asyncRawReject',
|
||||||
|
'locations' => [['line' => 9, 'column' => 7]],
|
||||||
|
'path' => ['asyncRawReject']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'An unknown error occurred.',
|
||||||
|
'locations' => [['line' => 10, 'column' => 7]],
|
||||||
|
'path' => ['asyncEmptyReject']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting asyncError',
|
||||||
|
'locations' => [['line' => 11, 'column' => 7]],
|
||||||
|
'path' => ['asyncError']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting asyncRawError',
|
||||||
|
'locations' => [ [ 'line' => 12, 'column' => 7 ] ],
|
||||||
|
'path' => [ 'asyncRawError' ]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Error getting asyncReturnError',
|
||||||
|
'locations' => [['line' => 13, 'column' => 7]],
|
||||||
|
'path' => ['asyncReturnError']
|
||||||
|
],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
$result = Executor::execute($schema, $docAst, $data);
|
$result = Executor::execute($schema, $docAst, $data);
|
||||||
|
|
||||||
$this->assertArraySubset($expected, $result->toArray());
|
$this->assertEquals($expected, self::awaitPromise($result));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -629,6 +741,62 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals(['data' => ['a' => 'b']], $subscriptionResult->toArray());
|
$this->assertEquals(['data' => ['a' => 'b']], $subscriptionResult->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCorrectFieldOrderingDespiteExecutionOrder()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
|
||||||
|
$doc = '{
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
e
|
||||||
|
}';
|
||||||
|
$data = [
|
||||||
|
'a' => function () {
|
||||||
|
return 'a';
|
||||||
|
},
|
||||||
|
'b' => function () {
|
||||||
|
return new Promise(function (callable $resolve) { return $resolve('b'); });
|
||||||
|
},
|
||||||
|
'c' => function () {
|
||||||
|
return 'c';
|
||||||
|
},
|
||||||
|
'd' => function () {
|
||||||
|
return new Promise(function (callable $resolve) { return $resolve('d'); });
|
||||||
|
},
|
||||||
|
'e' => function () {
|
||||||
|
return 'e';
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$queryType = new ObjectType([
|
||||||
|
'name' => 'DeepDataType',
|
||||||
|
'fields' => [
|
||||||
|
'a' => [ 'type' => Type::string() ],
|
||||||
|
'b' => [ 'type' => Type::string() ],
|
||||||
|
'c' => [ 'type' => Type::string() ],
|
||||||
|
'd' => [ 'type' => Type::string() ],
|
||||||
|
'e' => [ 'type' => Type::string() ],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$schema = new Schema(['query' => $queryType]);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'a' => 'a',
|
||||||
|
'b' => 'b',
|
||||||
|
'c' => 'c',
|
||||||
|
'd' => 'd',
|
||||||
|
'e' => 'e',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, self::awaitPromise(Executor::execute($schema, $ast, $data)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @it Avoids recursion
|
* @it Avoids recursion
|
||||||
*/
|
*/
|
||||||
@ -923,4 +1091,17 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
]
|
]
|
||||||
], $result->toArray());
|
], $result->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \GraphQL\Executor\Promise\Promise $promise
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function awaitPromise($promise)
|
||||||
|
{
|
||||||
|
$results = null;
|
||||||
|
$promise->then(function (ExecutionResult $executionResult) use (&$results) {
|
||||||
|
$results = $executionResult->toArray();
|
||||||
|
});
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace GraphQL\Tests\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Executor\ExecutionResult;
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Error\FormattedError;
|
use GraphQL\Error\FormattedError;
|
||||||
|
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
@ -12,6 +14,521 @@ use GraphQL\Type\Definition\Type;
|
|||||||
|
|
||||||
class ListsTest extends \PHPUnit_Framework_TestCase
|
class ListsTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tearDownAfterClass()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe: Execute: Handles list nullability
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]
|
||||||
|
*/
|
||||||
|
public function testHandlesNullableListsWithArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
[ 1, 2 ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
[ 1, null, 2 ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
null,
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]
|
||||||
|
*/
|
||||||
|
public function testHandlesNullableListsWithPromiseArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
\React\Promise\resolve([ 1, 2 ]),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
\React\Promise\resolve([ 1, null, 2 ]),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
\React\Promise\resolve(null),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rejected
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
function () {
|
||||||
|
return \React\Promise\reject(new \Exception('bad'));
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => ['test' => null]],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]
|
||||||
|
*/
|
||||||
|
public function testHandlesNullableListsWithArrayPromise()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(null), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
\React\Promise\resolve(null),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains reject
|
||||||
|
$this->checkHandlesNullableLists(
|
||||||
|
function () {
|
||||||
|
return [ \React\Promise\resolve(1), \React\Promise\reject(new \Exception('bad')), \React\Promise\resolve(2) ];
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => ['test' => [1, null, 2]]],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test', 1]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]!
|
||||||
|
*/
|
||||||
|
public function testHandlesNonNullableListsWithArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
[ 1, 2 ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
[ 1, null, 2 ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]!
|
||||||
|
*/
|
||||||
|
public function testHandlesNonNullableListsWithPromiseArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
\React\Promise\resolve([ 1, 2 ]),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
\React\Promise\resolve([ 1, null, 2 ]),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rejected
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
function () {
|
||||||
|
return \React\Promise\reject(new \Exception('bad'));
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => null],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]!
|
||||||
|
*/
|
||||||
|
public function testHandlesNonNullableListsWithArrayPromise()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(null), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains reject
|
||||||
|
$this->checkHandlesNonNullableLists(
|
||||||
|
function () {
|
||||||
|
return [ \React\Promise\resolve(1), \React\Promise\reject(new \Exception('bad')), \React\Promise\resolve(2) ];
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => ['test' => [1, null, 2]]],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test', 1]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T!]
|
||||||
|
*/
|
||||||
|
public function testHandlesListOfNonNullsWithArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
[ 1, 2 ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
[ 1, null, 2 ],
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => [ 'test' => null ] ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
null,
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T!]
|
||||||
|
*/
|
||||||
|
public function testHandlesListOfNonNullsWithPromiseArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
\React\Promise\resolve([ 1, 2 ]),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
\React\Promise\resolve([ 1, null, 2 ]),
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => [ 'test' => null ] ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
\React\Promise\resolve(null),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rejected
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
function () {
|
||||||
|
return \React\Promise\reject(new \Exception('bad'));
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => ['test' => null]],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T]!
|
||||||
|
*/
|
||||||
|
public function testHandlesListOfNonNullsWithArrayPromise()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(null), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains reject
|
||||||
|
$this->checkHandlesListOfNonNulls(
|
||||||
|
function () {
|
||||||
|
return [ \React\Promise\resolve(1), \React\Promise\reject(new \Exception('bad')), \React\Promise\resolve(2) ];
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => ['test' => null]],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test', 1]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T!]!
|
||||||
|
*/
|
||||||
|
public function testHandlesNonNullListOfNonNullsWithArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
[ 1, 2 ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
[ 1, null, 2 ],
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T!]!
|
||||||
|
*/
|
||||||
|
public function testHandlesNonNullListOfNonNullsWithPromiseArray()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
\React\Promise\resolve([ 1, 2 ]),
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
\React\Promise\resolve([ 1, null, 2 ]),
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns null
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
\React\Promise\resolve(null),
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rejected
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
function () {
|
||||||
|
return \React\Promise\reject(new \Exception('bad'));
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @describe [T!]!
|
||||||
|
*/
|
||||||
|
public function testHandlesNonNullListOfNonNullsWithArrayPromise()
|
||||||
|
{
|
||||||
|
// Contains values
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(2) ],
|
||||||
|
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains null
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
[ \React\Promise\resolve(1), \React\Promise\resolve(null), \React\Promise\resolve(2) ],
|
||||||
|
[
|
||||||
|
'data' => [ 'nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
'Cannot return null for non-nullable field DataType.test.',
|
||||||
|
[ new SourceLocation(1, 10) ]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contains reject
|
||||||
|
$this->checkHandlesNonNullListOfNonNulls(
|
||||||
|
function () {
|
||||||
|
return [ \React\Promise\resolve(1), \React\Promise\reject(new \Exception('bad')), \React\Promise\resolve(2) ];
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'data' => ['nest' => null ],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'bad',
|
||||||
|
'locations' => [['line' => 1, 'column' => 10]],
|
||||||
|
'path' => ['nest', 'test']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkHandlesNullableLists($testData, $expected)
|
||||||
|
{
|
||||||
|
$testType = Type::listOf(Type::int());;
|
||||||
|
$this->check($testType, $testData, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkHandlesNonNullableLists($testData, $expected)
|
||||||
|
{
|
||||||
|
$testType = Type::nonNull(Type::listOf(Type::int()));
|
||||||
|
$this->check($testType, $testData, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkHandlesListOfNonNulls($testData, $expected)
|
||||||
|
{
|
||||||
|
$testType = Type::listOf(Type::nonNull(Type::int()));
|
||||||
|
$this->check($testType, $testData, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkHandlesNonNullListOfNonNulls($testData, $expected)
|
||||||
|
{
|
||||||
|
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
|
||||||
|
$this->check($testType, $testData, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
private function check($testType, $testData, $expected)
|
private function check($testType, $testData, $expected)
|
||||||
{
|
{
|
||||||
$data = ['test' => $testData];
|
$data = ['test' => $testData];
|
||||||
@ -41,157 +558,19 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
|||||||
$ast = Parser::parse('{ nest { test } }');
|
$ast = Parser::parse('{ nest { test } }');
|
||||||
|
|
||||||
$result = Executor::execute($schema, $ast, $data);
|
$result = Executor::execute($schema, $ast, $data);
|
||||||
$this->assertArraySubset($expected, $result->toArray());
|
$this->assertArraySubset($expected, self::awaitPromise($result));
|
||||||
}
|
|
||||||
|
|
||||||
// Describe: Execute: Handles list nullability
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @describe [T]
|
|
||||||
*/
|
|
||||||
public function testHandlesNullableLists()
|
|
||||||
{
|
|
||||||
$type = Type::listOf(Type::int());
|
|
||||||
|
|
||||||
// Contains values
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, 2 ],
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Contains null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, null, 2 ],
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Returns null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
null,
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @describe [T]!
|
* @param \GraphQL\Executor\Promise\Promise $promise
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function testHandlesNonNullableLists()
|
private static function awaitPromise($promise)
|
||||||
{
|
{
|
||||||
$type = Type::nonNull(Type::listOf(Type::int()));
|
$results = null;
|
||||||
|
$promise->then(function (ExecutionResult $executionResult) use (&$results) {
|
||||||
// Contains values
|
$results = $executionResult->toArray();
|
||||||
$this->check(
|
});
|
||||||
$type,
|
return $results;
|
||||||
[ 1, 2 ],
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Contains null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, null, 2 ],
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Returns null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
null,
|
|
||||||
[
|
|
||||||
'data' => [ 'nest' => null ],
|
|
||||||
'errors' => [
|
|
||||||
FormattedError::create(
|
|
||||||
'Cannot return null for non-nullable field DataType.test.',
|
|
||||||
[ new SourceLocation(1, 10) ]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @describe [T!]
|
|
||||||
*/
|
|
||||||
public function testHandlesListOfNonNulls()
|
|
||||||
{
|
|
||||||
$type = Type::listOf(Type::nonNull(Type::int()));
|
|
||||||
|
|
||||||
// Contains values
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, 2 ],
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Contains null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, null, 2 ],
|
|
||||||
[
|
|
||||||
'data' => [ 'nest' => [ 'test' => null ] ],
|
|
||||||
'errors' => [
|
|
||||||
FormattedError::create(
|
|
||||||
'Cannot return null for non-nullable field DataType.test.',
|
|
||||||
[ new SourceLocation(1, 10) ]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Returns null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
null,
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => null ] ] ]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @describe [T!]!
|
|
||||||
*/
|
|
||||||
public function testHandlesNonNullListOfNonNulls()
|
|
||||||
{
|
|
||||||
$type = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
|
|
||||||
|
|
||||||
// Contains values
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, 2 ],
|
|
||||||
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Contains null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
[ 1, null, 2 ],
|
|
||||||
[
|
|
||||||
'data' => [ 'nest' => null ],
|
|
||||||
'errors' => [
|
|
||||||
FormattedError::create(
|
|
||||||
'Cannot return null for non-nullable field DataType.test.',
|
|
||||||
[ new SourceLocation(1, 10) ]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Returns null
|
|
||||||
$this->check(
|
|
||||||
$type,
|
|
||||||
null,
|
|
||||||
[
|
|
||||||
'data' => [ 'nest' => null ],
|
|
||||||
'errors' => [
|
|
||||||
FormattedError::create(
|
|
||||||
'Cannot return null for non-nullable field DataType.test.',
|
|
||||||
[ new SourceLocation(1, 10) ]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Tests\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
|
use GraphQL\Executor\ExecutionResult;
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Error\FormattedError;
|
use GraphQL\Error\FormattedError;
|
||||||
|
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use React\Promise\Promise;
|
||||||
|
|
||||||
class MutationsTest extends \PHPUnit_Framework_TestCase
|
class MutationsTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tearDownAfterClass()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute: Handles mutation execution ordering
|
// Execute: Handles mutation execution ordering
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +69,7 @@ class MutationsTest extends \PHPUnit_Framework_TestCase
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertEquals($expected, $mutationResult->toArray());
|
$this->assertEquals($expected, self::awaitPromise($mutationResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,7 +127,20 @@ class MutationsTest extends \PHPUnit_Framework_TestCase
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertArraySubset($expected, $mutationResult->toArray());
|
$this->assertArraySubset($expected, self::awaitPromise($mutationResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \GraphQL\Executor\Promise\Promise $promise
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function awaitPromise($promise)
|
||||||
|
{
|
||||||
|
$results = null;
|
||||||
|
$promise->then(function (ExecutionResult $executionResult) use (&$results) {
|
||||||
|
$results = $executionResult->toArray();
|
||||||
|
});
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function schema()
|
private function schema()
|
||||||
@ -200,12 +226,14 @@ class Root {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $newNumber
|
* @param $newNumber
|
||||||
* @return NumberHolder
|
*
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function promiseToChangeTheNumber($newNumber)
|
public function promiseToChangeTheNumber($newNumber)
|
||||||
{
|
{
|
||||||
// No promises
|
return new Promise(function (callable $resolve) use ($newNumber) {
|
||||||
return $this->immediatelyChangeTheNumber($newNumber);
|
return $resolve($this->immediatelyChangeTheNumber($newNumber));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,11 +245,12 @@ class Root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \Exception
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function promiseAndFailToChangeTheNumber()
|
public function promiseAndFailToChangeTheNumber()
|
||||||
{
|
{
|
||||||
// No promises
|
return new Promise(function (callable $resolve, callable $reject) {
|
||||||
throw new \Exception("Cannot change the number");
|
return $reject(new \Exception("Cannot change the number"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace GraphQL\Tests\Executor;
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Error\FormattedError;
|
use GraphQL\Error\FormattedError;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use React\Promise\Promise;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
class NonNullTest extends \PHPUnit_Framework_TestCase
|
class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
@ -17,6 +20,13 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/** @var \Exception */
|
/** @var \Exception */
|
||||||
public $nonNullSyncError;
|
public $nonNullSyncError;
|
||||||
|
|
||||||
|
/** @var \Exception */
|
||||||
|
public $promiseError;
|
||||||
|
|
||||||
|
/** @var \Exception */
|
||||||
|
public $nonNullPromiseError;
|
||||||
|
|
||||||
public $throwingData;
|
public $throwingData;
|
||||||
public $nullingData;
|
public $nullingData;
|
||||||
public $schema;
|
public $schema;
|
||||||
@ -25,6 +35,8 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$this->syncError = new \Exception('sync');
|
$this->syncError = new \Exception('sync');
|
||||||
$this->nonNullSyncError = new \Exception('nonNullSync');
|
$this->nonNullSyncError = new \Exception('nonNullSync');
|
||||||
|
$this->promiseError = new \Exception('promise');
|
||||||
|
$this->nonNullPromiseError = new \Exception('nonNullPromise');
|
||||||
|
|
||||||
$this->throwingData = [
|
$this->throwingData = [
|
||||||
'sync' => function () {
|
'sync' => function () {
|
||||||
@ -33,12 +45,32 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
'nonNullSync' => function () {
|
'nonNullSync' => function () {
|
||||||
throw $this->nonNullSyncError;
|
throw $this->nonNullSyncError;
|
||||||
},
|
},
|
||||||
|
'promise' => function () {
|
||||||
|
return new Promise(function () {
|
||||||
|
throw $this->promiseError;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'nonNullPromise' => function () {
|
||||||
|
return new Promise(function () {
|
||||||
|
throw $this->nonNullPromiseError;
|
||||||
|
});
|
||||||
|
},
|
||||||
'nest' => function () {
|
'nest' => function () {
|
||||||
return $this->throwingData;
|
return $this->throwingData;
|
||||||
},
|
},
|
||||||
'nonNullNest' => function () {
|
'nonNullNest' => function () {
|
||||||
return $this->throwingData;
|
return $this->throwingData;
|
||||||
},
|
},
|
||||||
|
'promiseNest' => function () {
|
||||||
|
return new Promise(function (callable $resolve) {
|
||||||
|
$resolve($this->throwingData);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'nonNullPromiseNest' => function () {
|
||||||
|
return new Promise(function (callable $resolve) {
|
||||||
|
$resolve($this->throwingData);
|
||||||
|
});
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->nullingData = [
|
$this->nullingData = [
|
||||||
@ -48,12 +80,32 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
'nonNullSync' => function () {
|
'nonNullSync' => function () {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
'promise' => function () {
|
||||||
|
return new Promise(function (callable $resolve) {
|
||||||
|
return $resolve(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'nonNullPromise' => function () {
|
||||||
|
return new Promise(function (callable $resolve) {
|
||||||
|
return $resolve(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
'nest' => function () {
|
'nest' => function () {
|
||||||
return $this->nullingData;
|
return $this->nullingData;
|
||||||
},
|
},
|
||||||
'nonNullNest' => function () {
|
'nonNullNest' => function () {
|
||||||
return $this->nullingData;
|
return $this->nullingData;
|
||||||
},
|
},
|
||||||
|
'promiseNest' => function () {
|
||||||
|
return new Promise(function (callable $resolve) {
|
||||||
|
$resolve($this->nullingData);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'nonNullPromiseNest' => function () {
|
||||||
|
return new Promise(function (callable $resolve) {
|
||||||
|
$resolve($this->nullingData);
|
||||||
|
});
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
$dataType = new ObjectType([
|
$dataType = new ObjectType([
|
||||||
@ -62,8 +114,12 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
return [
|
return [
|
||||||
'sync' => ['type' => Type::string()],
|
'sync' => ['type' => Type::string()],
|
||||||
'nonNullSync' => ['type' => Type::nonNull(Type::string())],
|
'nonNullSync' => ['type' => Type::nonNull(Type::string())],
|
||||||
|
'promise' => Type::string(),
|
||||||
|
'nonNullPromise' => Type::nonNull(Type::string()),
|
||||||
'nest' => $dataType,
|
'nest' => $dataType,
|
||||||
'nonNullNest' => Type::nonNull($dataType)
|
'nonNullNest' => Type::nonNull($dataType),
|
||||||
|
'promiseNest' => $dataType,
|
||||||
|
'nonNullPromiseNest' => Type::nonNull($dataType),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@ -71,6 +127,11 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->schema = new Schema(['query' => $dataType]);
|
$this->schema = new Schema(['query' => $dataType]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
Executor::setPromiseAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute: handles non-nullable types
|
// Execute: handles non-nullable types
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,6 +161,32 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNullsANullableFieldThatThrowsInAPromise()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'promise' => null,
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create(
|
||||||
|
$this->promiseError->getMessage(),
|
||||||
|
[new SourceLocation(3, 9)]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously()
|
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously()
|
||||||
{
|
{
|
||||||
// nulls a synchronously returned object that contains a non-nullable field that throws synchronously
|
// nulls a synchronously returned object that contains a non-nullable field that throws synchronously
|
||||||
@ -124,18 +211,111 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
nest {
|
||||||
|
nonNullPromise,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'nest' => null
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(4, 11)])
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
promiseNest {
|
||||||
|
nonNullSync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'promiseNest' => null
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(4, 11)])
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
promiseNest {
|
||||||
|
nonNullPromise,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'promiseNest' => null
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(4, 11)])
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testNullsAComplexTreeOfNullableFieldsThatThrow()
|
public function testNullsAComplexTreeOfNullableFieldsThatThrow()
|
||||||
{
|
{
|
||||||
$doc = '
|
$doc = '
|
||||||
query Q {
|
query Q {
|
||||||
nest {
|
nest {
|
||||||
sync
|
sync
|
||||||
|
promise
|
||||||
nest {
|
nest {
|
||||||
sync
|
sync
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
|
nest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
';
|
';
|
||||||
|
|
||||||
$ast = Parser::parse($doc);
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
@ -143,17 +323,119 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
'data' => [
|
'data' => [
|
||||||
'nest' => [
|
'nest' => [
|
||||||
'sync' => null,
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
'nest' => [
|
'nest' => [
|
||||||
'sync' => null,
|
'sync' => null,
|
||||||
]
|
'promise' => null,
|
||||||
]
|
],
|
||||||
|
'promiseNest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'promiseNest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
'nest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
],
|
||||||
|
'promiseNest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(4, 11)]),
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(4, 11)]),
|
||||||
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(6, 13)]),
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(5, 11)]),
|
||||||
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(7, 13)]),
|
||||||
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(8, 13)]),
|
||||||
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(11, 13)]),
|
||||||
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(12, 13)]),
|
||||||
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(16, 11)]),
|
||||||
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]),
|
||||||
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(19, 13)]),
|
||||||
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]),
|
||||||
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(23, 13)]),
|
||||||
|
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 13)]),
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
nest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anotherNest: nest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullPromise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anotherPromiseNest: promiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullPromise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'nest' => null,
|
||||||
|
'promiseNest' => null,
|
||||||
|
'anotherNest' => null,
|
||||||
|
'anotherPromiseNest' => null,
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(8, 19)]),
|
||||||
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(19, 19)]),
|
||||||
|
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(30, 19)]),
|
||||||
|
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(41, 19)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNullsANullableFieldThatSynchronouslyReturnsNull()
|
public function testNullsANullableFieldThatSynchronouslyReturnsNull()
|
||||||
@ -174,9 +456,28 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test4()
|
public function testNullsANullableFieldThatReturnsNullInAPromise()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'promise' => null,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously()
|
||||||
{
|
{
|
||||||
// nulls a synchronously returned object that contains a non-nullable field that returns null synchronously
|
|
||||||
$doc = '
|
$doc = '
|
||||||
query Q {
|
query Q {
|
||||||
nest {
|
nest {
|
||||||
@ -198,19 +499,107 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
$this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test5()
|
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise()
|
||||||
{
|
{
|
||||||
// nulls a complex tree of nullable fields that return null
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
nest {
|
||||||
|
nonNullPromise,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'nest' => null,
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(4, 11)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullSynchronously()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
promiseNest {
|
||||||
|
nonNullSync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'promiseNest' => null,
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(4, 11)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullInaAPromise()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
promiseNest {
|
||||||
|
nonNullPromise,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'promiseNest' => null,
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(4, 11)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsAComplexTreeOfNullableFieldsThatReturnNull()
|
||||||
|
{
|
||||||
$doc = '
|
$doc = '
|
||||||
query Q {
|
query Q {
|
||||||
nest {
|
nest {
|
||||||
sync
|
sync
|
||||||
|
promise
|
||||||
nest {
|
nest {
|
||||||
sync
|
sync
|
||||||
nest {
|
promise
|
||||||
sync
|
}
|
||||||
}
|
promiseNest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
|
nest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
sync
|
||||||
|
promise
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,16 +611,107 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
'data' => [
|
'data' => [
|
||||||
'nest' => [
|
'nest' => [
|
||||||
'sync' => null,
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
'nest' => [
|
'nest' => [
|
||||||
'sync' => null,
|
'sync' => null,
|
||||||
'nest' => [
|
'promise' => null,
|
||||||
'sync' => null
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
|
'promiseNest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
]
|
||||||
],
|
],
|
||||||
|
'promiseNest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
'nest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
],
|
||||||
|
'promiseNest' => [
|
||||||
|
'sync' => null,
|
||||||
|
'promise' => null,
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray());
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->then(function ($actual) use ($expected) {
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChainOfFieldsThatAreNonNull()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q {
|
||||||
|
nest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anotherNest: nest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullPromise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anotherPromiseNest: promiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullNest {
|
||||||
|
nonNullPromiseNest {
|
||||||
|
nonNullPromise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'nest' => null,
|
||||||
|
'promiseNest' => null,
|
||||||
|
'anotherNest' => null,
|
||||||
|
'anotherPromiseNest' => null,
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(8, 19)]),
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(19, 19)]),
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(30, 19)]),
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(41, 19)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNullsTheTopLevelIfSyncNonNullableFieldThrows()
|
public function testNullsTheTopLevelIfSyncNonNullableFieldThrows()
|
||||||
@ -241,11 +721,32 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
';
|
';
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
|
'data' => null,
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(2, 17)])
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(2, 17)])
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertArraySubset($expected, Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray());
|
$actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray();
|
||||||
|
$this->assertArraySubset($expected, $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullsTheTopLevelIfAsyncNonNullableFieldErrors()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q { nonNullPromise }
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => null,
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(2, 17)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull()
|
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull()
|
||||||
@ -262,4 +763,33 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
];
|
];
|
||||||
$this->assertArraySubset($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray());
|
$this->assertArraySubset($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query Q { nonNullPromise }
|
||||||
|
';
|
||||||
|
|
||||||
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => null,
|
||||||
|
'errors' => [
|
||||||
|
FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(2, 17)]),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Executor::setPromiseAdapter(new ReactPromiseAdapter());
|
||||||
|
$this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertArraySubsetPromise($subset, PromiseInterface $promise)
|
||||||
|
{
|
||||||
|
$array = null;
|
||||||
|
$promise->then(function ($value) use (&$array) {
|
||||||
|
$array = $value->toArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertArraySubset($subset, $array);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user