mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 06:16:05 +03:00
Updated to latest version of graphql-js
This commit is contained in:
parent
022c962942
commit
841d6ab851
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Schema;
|
||||
|
||||
@ -25,7 +26,7 @@ class ExecutionContext
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
public $root;
|
||||
public $rootValue;
|
||||
|
||||
/**
|
||||
* @var OperationDefinition
|
||||
@ -35,7 +36,7 @@ class ExecutionContext
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $variables;
|
||||
public $variableValues;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@ -46,13 +47,13 @@ class ExecutionContext
|
||||
{
|
||||
$this->schema = $schema;
|
||||
$this->fragments = $fragments;
|
||||
$this->root = $root;
|
||||
$this->rootValue = $root;
|
||||
$this->operation = $operation;
|
||||
$this->variables = $variables;
|
||||
$this->variableValues = $variables;
|
||||
$this->errors = $errors ?: [];
|
||||
}
|
||||
|
||||
public function addError($error)
|
||||
public function addError(Error $error)
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
return $this;
|
||||
|
38
src/Executor/ExecutionResult.php
Normal file
38
src/Executor/ExecutionResult.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error;
|
||||
|
||||
class ExecutionResult
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @var Error[]
|
||||
*/
|
||||
public $errors;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $errors
|
||||
*/
|
||||
public function __construct(array $data = null, array $errors = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->errors = $errors;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$result = ['data' => $this->data];
|
||||
|
||||
if (!empty($this->errors)) {
|
||||
$result['errors'] = array_map(['GraphQL\Error', 'formatError'], $this->errors);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Language\AST\SelectionSet;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\AbstractType;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
@ -16,6 +17,7 @@ use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
@ -45,40 +47,70 @@ class Executor
|
||||
{
|
||||
private static $UNDEFINED;
|
||||
|
||||
public static function execute(Schema $schema, $root, Document $ast, $operationName = null, array $args = null)
|
||||
private static $defaultResolveFn;
|
||||
|
||||
/**
|
||||
* Custom default resolve function
|
||||
*
|
||||
* @param $fn
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setDefaultResolveFn($fn)
|
||||
{
|
||||
Utils::invariant(is_callable($fn), 'Expecting callable, but got ' . Utils::getVariableType($fn));
|
||||
self::$defaultResolveFn = $fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @param Document $ast
|
||||
* @param $rootValue
|
||||
* @param array|\ArrayAccess $variableValues
|
||||
* @param null $operationName
|
||||
* @return ExecutionResult
|
||||
*/
|
||||
public static function execute(Schema $schema, Document $ast, $rootValue = null, $variableValues = null, $operationName = null)
|
||||
{
|
||||
if (!self::$UNDEFINED) {
|
||||
self::$UNDEFINED = new \stdClass();
|
||||
}
|
||||
|
||||
if (null !== $variableValues) {
|
||||
Utils::invariant(
|
||||
is_array($variableValues) || $variableValues instanceof \ArrayAccess,
|
||||
"Variable values are expected to be array or instance of ArrayAccess, got " . Utils::getVariableType($variableValues)
|
||||
);
|
||||
}
|
||||
if (null !== $operationName) {
|
||||
Utils::invariant(
|
||||
is_string($operationName),
|
||||
"Operation name is supposed to be string, got " . Utils::getVariableType($operationName)
|
||||
);
|
||||
}
|
||||
|
||||
$exeContext = self::buildExecutionContext($schema, $ast, $rootValue, $variableValues, $operationName);
|
||||
|
||||
try {
|
||||
$errors = new \ArrayObject();
|
||||
$exeContext = self::buildExecutionContext($schema, $root, $ast, $operationName, $args, $errors);
|
||||
$data = self::executeOperation($exeContext, $root, $exeContext->operation);
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = $e;
|
||||
$data = self::executeOperation($exeContext, $exeContext->operation, $rootValue);
|
||||
} catch (Error $e) {
|
||||
$exeContext->addError($e);
|
||||
$data = null;
|
||||
}
|
||||
|
||||
$result = [
|
||||
'data' => isset($data) ? $data : null
|
||||
];
|
||||
if (count($errors) > 0) {
|
||||
$result['errors'] = array_map(['GraphQL\Error', 'formatError'], $errors->getArrayCopy());
|
||||
}
|
||||
|
||||
return $result;
|
||||
return new ExecutionResult($data, $exeContext->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ExecutionContext object from the arguments passed to
|
||||
* execute, which we will pass throughout the other execution methods.
|
||||
*/
|
||||
private static function buildExecutionContext(Schema $schema, $root, Document $ast, $operationName = null, array $args = null, &$errors)
|
||||
private static function buildExecutionContext(Schema $schema, Document $documentAst, $rootValue, $rawVariableValues, $operationName = null)
|
||||
{
|
||||
$errors = [];
|
||||
$operations = [];
|
||||
$fragments = [];
|
||||
|
||||
foreach ($ast->definitions as $statement) {
|
||||
foreach ($documentAst->definitions as $statement) {
|
||||
switch ($statement->kind) {
|
||||
case Node::OPERATION_DEFINITION:
|
||||
$operations[$statement->name ? $statement->name->value : ''] = $statement;
|
||||
@ -91,32 +123,34 @@ class Executor
|
||||
|
||||
if (!$operationName && count($operations) !== 1) {
|
||||
throw new Error(
|
||||
'Must provide operation name if query contains multiple operations'
|
||||
'Must provide operation name if query contains multiple operations.'
|
||||
);
|
||||
}
|
||||
|
||||
$opName = $operationName ?: key($operations);
|
||||
if (!isset($operations[$opName])) {
|
||||
throw new Error('Unknown operation name: ' . $opName);
|
||||
if (empty($operations[$opName])) {
|
||||
throw new Error('Unknown operation named ' . $opName);
|
||||
}
|
||||
$operation = $operations[$opName];
|
||||
|
||||
$variables = Values::getVariableValues($schema, $operation->variableDefinitions ?: array(), $args ?: []);
|
||||
$exeContext = new ExecutionContext($schema, $fragments, $root, $operation, $variables, $errors);
|
||||
$variableValues = Values::getVariableValues($schema, $operation->variableDefinitions ?: [], $rawVariableValues ?: []);
|
||||
$exeContext = new ExecutionContext($schema, $fragments, $rootValue, $operation, $variableValues, $errors);
|
||||
return $exeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the "Evaluating operations" section of the spec.
|
||||
*/
|
||||
private static function executeOperation(ExecutionContext $exeContext, $root, OperationDefinition $operation)
|
||||
private static function executeOperation(ExecutionContext $exeContext, OperationDefinition $operation, $rootValue)
|
||||
{
|
||||
$type = self::getOperationRootType($exeContext->schema, $operation);
|
||||
$fields = self::collectFields($exeContext, $type, $operation->selectionSet, new \ArrayObject(), new \ArrayObject());
|
||||
|
||||
if ($operation->operation === 'mutation') {
|
||||
return self::executeFieldsSerially($exeContext, $type, $root, $fields->getArrayCopy());
|
||||
return self::executeFieldsSerially($exeContext, $type, $rootValue, $fields->getArrayCopy());
|
||||
}
|
||||
return self::executeFields($exeContext, $type, $root, $fields);
|
||||
|
||||
return self::executeFields($exeContext, $type, $rootValue, $fields);
|
||||
}
|
||||
|
||||
|
||||
@ -154,11 +188,11 @@ class Executor
|
||||
* Implements the "Evaluating selection sets" section of the spec
|
||||
* for "write" mode.
|
||||
*/
|
||||
private static function executeFieldsSerially(ExecutionContext $exeContext, ObjectType $parentType, $source, $fields)
|
||||
private static function executeFieldsSerially(ExecutionContext $exeContext, ObjectType $parentType, $sourceValue, $fields)
|
||||
{
|
||||
$results = [];
|
||||
foreach ($fields as $responseName => $fieldASTs) {
|
||||
$result = self::resolveField($exeContext, $parentType, $source, $fieldASTs);
|
||||
$result = self::resolveField($exeContext, $parentType, $sourceValue, $fieldASTs);
|
||||
|
||||
if ($result !== self::$UNDEFINED) {
|
||||
// Undefined means that field is not defined in schema
|
||||
@ -250,18 +284,36 @@ class Executor
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a field should be included based on @if and @unless directives.
|
||||
* Determines if a field should be included based on the @include and @skip
|
||||
* directives, where @skip has higher precedence than @include.
|
||||
*/
|
||||
private static function shouldIncludeNode(ExecutionContext $exeContext, $directives)
|
||||
{
|
||||
$ifDirective = Values::getDirectiveValue(Directive::ifDirective(), $directives, $exeContext->variables);
|
||||
if ($ifDirective !== null) {
|
||||
return $ifDirective;
|
||||
$skipDirective = Directive::skipDirective();
|
||||
$includeDirective = Directive::includeDirective();
|
||||
|
||||
/** @var \GraphQL\Language\AST\Directive $skipAST */
|
||||
$skipAST = $directives
|
||||
? Utils::find($directives, function(\GraphQL\Language\AST\Directive $directive) use ($skipDirective) {
|
||||
return $directive->name->value === $skipDirective->name;
|
||||
})
|
||||
: null;
|
||||
|
||||
if ($skipAST) {
|
||||
$argValues = Values::getArgumentValues($skipDirective->args, $skipAST->arguments, $exeContext->variableValues);
|
||||
return empty($argValues['if']);
|
||||
}
|
||||
|
||||
$unlessDirective = Values::getDirectiveValue(Directive::unlessDirective(), $directives, $exeContext->variables);
|
||||
if ($unlessDirective !== null) {
|
||||
return !$unlessDirective;
|
||||
/** @var \GraphQL\Language\AST\Directive $includeAST */
|
||||
$includeAST = $directives
|
||||
? Utils::find($directives, function(\GraphQL\Language\AST\Directive $directive) use ($includeDirective) {
|
||||
return $directive->name->value === $includeDirective->name;
|
||||
})
|
||||
: null;
|
||||
|
||||
if ($includeAST) {
|
||||
$argValues = Values::getArgumentValues($includeDirective->args, $includeAST->arguments, $exeContext->variableValues);
|
||||
return !empty($argValues['if']);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -293,97 +345,106 @@ class Executor
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper function for resolving the field, that catches the error
|
||||
* and adds it to the context's global if the error is not rethrowable.
|
||||
* Resolves the field on the given source object. In particular, this
|
||||
* figures out the value that the field returns by calling its resolve function,
|
||||
* then calls completeValue to complete promises, serialize scalars, or execute
|
||||
* the sub-selection-set for objects.
|
||||
*/
|
||||
private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs)
|
||||
{
|
||||
$fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldASTs[0]);
|
||||
$fieldAST = $fieldASTs[0];
|
||||
$fieldName = $fieldAST->name->value;
|
||||
|
||||
$fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldName);
|
||||
|
||||
if (!$fieldDef) {
|
||||
return self::$UNDEFINED;
|
||||
}
|
||||
|
||||
$returnType = $fieldDef->getType();
|
||||
|
||||
if (isset($fieldDef->resolve)) {
|
||||
$resolveFn = $fieldDef->resolve;
|
||||
} else if (isset(self::$defaultResolveFn)) {
|
||||
$resolveFn = self::$defaultResolveFn;
|
||||
} else {
|
||||
$resolveFn = [__CLASS__, 'defaultResolveFn'];
|
||||
}
|
||||
|
||||
// Build hash of arguments from the field.arguments AST, using the
|
||||
// variables scope to fulfill any variable references.
|
||||
// TODO: find a way to memoize, in case this field is within a List type.
|
||||
$args = Values::getArgumentValues(
|
||||
$fieldDef->args,
|
||||
$fieldAST->arguments,
|
||||
$exeContext->variableValues
|
||||
);
|
||||
|
||||
// The resolve function's optional third argument is a collection of
|
||||
// information about the current execution state.
|
||||
$info = new ResolveInfo([
|
||||
'fieldName' => $fieldName,
|
||||
'fieldASTs' => $fieldASTs,
|
||||
'returnType' => $returnType,
|
||||
'parentType' => $parentType,
|
||||
'schema' => $exeContext->schema,
|
||||
'fragments' => $exeContext->fragments,
|
||||
'rootValue' => $exeContext->rootValue,
|
||||
'operation' => $exeContext->operation,
|
||||
'variableValues' => $exeContext->variableValues,
|
||||
]);
|
||||
|
||||
// If an error occurs while calling the field `resolve` function, ensure that
|
||||
// it is wrapped as a GraphQLError with locations. Log this error and return
|
||||
// null if allowed, otherwise throw the error so the parent field can handle
|
||||
// it.
|
||||
try {
|
||||
$result = call_user_func($resolveFn, $source, $args, $info);
|
||||
} catch (\Exception $error) {
|
||||
$reportedError = Error::createLocatedError($error, $fieldASTs);
|
||||
|
||||
if ($returnType instanceof NonNull) {
|
||||
throw $reportedError;
|
||||
}
|
||||
|
||||
$exeContext->addError($reportedError);
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::completeValueCatchingError(
|
||||
$exeContext,
|
||||
$returnType,
|
||||
$fieldASTs,
|
||||
$info,
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public static function completeValueCatchingError(
|
||||
ExecutionContext $exeContext,
|
||||
Type $returnType,
|
||||
$fieldASTs,
|
||||
ResolveInfo $info,
|
||||
$result
|
||||
)
|
||||
{
|
||||
// If the field type is non-nullable, then it is resolved without any
|
||||
// protection from errors.
|
||||
if ($fieldDef->getType() instanceof NonNull) {
|
||||
return self::resolveFieldOrError(
|
||||
$exeContext,
|
||||
$parentType,
|
||||
$source,
|
||||
$fieldASTs,
|
||||
$fieldDef
|
||||
);
|
||||
if ($returnType instanceof NonNull) {
|
||||
return self::completeValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
||||
}
|
||||
|
||||
// Otherwise, error protection is applied, logging the error and resolving
|
||||
// a null value for this field if one is encountered.
|
||||
try {
|
||||
$result = self::resolveFieldOrError(
|
||||
$exeContext,
|
||||
$parentType,
|
||||
$source,
|
||||
$fieldASTs,
|
||||
$fieldDef
|
||||
);
|
||||
|
||||
return $result;
|
||||
} catch (\Exception $error) {
|
||||
$exeContext->addError($error);
|
||||
return self::completeValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
||||
} catch (Error $err) {
|
||||
$exeContext->addError($err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the field on the given source object. In particular, this
|
||||
* figures out the object that the field returns using the resolve function,
|
||||
* then calls completeField to coerce scalars or execute the sub
|
||||
* selection set for objects.
|
||||
*/
|
||||
private static function resolveFieldOrError(
|
||||
ExecutionContext $exeContext,
|
||||
ObjectType $parentType,
|
||||
$source,
|
||||
/*array<Field>*/ $fieldASTs,
|
||||
FieldDefinition $fieldDef
|
||||
)
|
||||
{
|
||||
$fieldAST = $fieldASTs[0];
|
||||
$fieldType = $fieldDef->getType();
|
||||
$resolveFn = $fieldDef->resolve ?: [__CLASS__, 'defaultResolveFn'];
|
||||
|
||||
// Build a JS object of arguments from the field.arguments AST, using the
|
||||
// variables scope to fulfill any variable references.
|
||||
// TODO: find a way to memoize, in case this field is within a Array type.
|
||||
$args = Values::getArgumentValues(
|
||||
$fieldDef->args,
|
||||
$fieldAST->arguments,
|
||||
$exeContext->variables
|
||||
);
|
||||
|
||||
try {
|
||||
$result = call_user_func($resolveFn,
|
||||
$source,
|
||||
$args,
|
||||
$exeContext->root,
|
||||
// TODO: provide all fieldASTs, not just the first field
|
||||
$fieldAST,
|
||||
$fieldType,
|
||||
$parentType,
|
||||
$exeContext->schema
|
||||
);
|
||||
} catch (\Exception $error) {
|
||||
throw Error::createLocatedError($error, [$fieldAST]);
|
||||
}
|
||||
|
||||
return self::completeField(
|
||||
$exeContext,
|
||||
$fieldType,
|
||||
$fieldASTs,
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements the instructions for completeValue as defined in the
|
||||
* "Field entries" section of the spec.
|
||||
@ -396,20 +457,22 @@ class Executor
|
||||
* for the inner type on each item in the list.
|
||||
*
|
||||
* If the field type is a Scalar or Enum, ensures the completed value is a legal
|
||||
* value of the type by calling the `coerce` method of GraphQL type definition.
|
||||
* value of the type by calling the `serialize` method of GraphQL type
|
||||
* definition.
|
||||
*
|
||||
* Otherwise, the field type expects a sub-selection set, and will complete the
|
||||
* value by evaluating all sub-selections.
|
||||
*/
|
||||
private static function completeField(ExecutionContext $exeContext, Type $fieldType,/* Array<Field> */ $fieldASTs, &$result)
|
||||
private static function completeValue(ExecutionContext $exeContext, Type $returnType,/* Array<Field> */ $fieldASTs, ResolveInfo $info, &$result)
|
||||
{
|
||||
// If field type is NonNull, complete for inner type, and throw field error
|
||||
// if result is null.
|
||||
if ($fieldType instanceof NonNull) {
|
||||
$completed = self::completeField(
|
||||
if ($returnType instanceof NonNull) {
|
||||
$completed = self::completeValue(
|
||||
$exeContext,
|
||||
$fieldType->getWrappedType(),
|
||||
$returnType->getWrappedType(),
|
||||
$fieldASTs,
|
||||
$info,
|
||||
$result
|
||||
);
|
||||
if ($completed === null) {
|
||||
@ -427,8 +490,8 @@ class Executor
|
||||
}
|
||||
|
||||
// If field type is List, complete each item in the list with the inner type
|
||||
if ($fieldType instanceof ListOfType) {
|
||||
$itemType = $fieldType->getWrappedType();
|
||||
if ($returnType instanceof ListOfType) {
|
||||
$itemType = $returnType->getWrappedType();
|
||||
Utils::invariant(
|
||||
is_array($result) || $result instanceof \Traversable,
|
||||
'User Error: expected iterable, but did not find one.'
|
||||
@ -436,32 +499,48 @@ class Executor
|
||||
|
||||
$tmp = [];
|
||||
foreach ($result as $item) {
|
||||
$tmp[] = self::completeField($exeContext, $itemType, $fieldASTs, $item);
|
||||
$tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item);
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
// If field type is Scalar or Enum, coerce to a valid value, returning null
|
||||
// if coercion is not possible.
|
||||
if ($fieldType instanceof ScalarType ||
|
||||
$fieldType instanceof EnumType
|
||||
) {
|
||||
Utils::invariant(method_exists($fieldType, 'coerce'), 'Missing coerce method on type');
|
||||
return $fieldType->coerce($result);
|
||||
// If field type is Scalar or Enum, serialize to a valid value, returning
|
||||
// null if serialization is not possible.
|
||||
if ($returnType instanceof ScalarType ||
|
||||
$returnType instanceof EnumType) {
|
||||
Utils::invariant(method_exists($returnType, 'serialize'), 'Missing serialize method on type');
|
||||
return $returnType->serialize($result);
|
||||
}
|
||||
|
||||
// Field type must be Object, Interface or Union and expect sub-selections.
|
||||
if ($returnType instanceof ObjectType) {
|
||||
$objectType = $returnType;
|
||||
} else if ($returnType instanceof AbstractType) {
|
||||
$objectType = $returnType->getObjectType($result, $info);
|
||||
|
||||
$objectType =
|
||||
$fieldType instanceof ObjectType ? $fieldType :
|
||||
($fieldType instanceof InterfaceType ||
|
||||
$fieldType instanceof UnionType ? $fieldType->resolveType($result) :
|
||||
null);
|
||||
if ($objectType && !$returnType->isPossibleType($objectType)) {
|
||||
throw new Error(
|
||||
"Runtime Object type \"$objectType\" is not a possible type for \"$returnType\"."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$objectType = null;
|
||||
}
|
||||
|
||||
if (!$objectType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there is an isTypeOf predicate function, call it with the
|
||||
// current result. If isTypeOf returns false, then raise an error rather
|
||||
// than continuing execution.
|
||||
if (false === $objectType->isTypeOf($result, $info)) {
|
||||
throw new Error(
|
||||
"Expected value of type $objectType but got: $result.",
|
||||
$fieldASTs
|
||||
);
|
||||
}
|
||||
|
||||
// Collect sub-fields to execute to complete this value.
|
||||
$subFieldASTs = new \ArrayObject();
|
||||
$visitedFragmentNames = new \ArrayObject();
|
||||
@ -488,17 +567,18 @@ class Executor
|
||||
* and returns it as the result, or if it's a function, returns the result
|
||||
* of calling that function.
|
||||
*/
|
||||
public static function defaultResolveFn($source, $args, $root, $fieldAST)
|
||||
public static function defaultResolveFn($source, $args, ResolveInfo $info)
|
||||
{
|
||||
$fieldName = $info->fieldName;
|
||||
$property = null;
|
||||
|
||||
if (is_array($source) || $source instanceof \ArrayAccess) {
|
||||
if (isset($source[$fieldAST->name->value])) {
|
||||
$property = $source[$fieldAST->name->value];
|
||||
if (isset($source[$fieldName])) {
|
||||
$property = $source[$fieldName];
|
||||
}
|
||||
} else if (is_object($source)) {
|
||||
if (property_exists($source, $fieldAST->name->value)) {
|
||||
$e = func_get_args();
|
||||
$property = $source->{$fieldAST->name->value};
|
||||
if (property_exists($source, $fieldName)) {
|
||||
$property = $source->{$fieldName};
|
||||
}
|
||||
}
|
||||
|
||||
@ -516,25 +596,21 @@ class Executor
|
||||
*
|
||||
* @return FieldDefinition
|
||||
*/
|
||||
private static function getFieldDef(Schema $schema, ObjectType $parentType, Field $fieldAST)
|
||||
private static function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName)
|
||||
{
|
||||
$name = $fieldAST->name->value;
|
||||
$schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
|
||||
$typeMetaFieldDef = Introspection::typeMetaFieldDef();
|
||||
$typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
|
||||
|
||||
if ($name === $schemaMetaFieldDef->name &&
|
||||
$schema->getQueryType() === $parentType
|
||||
) {
|
||||
if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||
return $schemaMetaFieldDef;
|
||||
} else if ($name === $typeMetaFieldDef->name &&
|
||||
$schema->getQueryType() === $parentType
|
||||
) {
|
||||
} else if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||
return $typeMetaFieldDef;
|
||||
} else if ($name === $typeNameMetaFieldDef->name) {
|
||||
} else if ($fieldName === $typeNameMetaFieldDef->name) {
|
||||
return $typeNameMetaFieldDef;
|
||||
}
|
||||
|
||||
$tmp = $parentType->getFields();
|
||||
return isset($tmp[$name]) ? $tmp[$name] : null;
|
||||
return isset($tmp[$fieldName]) ? $tmp[$fieldName] : null;
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,25 @@ namespace GraphQL\Executor;
|
||||
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Argument;
|
||||
use GraphQL\Language\AST\ListType;
|
||||
use GraphQL\Language\AST\ListValue;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\ObjectValue;
|
||||
use GraphQL\Language\AST\Value;
|
||||
use GraphQL\Language\AST\Variable;
|
||||
use GraphQL\Language\AST\VariableDefinition;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\FieldArgument;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils;
|
||||
@ -23,8 +32,14 @@ class Values
|
||||
* Prepares an object map of variables of the correct type based on the provided
|
||||
* variable definitions and arbitrary input. If the input cannot be coerced
|
||||
* to match the variable definitions, a Error will be thrown.
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param VariableDefinition[] $definitionASTs
|
||||
* @param array $inputs
|
||||
* @return array
|
||||
* @throws Error
|
||||
*/
|
||||
public static function getVariableValues(Schema $schema, /* Array<VariableDefinition> */ $definitionASTs, array $inputs)
|
||||
public static function getVariableValues(Schema $schema, $definitionASTs, array $inputs)
|
||||
{
|
||||
$values = [];
|
||||
foreach ($definitionASTs as $defAST) {
|
||||
@ -37,11 +52,16 @@ class Values
|
||||
/**
|
||||
* Prepares an object map of argument values given a list of argument
|
||||
* definitions and list of argument AST nodes.
|
||||
*
|
||||
* @param FieldArgument[] $argDefs
|
||||
* @param Argument[] $argASTs
|
||||
* @param $variableValues
|
||||
* @return array
|
||||
*/
|
||||
public static function getArgumentValues(/* Array<GraphQLFieldArgument>*/ $argDefs, /*Array<Argument>*/ $argASTs, $variables)
|
||||
public static function getArgumentValues($argDefs, $argASTs, $variableValues)
|
||||
{
|
||||
if (!$argDefs || count($argDefs) === 0) {
|
||||
return null;
|
||||
if (!$argDefs || !$argASTs) {
|
||||
return [];
|
||||
}
|
||||
$argASTMap = $argASTs ? Utils::keyMap($argASTs, function ($arg) {
|
||||
return $arg->name->value;
|
||||
@ -50,28 +70,71 @@ class Values
|
||||
foreach ($argDefs as $argDef) {
|
||||
$name = $argDef->name;
|
||||
$valueAST = isset($argASTMap[$name]) ? $argASTMap[$name]->value : null;
|
||||
$result[$name] = self::coerceValueAST($argDef->getType(), $valueAST, $variables);
|
||||
$value = self::valueFromAST($valueAST, $argDef->getType(), $variableValues);
|
||||
|
||||
if (null === $value) {
|
||||
$value = $argDef->defaultValue;
|
||||
}
|
||||
if (null !== $value) {
|
||||
$result[$name] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getDirectiveValue(Directive $directiveDef, /* Array<Directive> */ $directives, $variables)
|
||||
public static function valueFromAST($valueAST, InputType $type, $variables = null)
|
||||
{
|
||||
$directiveAST = null;
|
||||
if ($directives) {
|
||||
foreach ($directives as $directive) {
|
||||
if ($directive->name->value === $directiveDef->name) {
|
||||
$directiveAST = $directive;
|
||||
break;
|
||||
if ($type instanceof NonNull) {
|
||||
return self::valueFromAST($valueAST, $type->getWrappedType(), $variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($directiveAST) {
|
||||
if (!$directiveDef->type) {
|
||||
|
||||
if (!$valueAST) {
|
||||
return null;
|
||||
}
|
||||
return self::coerceValueAST($directiveDef->type, $directiveAST->value, $variables);
|
||||
|
||||
if ($valueAST instanceof Variable) {
|
||||
$variableName = $valueAST->name->value;
|
||||
|
||||
if (!$variables || !isset($variables[$variableName])) {
|
||||
return null;
|
||||
}
|
||||
return $variables[$variableName];
|
||||
}
|
||||
|
||||
if ($type instanceof ListOfType) {
|
||||
$itemType = $type->getWrappedType();
|
||||
if ($valueAST instanceof ListValue) {
|
||||
return array_map(function($itemAST) use ($itemType, $variables) {
|
||||
return Values::valueFromAST($itemAST, $itemType, $variables);
|
||||
}, $valueAST->values);
|
||||
} else {
|
||||
return [self::valueFromAST($valueAST, $itemType, $variables)];
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof InputObjectType) {
|
||||
$fields = $type->getFields();
|
||||
if (!$valueAST instanceof ObjectValue) {
|
||||
return null;
|
||||
}
|
||||
$fieldASTs = Utils::keyMap($valueAST->fields, function($field) {return $field->name->value;});
|
||||
$values = [];
|
||||
foreach ($fields as $field) {
|
||||
$fieldAST = isset($fieldASTs[$field->name]) ? $fieldASTs[$field->name] : null;
|
||||
$fieldValue = self::valueFromAST($fieldAST ? $fieldAST->value : null, $field->getType(), $variables);
|
||||
|
||||
if (null === $fieldValue) {
|
||||
$fieldValue = $field->defaultValue;
|
||||
}
|
||||
if (null !== $fieldValue) {
|
||||
$values[$field->name] = $fieldValue;
|
||||
}
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type');
|
||||
return $type->parseLiteral($valueAST);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,14 +144,21 @@ class Values
|
||||
private static function getVariableValue(Schema $schema, VariableDefinition $definitionAST, $input)
|
||||
{
|
||||
$type = Utils\TypeInfo::typeFromAST($schema, $definitionAST->type);
|
||||
if (!$type) {
|
||||
return null;
|
||||
$variable = $definitionAST->variable;
|
||||
|
||||
if (!$type || !Type::isInputType($type)) {
|
||||
$printed = Printer::doPrint($definitionAST->type);
|
||||
throw new Error(
|
||||
"Variable \"\${$variable->name->value}\" expected value of type " .
|
||||
"\"$printed\" which cannot be used as an input type.",
|
||||
[ $definitionAST ]
|
||||
);
|
||||
}
|
||||
if (self::isValidValue($type, $input)) {
|
||||
if (self::isValidValue($input, $type)) {
|
||||
if (null === $input) {
|
||||
$defaultValue = $definitionAST->defaultValue;
|
||||
if ($defaultValue) {
|
||||
return self::coerceValueAST($type, $defaultValue);
|
||||
return self::valueFromAST($defaultValue, $type);
|
||||
}
|
||||
}
|
||||
return self::coerceValue($type, $input);
|
||||
@ -103,15 +173,21 @@ class Values
|
||||
|
||||
|
||||
/**
|
||||
* Given a type and any value, return true if that value is valid.
|
||||
* Given a PHP value and a GraphQL type, determine if the value will be
|
||||
* accepted for that type. This is primarily useful for validating the
|
||||
* runtime values of query variables.
|
||||
*
|
||||
* @param $value
|
||||
* @param Type $type
|
||||
* @return bool
|
||||
*/
|
||||
private static function isValidValue(Type $type, $value)
|
||||
private static function isValidValue($value, Type $type)
|
||||
{
|
||||
if ($type instanceof NonNull) {
|
||||
if (null === $value) {
|
||||
return false;
|
||||
}
|
||||
return self::isValidValue($type->getWrappedType(), $value);
|
||||
return self::isValidValue($value, $type->getWrappedType());
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
@ -122,34 +198,44 @@ class Values
|
||||
$itemType = $type->getWrappedType();
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
if (!self::isValidValue($itemType, $item)) {
|
||||
if (!self::isValidValue($item, $itemType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return self::isValidValue($itemType, $value);
|
||||
return self::isValidValue($value, $itemType);
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof InputObjectType) {
|
||||
$fields = $type->getFields();
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
/** @var FieldDefinition $field */
|
||||
if (!self::isValidValue($field->getType(), isset($value[$fieldName]) ? $value[$fieldName] : null)) {
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
$fields = $type->getFields();
|
||||
$fieldMap = [];
|
||||
|
||||
// Ensure every defined field is valid.
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
/** @var FieldDefinition $field */
|
||||
if (!self::isValidValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $field->getType())) {
|
||||
return false;
|
||||
}
|
||||
$fieldMap[$field->name] = $field;
|
||||
}
|
||||
|
||||
// Ensure every provided field is defined.
|
||||
$diff = array_diff_key($value, $fieldMap);
|
||||
|
||||
if (!empty($diff)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($type instanceof ScalarType ||
|
||||
$type instanceof EnumType
|
||||
) {
|
||||
return null !== $type->coerce($value);
|
||||
}
|
||||
|
||||
return false;
|
||||
Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type');
|
||||
return null !== $type->parseValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,93 +269,18 @@ class Values
|
||||
$fields = $type->getFields();
|
||||
$obj = [];
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
$fieldValue = self::coerceValue($field->getType(), $value[$fieldName]);
|
||||
$obj[$fieldName] = $fieldValue === null ? $field->defaultValue : $fieldValue;
|
||||
$fieldValue = self::coerceValue($field->getType(), isset($value[$fieldName]) ? $value[$fieldName] : null);
|
||||
if (null === $fieldValue) {
|
||||
$fieldValue = $field->defaultValue;
|
||||
}
|
||||
if (null !== $fieldValue) {
|
||||
$obj[$fieldName] = $fieldValue;
|
||||
}
|
||||
}
|
||||
return $obj;
|
||||
|
||||
}
|
||||
|
||||
if ($type instanceof ScalarType ||
|
||||
$type instanceof EnumType
|
||||
) {
|
||||
$coerced = $type->coerce($value);
|
||||
if (null !== $coerced) {
|
||||
return $coerced;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a type and a value AST node known to match this type, build a
|
||||
* runtime value.
|
||||
*/
|
||||
private static function coerceValueAST(Type $type, $valueAST, $variables)
|
||||
{
|
||||
if ($type instanceof NonNull) {
|
||||
// Note: we're not checking that the result of coerceValueAST is non-null.
|
||||
// We're assuming that this query has been validated and the value used
|
||||
// here is of the correct type.
|
||||
return self::coerceValueAST($type->getWrappedType(), $valueAST, $variables);
|
||||
}
|
||||
|
||||
if (!$valueAST) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($valueAST->kind === Node::VARIABLE) {
|
||||
$variableName = $valueAST->name->value;
|
||||
|
||||
if (!isset($variables, $variables[$variableName])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Note: we're not doing any checking that this variable is correct. We're
|
||||
// assuming that this query has been validated and the variable usage here
|
||||
// is of the correct type.
|
||||
return $variables[$variableName];
|
||||
}
|
||||
|
||||
if ($type instanceof ListOfType) {
|
||||
$itemType = $type->getWrappedType();
|
||||
if ($valueAST->kind === Node::ARR) {
|
||||
$tmp = [];
|
||||
foreach ($valueAST->values as $itemAST) {
|
||||
$tmp[] = self::coerceValueAST($itemType, $itemAST, $variables);
|
||||
}
|
||||
return $tmp;
|
||||
} else {
|
||||
return [self::coerceValueAST($itemType, $valueAST, $variables)];
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof InputObjectType) {
|
||||
$fields = $type->getFields();
|
||||
if ($valueAST->kind !== Node::OBJECT) {
|
||||
return null;
|
||||
}
|
||||
$fieldASTs = Utils::keyMap($valueAST->fields, function ($field) {
|
||||
return $field->name->value;
|
||||
});
|
||||
|
||||
$obj = [];
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
$fieldAST = $fieldASTs[$fieldName];
|
||||
$fieldValue = self::coerceValueAST($field->getType(), $fieldAST ? $fieldAST->value : null, $variables);
|
||||
$obj[$fieldName] = $fieldValue === null ? $field->defaultValue : $fieldValue;
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
if ($type instanceof ScalarType || $type instanceof EnumType) {
|
||||
$coerced = $type->coerceLiteral($valueAST);
|
||||
if (null !== $coerced) {
|
||||
return $coerced;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type');
|
||||
return $type->parseValue($value);
|
||||
}
|
||||
}
|
||||
|
@ -11,25 +11,25 @@ class GraphQL
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @param $requestString
|
||||
* @param mixed $rootObject
|
||||
* @param mixed $rootValue
|
||||
* @param array <string, string>|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(Schema $schema, $requestString, $rootObject = null, $variableValues = null, $operationName = null)
|
||||
public static function execute(Schema $schema, $requestString, $rootValue = null, $variableValues = null, $operationName = null)
|
||||
{
|
||||
try {
|
||||
$source = new Source($requestString ?: '', 'GraphQL request');
|
||||
$ast = Parser::parse($source);
|
||||
$validationResult = DocumentValidator::validate($schema, $ast);
|
||||
$documentAST = Parser::parse($source);
|
||||
$validationErrors = DocumentValidator::validate($schema, $documentAST);
|
||||
|
||||
if (empty($validationResult['isValid'])) {
|
||||
return ['errors' => $validationResult['errors']];
|
||||
if (!empty($validationErrors)) {
|
||||
return ['errors' => array_map(['GraphQL\Error', 'formatError'], $validationErrors)];
|
||||
} else {
|
||||
return Executor::execute($schema, $rootObject, $ast, $operationName, $variableValues);
|
||||
return Executor::execute($schema, $documentAST, $rootValue, $variableValues, $operationName)->toArray();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ['errors' => Error::formatError($e)];
|
||||
} catch (Error $e) {
|
||||
return ['errors' => [Error::formatError($e)]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class Argument extends NamedType
|
||||
class Argument extends Node
|
||||
{
|
||||
public $kind = Node::ARGUMENT;
|
||||
|
||||
@ -9,4 +9,9 @@ class Argument extends NamedType
|
||||
* @var Value
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class Field extends NamedType
|
||||
class Field extends Node
|
||||
{
|
||||
public $kind = Node::FIELD;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Name|null
|
||||
*/
|
||||
|
@ -2,10 +2,15 @@
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
|
||||
class FragmentDefinition extends NamedType implements Definition
|
||||
class FragmentDefinition extends Node implements Definition
|
||||
{
|
||||
public $kind = Node::FRAGMENT_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var NamedType
|
||||
*/
|
||||
|
@ -1,10 +1,15 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class FragmentSpread extends NamedType
|
||||
class FragmentSpread extends Node
|
||||
{
|
||||
public $kind = Node::FRAGMENT_SPREAD;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var array<Directive>
|
||||
*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NamedType extends Node
|
||||
class NamedType extends Node implements Type
|
||||
{
|
||||
public $kind = Node::NAMED_TYPE;
|
||||
|
||||
|
@ -2,10 +2,15 @@
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
|
||||
class ObjectField extends NamedType
|
||||
class ObjectField extends Node
|
||||
{
|
||||
public $kind = Node::OBJECT_FIELD;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Value
|
||||
*/
|
||||
|
@ -1,13 +1,18 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class OperationDefinition extends NamedType implements Definition
|
||||
class OperationDefinition extends Node implements Definition
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $kind = Node::OPERATION_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string (oneOf 'query', 'mutation'))
|
||||
*/
|
||||
|
@ -1,7 +1,12 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class Variable extends NamedType
|
||||
class Variable extends Node
|
||||
{
|
||||
public $kind = Node::VARIABLE;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
}
|
||||
|
128
src/Schema.php
128
src/Schema.php
@ -2,7 +2,12 @@
|
||||
namespace GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\FieldArgument;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
@ -24,6 +29,99 @@ class Schema
|
||||
Utils::invariant($querySchema || $mutationSchema, "Either query or mutation type must be set");
|
||||
$this->querySchema = $querySchema;
|
||||
$this->mutationSchema = $mutationSchema;
|
||||
|
||||
// Build type map now to detect any errors within this schema.
|
||||
$map = [];
|
||||
foreach ([$this->getQueryType(), $this->getMutationType(), Introspection::_schema()] as $type) {
|
||||
$this->_extractTypes($type, $map);
|
||||
}
|
||||
$this->_typeMap = $map + Type::getInternalTypes();
|
||||
|
||||
// Enforce correct interface implementations
|
||||
foreach ($this->_typeMap as $typeName => $type) {
|
||||
if ($type instanceof ObjectType) {
|
||||
foreach ($type->getInterfaces() as $iface) {
|
||||
$this->assertObjectImplementsInterface($type, $iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectType $object
|
||||
* @param InterfaceType $iface
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function assertObjectImplementsInterface(ObjectType $object, InterfaceType $iface)
|
||||
{
|
||||
$objectFieldMap = $object->getFields();
|
||||
$ifaceFieldMap = $iface->getFields();
|
||||
|
||||
foreach ($ifaceFieldMap as $fieldName => $ifaceField) {
|
||||
Utils::invariant(
|
||||
isset($objectFieldMap[$fieldName]),
|
||||
"\"$iface\" expects field \"$fieldName\" but \"$object\" does not provide it"
|
||||
);
|
||||
|
||||
/** @var $ifaceField FieldDefinition */
|
||||
/** @var $objectField FieldDefinition */
|
||||
$objectField = $objectFieldMap[$fieldName];
|
||||
|
||||
Utils::invariant(
|
||||
$this->isEqualType($ifaceField->getType(), $objectField->getType()),
|
||||
"$iface.$fieldName expects type \"{$ifaceField->getType()}\" but " .
|
||||
"$object.$fieldName provides type \"{$objectField->getType()}"
|
||||
);
|
||||
|
||||
foreach ($ifaceField->args as $ifaceArg) {
|
||||
/** @var $ifaceArg FieldArgument */
|
||||
/** @var $objectArg FieldArgument */
|
||||
$argName = $ifaceArg->name;
|
||||
$objectArg = $objectField->getArg($argName);
|
||||
|
||||
// Assert interface field arg exists on object field.
|
||||
Utils::invariant(
|
||||
$objectArg,
|
||||
"$iface.$fieldName expects argument \"$argName\" but $object.$fieldName does not provide it."
|
||||
);
|
||||
|
||||
// Assert interface field arg type matches object field arg type.
|
||||
// (invariant)
|
||||
Utils::invariant(
|
||||
$this->isEqualType($ifaceArg->getType(), $objectArg->getType()),
|
||||
"$iface.$fieldName($argName:) expects type \"{$ifaceArg->getType()}\" " .
|
||||
"but $object.$fieldName($argName:) provides " .
|
||||
"type \"{$objectArg->getType()}\""
|
||||
);
|
||||
|
||||
// Assert argument set invariance.
|
||||
foreach ($objectField->args as $objectArg) {
|
||||
$argName = $objectArg->name;
|
||||
$ifaceArg = $ifaceField->getArg($argName);
|
||||
Utils::invariant(
|
||||
$ifaceArg,
|
||||
"$iface.$fieldName does not define argument \"$argName\" but " .
|
||||
"$object.$fieldName provides it."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $typeA
|
||||
* @param $typeB
|
||||
* @return bool
|
||||
*/
|
||||
private function isEqualType($typeA, $typeB)
|
||||
{
|
||||
if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
|
||||
return $this->isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
|
||||
return $this->isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
return $typeA === $typeB;
|
||||
}
|
||||
|
||||
public function getQueryType()
|
||||
@ -57,8 +155,8 @@ class Schema
|
||||
{
|
||||
if (!$this->_directives) {
|
||||
$this->_directives = [
|
||||
Directive::ifDirective(),
|
||||
Directive::unlessDirective()
|
||||
Directive::includeDirective(),
|
||||
Directive::skipDirective()
|
||||
];
|
||||
}
|
||||
return $this->_directives;
|
||||
@ -66,27 +164,26 @@ class Schema
|
||||
|
||||
public function getTypeMap()
|
||||
{
|
||||
if (null === $this->_typeMap) {
|
||||
$map = [];
|
||||
foreach ([$this->getQueryType(), $this->getMutationType(), Introspection::_schema()] as $type) {
|
||||
$this->_extractTypes($type, $map);
|
||||
}
|
||||
$this->_typeMap = $map + Type::getInternalTypes();
|
||||
}
|
||||
return $this->_typeMap;
|
||||
}
|
||||
|
||||
private function _extractTypes($type, &$map)
|
||||
{
|
||||
if (!$type) {
|
||||
return $map;
|
||||
}
|
||||
|
||||
if ($type instanceof WrappingType) {
|
||||
return $this->_extractTypes($type->getWrappedType(), $map);
|
||||
}
|
||||
|
||||
if (!$type instanceof Type || !empty($map[$type->name])) {
|
||||
// TODO: warning?
|
||||
if (!empty($map[$type->name])) {
|
||||
Utils::invariant(
|
||||
$map[$type->name] === $type,
|
||||
"Schema must contain unique named types but contains multiple types named \"$type\"."
|
||||
);
|
||||
return $map;
|
||||
}
|
||||
|
||||
$map[$type->name] = $type;
|
||||
|
||||
$nestedTypes = [];
|
||||
@ -97,13 +194,12 @@ class Schema
|
||||
if ($type instanceof ObjectType) {
|
||||
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
|
||||
}
|
||||
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
|
||||
if ($type instanceof ObjectType || $type instanceof InterfaceType || $type instanceof InputObjectType) {
|
||||
foreach ((array) $type->getFields() as $fieldName => $field) {
|
||||
if (null === $field->args) {
|
||||
trigger_error('WTF ' . $field->name . ' has no args?'); // gg
|
||||
}
|
||||
if (isset($field->args)) {
|
||||
$fieldArgTypes = array_map(function($arg) { return $arg->getType(); }, $field->args);
|
||||
$nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
|
||||
}
|
||||
$nestedTypes[] = $field->getType();
|
||||
}
|
||||
}
|
||||
|
@ -13,4 +13,15 @@ GraphQLUnionType;
|
||||
* @return array<ObjectType>
|
||||
*/
|
||||
public function getPossibleTypes();
|
||||
|
||||
/**
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function getObjectType($value, ResolveInfo $info);
|
||||
|
||||
/**
|
||||
* @param Type $type
|
||||
* @return bool
|
||||
*/
|
||||
public function isPossibleType(Type $type);
|
||||
}
|
||||
|
@ -7,12 +7,17 @@ class BooleanType extends ScalarType
|
||||
{
|
||||
public $name = Type::BOOLEAN;
|
||||
|
||||
public function coerce($value)
|
||||
public function serialize($value)
|
||||
{
|
||||
return !!$value;
|
||||
}
|
||||
|
||||
public function coerceLiteral($ast)
|
||||
public function parseValue($value)
|
||||
{
|
||||
return !!$value;
|
||||
}
|
||||
|
||||
public function parseLiteral($ast)
|
||||
{
|
||||
if ($ast instanceof BooleanValue) {
|
||||
return (bool) $ast->value;
|
||||
|
@ -8,39 +8,51 @@ class Directive
|
||||
/**
|
||||
* @return Directive
|
||||
*/
|
||||
public static function ifDirective()
|
||||
public static function includeDirective()
|
||||
{
|
||||
$internal = self::getInternalDirectives();
|
||||
return $internal['if'];
|
||||
return $internal['include'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directive
|
||||
*/
|
||||
public static function unlessDirective()
|
||||
public static function skipDirective()
|
||||
{
|
||||
$internal = self::getInternalDirectives();
|
||||
return $internal['unless'];
|
||||
return $internal['skip'];
|
||||
}
|
||||
|
||||
public static function getInternalDirectives()
|
||||
{
|
||||
if (!self::$internalDirectives) {
|
||||
self::$internalDirectives = [
|
||||
'if' => new self([
|
||||
'include' => new self([
|
||||
'name' => 'include',
|
||||
'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.',
|
||||
'args' => [
|
||||
new FieldArgument([
|
||||
'name' => 'if',
|
||||
'description' => 'Directs the executor to omit this field if the argument provided is false.',
|
||||
'type' => Type::nonNull(Type::boolean()),
|
||||
'description' => 'Included when true.'
|
||||
])
|
||||
],
|
||||
'onOperation' => false,
|
||||
'onFragment' => false,
|
||||
'onFragment' => true,
|
||||
'onField' => true
|
||||
]),
|
||||
'unless' => new self([
|
||||
'name' => 'unless',
|
||||
'description' => 'Directs the executor to omit this field if the argument provided is true.',
|
||||
'skip' => new self([
|
||||
'name' => 'skip',
|
||||
'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.',
|
||||
'args' => [
|
||||
new FieldArgument([
|
||||
'name' => 'if',
|
||||
'type' => Type::nonNull(Type::boolean()),
|
||||
'description' => 'Skipped when true'
|
||||
])
|
||||
],
|
||||
'onOperation' => false,
|
||||
'onFragment' => false,
|
||||
'onFragment' => true,
|
||||
'onField' => true
|
||||
])
|
||||
];
|
||||
@ -59,9 +71,9 @@ class Directive
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* @var Type
|
||||
* @var FieldArgument[]
|
||||
*/
|
||||
public $type;
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
|
@ -53,13 +53,13 @@ class EnumType extends Type implements InputType, OutputType
|
||||
return $this->_values;
|
||||
}
|
||||
|
||||
public function coerce($value)
|
||||
public function serialize($value)
|
||||
{
|
||||
$enumValue = $this->_getValueLookup()->offsetGet($value);
|
||||
return $enumValue ? $enumValue->name : null;
|
||||
}
|
||||
|
||||
public function coerceLiteral($value)
|
||||
public function parseLiteral($value)
|
||||
{
|
||||
if ($value instanceof EnumValue) {
|
||||
$lookup = $this->_getNameLookup();
|
||||
|
@ -4,6 +4,12 @@ namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Utils;
|
||||
|
||||
/**
|
||||
* Class FieldArgument
|
||||
*
|
||||
* @package GraphQL\Type\Definition
|
||||
* @todo Rename to Argument as it is also applicable to directives, not only fields
|
||||
*/
|
||||
class FieldArgument
|
||||
{
|
||||
/**
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Utils;
|
||||
|
||||
class FieldDefinition
|
||||
{
|
||||
/**
|
||||
@ -98,12 +100,28 @@ class FieldDefinition
|
||||
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return FieldArgument|null
|
||||
*/
|
||||
public function getArg($name)
|
||||
{
|
||||
foreach ($this->args ?: [] as $arg) {
|
||||
/** @var FieldArgument $arg */
|
||||
if ($arg->name === $name) {
|
||||
return $arg;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
if (null === $this->resolvedType) {
|
||||
// TODO: deprecate types as callbacks - instead just allow field definitions to be callbacks
|
||||
$this->resolvedType = Type::resolve($this->type);
|
||||
}
|
||||
return $this->resolvedType;
|
||||
|
@ -8,12 +8,22 @@ class FloatType extends ScalarType
|
||||
{
|
||||
public $name = Type::FLOAT;
|
||||
|
||||
public function coerce($value)
|
||||
public function serialize($value)
|
||||
{
|
||||
return $this->coerceFloat($value);
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
return $this->coerceFloat($value);
|
||||
}
|
||||
|
||||
private function coerceFloat($value)
|
||||
{
|
||||
return is_numeric($value) || $value === true || $value === false ? (float) $value : null;
|
||||
}
|
||||
|
||||
public function coerceLiteral($ast)
|
||||
public function parseLiteral($ast)
|
||||
{
|
||||
if ($ast instanceof FloatValue || $ast instanceof IntValue) {
|
||||
return (float) $ast->value;
|
||||
|
@ -8,12 +8,17 @@ class IDType extends ScalarType
|
||||
{
|
||||
public $name = 'ID';
|
||||
|
||||
public function coerce($value)
|
||||
public function serialize($value)
|
||||
{
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
public function coerceLiteral($ast)
|
||||
public function parseValue($value)
|
||||
{
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
public function parseLiteral($ast)
|
||||
{
|
||||
if ($ast instanceof StringValue || $ast instanceof IntValue) {
|
||||
return $ast->value;
|
||||
|
@ -32,7 +32,7 @@ class InputObjectType extends Type implements InputType
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<InputObjectField>
|
||||
* @return InputObjectField[]
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
|
@ -2,12 +2,23 @@
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Language\AST\IntValue;
|
||||
use GraphQL\Language\AST\Value;
|
||||
|
||||
class IntType extends ScalarType
|
||||
{
|
||||
public $name = Type::INT;
|
||||
|
||||
public function coerce($value)
|
||||
public function serialize($value)
|
||||
{
|
||||
return $this->coerceInt($value);
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
return $this->coerceInt($value);
|
||||
}
|
||||
|
||||
private function coerceInt($value)
|
||||
{
|
||||
if (false === $value || true === $value) {
|
||||
return (int) $value;
|
||||
@ -18,7 +29,7 @@ class IntType extends ScalarType
|
||||
return null;
|
||||
}
|
||||
|
||||
public function coerceLiteral($ast)
|
||||
public function parseLiteral($ast)
|
||||
{
|
||||
if ($ast instanceof IntValue) {
|
||||
$val = (int) $ast->value;
|
||||
|
@ -35,11 +35,11 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
|
||||
* implementation for Interface types.
|
||||
*
|
||||
* @param ObjectType $impl
|
||||
* @param array<InterfaceType> $interfaces
|
||||
* @param InterfaceType[] $interfaces
|
||||
*/
|
||||
public static function addImplementationToInterfaces(ObjectType $impl, array $interfaces)
|
||||
public static function addImplementationToInterfaces(ObjectType $impl)
|
||||
{
|
||||
foreach ($interfaces as $interface) {
|
||||
foreach ($impl->getInterfaces() as $interface) {
|
||||
$interface->_implementations[] = $impl;
|
||||
}
|
||||
}
|
||||
@ -84,10 +84,10 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
|
||||
return $this->_implementations;
|
||||
}
|
||||
|
||||
public function isPossibleType(ObjectType $type)
|
||||
public function isPossibleType(Type $type)
|
||||
{
|
||||
$possibleTypeNames = $this->_possibleTypeNames;
|
||||
if (!$possibleTypeNames) {
|
||||
if (null === $possibleTypeNames) {
|
||||
$this->_possibleTypeNames = $possibleTypeNames = array_reduce($this->getPossibleTypes(), function(&$map, Type $possibleType) {
|
||||
$map[$possibleType->name] = true;
|
||||
return $map;
|
||||
@ -98,11 +98,13 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return ObjectType|null
|
||||
* @param ResolveInfo $info
|
||||
* @return Type|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function resolveType($value)
|
||||
public function getObjectType($value, ResolveInfo $info)
|
||||
{
|
||||
$resolver = $this->_resolveType;
|
||||
return $resolver ? call_user_func($resolver, $value) : Type::getTypeOf($value, $this);
|
||||
return $resolver ? call_user_func($resolver, $value, $info) : Type::getTypeOf($value, $info, $this);
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
/**
|
||||
* @var array<Field>
|
||||
*/
|
||||
private $_fields = [];
|
||||
private $_fields;
|
||||
|
||||
/**
|
||||
* @var array<InterfaceType>
|
||||
@ -57,9 +57,46 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
*/
|
||||
private $_isTypeOf;
|
||||
|
||||
/**
|
||||
* Keeping reference of config for late bindings
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_config;
|
||||
|
||||
private $_initialized = false;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
Config::validate($config, [
|
||||
$this->name = $config['name'];
|
||||
$this->description = isset($config['description']) ? $config['description'] : null;
|
||||
$this->_config = $config;
|
||||
|
||||
if (isset($config['interfaces'])) {
|
||||
InterfaceType::addImplementationToInterfaces($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Late instance initialization
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if ($this->_initialized) {
|
||||
return ;
|
||||
}
|
||||
$config = $this->_config;
|
||||
|
||||
if (isset($config['fields']) && is_callable($config['fields'])) {
|
||||
$config['fields'] = call_user_func($config['fields']);
|
||||
}
|
||||
if (isset($config['interfaces']) && is_callable($config['interfaces'])) {
|
||||
$config['interfaces'] = call_user_func($config['interfaces']);
|
||||
}
|
||||
|
||||
// Note: this validation is disabled by default, because it is resource-consuming
|
||||
// TODO: add bin/validate script to check if schema is valid during development
|
||||
Config::validate($this->_config, [
|
||||
'name' => Config::STRING | Config::REQUIRED,
|
||||
'fields' => Config::arrayOf(
|
||||
FieldDefinition::getDefinition(),
|
||||
@ -69,22 +106,13 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
'interfaces' => Config::arrayOf(
|
||||
Config::INTERFACE_TYPE
|
||||
),
|
||||
'isTypeOf' => Config::CALLBACK,
|
||||
'isTypeOf' => Config::CALLBACK, // ($value, ResolveInfo $info) => boolean
|
||||
]);
|
||||
|
||||
$this->name = $config['name'];
|
||||
$this->description = isset($config['description']) ? $config['description'] : null;
|
||||
|
||||
if (isset($config['fields'])) {
|
||||
$this->_fields = FieldDefinition::createMap($config['fields']);
|
||||
}
|
||||
|
||||
$this->_interfaces = isset($config['interfaces']) ? $config['interfaces'] : [];
|
||||
$this->_isTypeOf = isset($config['isTypeOf']) ? $config['isTypeOf'] : null;
|
||||
|
||||
if (!empty($this->_interfaces)) {
|
||||
InterfaceType::addImplementationToInterfaces($this, $this->_interfaces);
|
||||
}
|
||||
$this->_initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,6 +120,9 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
if (false === $this->_initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
return $this->_fields;
|
||||
}
|
||||
|
||||
@ -102,6 +133,9 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
*/
|
||||
public function getField($name)
|
||||
{
|
||||
if (false === $this->_initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
Utils::invariant(isset($this->_fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);
|
||||
return $this->_fields[$name];
|
||||
}
|
||||
@ -111,6 +145,9 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
*/
|
||||
public function getInterfaces()
|
||||
{
|
||||
if (false === $this->_initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
return $this->_interfaces;
|
||||
}
|
||||
|
||||
@ -118,8 +155,8 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
* @param $value
|
||||
* @return bool|null
|
||||
*/
|
||||
public function isTypeOf($value)
|
||||
public function isTypeOf($value, ResolveInfo $info)
|
||||
{
|
||||
return isset($this->_isTypeOf) ? call_user_func($this->_isTypeOf, $value) : null;
|
||||
return isset($this->_isTypeOf) ? call_user_func($this->_isTypeOf, $value, $info) : null;
|
||||
}
|
||||
}
|
||||
|
61
src/Type/Definition/ResolveInfo.php
Normal file
61
src/Type/Definition/ResolveInfo.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Language\AST\Field;
|
||||
use GraphQL\Language\AST\FragmentDefinition;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Utils;
|
||||
|
||||
class ResolveInfo
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $fieldName;
|
||||
|
||||
/**
|
||||
* @var Field[]
|
||||
*/
|
||||
public $fieldASTs;
|
||||
|
||||
/**
|
||||
* @var OutputType
|
||||
*/
|
||||
public $returnType;
|
||||
|
||||
/**
|
||||
* @var Type|CompositeType
|
||||
*/
|
||||
public $parentType;
|
||||
|
||||
/**
|
||||
* @var Schema
|
||||
*/
|
||||
public $schema;
|
||||
|
||||
/**
|
||||
* @var array<fragmentName, FragmentDefinition>
|
||||
*/
|
||||
public $fragments;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $rootValue;
|
||||
|
||||
/**
|
||||
* @var OperationDefinition
|
||||
*/
|
||||
public $operation;
|
||||
|
||||
/**
|
||||
* @var array<variableName, mixed>
|
||||
*/
|
||||
public $variableValues;
|
||||
|
||||
public function __construct(array $values)
|
||||
{
|
||||
Utils::assign($this, $values);
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ use GraphQL\Utils;
|
||||
*
|
||||
* var OddType = new GraphQLScalarType({
|
||||
* name: 'Odd',
|
||||
* coerce(value) {
|
||||
* serialize(value) {
|
||||
* return value % 2 === 1 ? value : null;
|
||||
* }
|
||||
* });
|
||||
@ -27,7 +27,9 @@ abstract class ScalarType extends Type implements OutputType, InputType
|
||||
Utils::invariant($this->name, 'Type must be named.');
|
||||
}
|
||||
|
||||
abstract public function coerce($value);
|
||||
abstract public function serialize($value);
|
||||
|
||||
abstract public function coerceLiteral($ast);
|
||||
abstract public function parseValue($value);
|
||||
|
||||
abstract public function parseLiteral(/* GraphQL\Language\AST\Value */$valueAST);
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
|
||||
class ScalarTypeConfig
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* @var \Closure
|
||||
*/
|
||||
public $coerce;
|
||||
|
||||
/**
|
||||
* @var \Closure
|
||||
*/
|
||||
public $coerceLiteral;
|
||||
}
|
@ -7,7 +7,12 @@ class StringType extends ScalarType
|
||||
{
|
||||
public $name = Type::STRING;
|
||||
|
||||
public function coerce($value)
|
||||
public function serialize($value)
|
||||
{
|
||||
return $this->parseValue($value);
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
if ($value === true) {
|
||||
return 'true';
|
||||
@ -18,7 +23,7 @@ class StringType extends ScalarType
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
public function coerceLiteral($ast)
|
||||
public function parseLiteral($ast)
|
||||
{
|
||||
if ($ast instanceof StringValue) {
|
||||
return $ast->value;
|
||||
|
@ -111,7 +111,7 @@ GraphQLNonNull;
|
||||
*/
|
||||
public static function isInputType($type)
|
||||
{
|
||||
$nakedType = self::getUnmodifiedType($type);
|
||||
$nakedType = self::getNamedType($type);
|
||||
return $nakedType instanceof InputType;
|
||||
}
|
||||
|
||||
@ -121,13 +121,14 @@ GraphQLNonNull;
|
||||
*/
|
||||
public static function isOutputType($type)
|
||||
{
|
||||
$nakedType = self::getUnmodifiedType($type);
|
||||
$nakedType = self::getNamedType($type);
|
||||
return $nakedType instanceof OutputType;
|
||||
}
|
||||
|
||||
public static function isLeafType($type)
|
||||
{
|
||||
$nakedType = self::getUnmodifiedType($type);
|
||||
// TODO: add LeafType interface
|
||||
$nakedType = self::getNamedType($type);
|
||||
return (
|
||||
$nakedType instanceof ScalarType ||
|
||||
$nakedType instanceof EnumType
|
||||
@ -136,19 +137,12 @@ GraphQLNonNull;
|
||||
|
||||
public static function isCompositeType($type)
|
||||
{
|
||||
return (
|
||||
$type instanceof ObjectType ||
|
||||
$type instanceof InterfaceType ||
|
||||
$type instanceof UnionType
|
||||
);
|
||||
return $type instanceof CompositeType;
|
||||
}
|
||||
|
||||
public static function isAbstractType($type)
|
||||
{
|
||||
return (
|
||||
$type instanceof InterfaceType ||
|
||||
$type instanceof UnionType
|
||||
);
|
||||
return $type instanceof AbstractType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,7 +158,7 @@ GraphQLNonNull;
|
||||
* @param $type
|
||||
* @return UnmodifiedType
|
||||
*/
|
||||
public static function getUnmodifiedType($type)
|
||||
public static function getNamedType($type)
|
||||
{
|
||||
if (null === $type) {
|
||||
return null;
|
||||
@ -195,21 +189,21 @@ GraphQLNonNull;
|
||||
* @return Type
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getTypeOf($value, AbstractType $abstractType)
|
||||
public static function getTypeOf($value, ResolveInfo $info, AbstractType $abstractType)
|
||||
{
|
||||
$possibleTypes = $abstractType->getPossibleTypes();
|
||||
|
||||
for ($i = 0; $i < count($possibleTypes); $i++) {
|
||||
/** @var ObjectType $type */
|
||||
$type = $possibleTypes[$i];
|
||||
$isTypeOf = $type->isTypeOf($value);
|
||||
$isTypeOf = $type->isTypeOf($value, $info);
|
||||
|
||||
if ($isTypeOf === null) {
|
||||
// TODO: move this to a JS impl specific type system validation step
|
||||
// so the error can be found before execution.
|
||||
throw new \Exception(
|
||||
'Non-Object Type ' . $abstractType->name . ' does not implement ' .
|
||||
'resolveType and Object Type ' . $type->name . ' does not implement ' .
|
||||
'getObjectType and Object Type ' . $type->name . ' does not implement ' .
|
||||
'isTypeOf. There is no way to determine if a value is of this type.'
|
||||
);
|
||||
}
|
||||
|
@ -73,9 +73,9 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
||||
* @param ObjectType $value
|
||||
* @return Type
|
||||
*/
|
||||
public function resolveType($value)
|
||||
public function getObjectType($value, ResolveInfo $info)
|
||||
{
|
||||
$resolver = $this->_resolveType;
|
||||
return $resolver ? call_user_func($resolver, $value) : Type::getTypeOf($value, $this);
|
||||
return $resolver ? call_user_func($resolver, $value) : Type::getTypeOf($value, $info, $this);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ namespace GraphQL\Type;
|
||||
|
||||
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
@ -10,6 +11,7 @@ use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
@ -30,6 +32,165 @@ class Introspection
|
||||
{
|
||||
private static $_map = [];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getIntrospectionQuery($includeDescription = true)
|
||||
{
|
||||
$withDescription = <<<'EOD'
|
||||
query IntrospectionQuery {
|
||||
__schema {
|
||||
queryType { name }
|
||||
mutationType { name }
|
||||
types {
|
||||
...FullType
|
||||
}
|
||||
directives {
|
||||
name
|
||||
description
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
onOperation
|
||||
onFragment
|
||||
onField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment FullType on __Type {
|
||||
kind
|
||||
name
|
||||
description
|
||||
fields {
|
||||
name
|
||||
description
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
type {
|
||||
...TypeRef
|
||||
}
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
inputFields {
|
||||
...InputValue
|
||||
}
|
||||
interfaces {
|
||||
...TypeRef
|
||||
}
|
||||
enumValues {
|
||||
name
|
||||
description
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
possibleTypes {
|
||||
...TypeRef
|
||||
}
|
||||
}
|
||||
|
||||
fragment InputValue on __InputValue {
|
||||
name
|
||||
description
|
||||
type { ...TypeRef }
|
||||
defaultValue
|
||||
}
|
||||
|
||||
fragment TypeRef on __Type {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOD;
|
||||
$withoutDescription = <<<'EOD'
|
||||
query IntrospectionQuery {
|
||||
__schema {
|
||||
queryType { name }
|
||||
mutationType { name }
|
||||
types {
|
||||
...FullType
|
||||
}
|
||||
directives {
|
||||
name
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
onOperation
|
||||
onFragment
|
||||
onField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment FullType on __Type {
|
||||
kind
|
||||
name
|
||||
fields {
|
||||
name
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
type {
|
||||
...TypeRef
|
||||
}
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
inputFields {
|
||||
...InputValue
|
||||
}
|
||||
interfaces {
|
||||
...TypeRef
|
||||
}
|
||||
enumValues {
|
||||
name
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
possibleTypes {
|
||||
...TypeRef
|
||||
}
|
||||
}
|
||||
|
||||
fragment InputValue on __InputValue {
|
||||
name
|
||||
type { ...TypeRef }
|
||||
defaultValue
|
||||
}
|
||||
|
||||
fragment TypeRef on __Type {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOD;
|
||||
return $includeDescription ? $withDescription : $withoutDescription;
|
||||
}
|
||||
|
||||
public static function _schema()
|
||||
{
|
||||
if (!isset(self::$_map['__Schema'])) {
|
||||
@ -83,12 +244,15 @@ class Introspection
|
||||
self::$_map['__Directive'] = new ObjectType([
|
||||
'name' => '__Directive',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => ['type' => Type::nonNull(Type::string())],
|
||||
'description' => ['type' => Type::string()],
|
||||
'type' => ['type' => [__CLASS__, '_type']],
|
||||
'onOperation' => ['type' => Type::boolean()],
|
||||
'onFragment' => ['type' => Type::boolean()],
|
||||
'onField' => ['type' => Type::boolean()]
|
||||
'args' => [
|
||||
'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
|
||||
'resolve' => function(Directive $directive) {return $directive->args ?: [];}
|
||||
],
|
||||
'onOperation' => ['type' => Type::nonNull(Type::boolean())],
|
||||
'onFragment' => ['type' => Type::nonNull(Type::boolean())],
|
||||
'onField' => ['type' => Type::nonNull(Type::boolean())]
|
||||
]
|
||||
]);
|
||||
}
|
||||
@ -349,14 +513,9 @@ class Introspection
|
||||
'resolve' => function (
|
||||
$source,
|
||||
$args,
|
||||
$root,
|
||||
$fieldAST,
|
||||
$fieldType,
|
||||
$parentType,
|
||||
$schema
|
||||
ResolveInfo $info
|
||||
) {
|
||||
// TODO: move 3+ args to separate object
|
||||
return $schema;
|
||||
return $info->schema;
|
||||
}
|
||||
]);
|
||||
}
|
||||
@ -373,8 +532,8 @@ class Introspection
|
||||
'args' => [
|
||||
['name' => 'name', 'type' => Type::nonNull(Type::string())]
|
||||
],
|
||||
'resolve' => function ($source, $args, $root, $fieldAST, $fieldType, $parentType, $schema) {
|
||||
return $schema->getType($args['name']);
|
||||
'resolve' => function ($source, $args, ResolveInfo $info) {
|
||||
return $info->schema->getType($args['name']);
|
||||
}
|
||||
]);
|
||||
}
|
||||
@ -392,12 +551,9 @@ class Introspection
|
||||
'resolve' => function (
|
||||
$source,
|
||||
$args,
|
||||
$root,
|
||||
$fieldAST,
|
||||
$fieldType,
|
||||
$parentType
|
||||
ResolveInfo $info
|
||||
) {
|
||||
return $parentType->name;
|
||||
return $info->parentType->name;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
@ -169,11 +169,6 @@ class SchemaValidator
|
||||
$errors = array_merge($errors, $newErrors);
|
||||
}
|
||||
}
|
||||
$isValid = empty($errors);
|
||||
$result = [
|
||||
'isValid' => $isValid,
|
||||
'errors' => $isValid ? null : array_map(['GraphQL\Error', 'formatError'], $errors)
|
||||
];
|
||||
return (object) $result;
|
||||
return $errors;
|
||||
}
|
||||
}
|
@ -4,9 +4,12 @@ namespace GraphQL\Utils;
|
||||
use GraphQL\Language\AST\Field;
|
||||
use GraphQL\Language\AST\ListType;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NonNullType;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\FieldArgument;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
@ -38,8 +41,8 @@ class TypeInfo
|
||||
return $innerType ? new NonNull($innerType) : null;
|
||||
}
|
||||
|
||||
Utils::invariant($inputTypeAst instanceof Name, 'Must be a type name');
|
||||
return $schema->getType($inputTypeAst->value);
|
||||
Utils::invariant($inputTypeAst->kind === Node::NAMED_TYPE, 'Must be a named type');
|
||||
return $schema->getType($inputTypeAst->name->value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,6 +106,15 @@ class TypeInfo
|
||||
*/
|
||||
private $_fieldDefStack;
|
||||
|
||||
/**
|
||||
* @var Directive
|
||||
*/
|
||||
private $_directive;
|
||||
|
||||
/**
|
||||
* @var FieldArgument
|
||||
*/
|
||||
private $_argument;
|
||||
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
@ -157,6 +169,21 @@ class TypeInfo
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directive|null
|
||||
*/
|
||||
function getDirective()
|
||||
{
|
||||
return $this->_directive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FieldArgument|null
|
||||
*/
|
||||
function getArgument()
|
||||
{
|
||||
return $this->_argument;
|
||||
}
|
||||
|
||||
function enter(Node $node)
|
||||
{
|
||||
@ -164,16 +191,19 @@ class TypeInfo
|
||||
|
||||
switch ($node->kind) {
|
||||
case Node::SELECTION_SET:
|
||||
// var $compositeType: ?GraphQLCompositeType;
|
||||
$rawType = Type::getUnmodifiedType($this->getType());
|
||||
$namedType = Type::getNamedType($this->getType());
|
||||
$compositeType = null;
|
||||
if (Type::isCompositeType($rawType)) {
|
||||
if (Type::isCompositeType($namedType)) {
|
||||
// isCompositeType is a type refining predicate, so this is safe.
|
||||
$compositeType = $rawType;
|
||||
$compositeType = $namedType;
|
||||
}
|
||||
array_push($this->_parentTypeStack, $compositeType);
|
||||
break;
|
||||
|
||||
case Node::DIRECTIVE:
|
||||
$this->_directive = $schema->getDirective($node->name->value);
|
||||
break;
|
||||
|
||||
case Node::FIELD:
|
||||
$parentType = $this->getParentType();
|
||||
$fieldDef = null;
|
||||
@ -196,7 +226,7 @@ class TypeInfo
|
||||
|
||||
case Node::INLINE_FRAGMENT:
|
||||
case Node::FRAGMENT_DEFINITION:
|
||||
$type = $schema->getType($node->typeCondition->value);
|
||||
$type = self::typeFromAST($schema, $node->typeCondition);
|
||||
array_push($this->_typeStack, $type);
|
||||
break;
|
||||
|
||||
@ -205,32 +235,28 @@ class TypeInfo
|
||||
break;
|
||||
|
||||
case Node::ARGUMENT:
|
||||
$field = $this->getFieldDef();
|
||||
$argType = null;
|
||||
if ($field) {
|
||||
$argDef = Utils::find($field->args, function($arg) use ($node) {return $arg->name === $node->name->value;});
|
||||
$fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
|
||||
$argDef = $argType = null;
|
||||
if ($fieldOrDirective) {
|
||||
$argDef = Utils::find($fieldOrDirective->args, function($arg) use ($node) {return $arg->name === $node->name->value;});
|
||||
if ($argDef) {
|
||||
$argType = $argDef->getType();
|
||||
}
|
||||
}
|
||||
$this->_argument = $argDef;
|
||||
array_push($this->_inputTypeStack, $argType);
|
||||
break;
|
||||
|
||||
case Node::DIRECTIVE:
|
||||
$directive = $schema->getDirective($node->name->value);
|
||||
array_push($this->_inputTypeStack, $directive ? $directive->type : null);
|
||||
break;
|
||||
|
||||
case Node::LST:
|
||||
$arrayType = Type::getNullableType($this->getInputType());
|
||||
$listType = Type::getNullableType($this->getInputType());
|
||||
array_push(
|
||||
$this->_inputTypeStack,
|
||||
$arrayType instanceof ListOfType ? $arrayType->getWrappedType() : null
|
||||
$listType instanceof ListOfType ? $listType->getWrappedType() : null
|
||||
);
|
||||
break;
|
||||
|
||||
case Node::OBJECT_FIELD:
|
||||
$objectType = Type::getUnmodifiedType($this->getInputType());
|
||||
$objectType = Type::getNamedType($this->getInputType());
|
||||
$fieldType = null;
|
||||
if ($objectType instanceof InputObjectType) {
|
||||
$tmp = $objectType->getFields();
|
||||
@ -248,10 +274,16 @@ class TypeInfo
|
||||
case Node::SELECTION_SET:
|
||||
array_pop($this->_parentTypeStack);
|
||||
break;
|
||||
|
||||
case Node::FIELD:
|
||||
array_pop($this->_fieldDefStack);
|
||||
array_pop($this->_typeStack);
|
||||
break;
|
||||
|
||||
case Node::DIRECTIVE:
|
||||
$this->_directive = null;
|
||||
break;
|
||||
|
||||
case Node::OPERATION_DEFINITION:
|
||||
case Node::INLINE_FRAGMENT:
|
||||
case Node::FRAGMENT_DEFINITION:
|
||||
@ -261,9 +293,9 @@ class TypeInfo
|
||||
array_pop($this->_inputTypeStack);
|
||||
break;
|
||||
case Node::ARGUMENT:
|
||||
$this->_argument = null;
|
||||
array_pop($this->_inputTypeStack);
|
||||
break;
|
||||
case Node::DIRECTIVE:
|
||||
case Node::LST:
|
||||
case Node::OBJECT_FIELD:
|
||||
array_pop($this->_inputTypeStack);
|
||||
|
@ -2,7 +2,7 @@
|
||||
namespace GraphQL\Validator;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\ArrayValue;
|
||||
use GraphQL\Language\AST\ListValue;
|
||||
use GraphQL\Language\AST\Document;
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\Node;
|
||||
@ -33,6 +33,7 @@ use GraphQL\Validator\Rules\NoUnusedFragments;
|
||||
use GraphQL\Validator\Rules\NoUnusedVariables;
|
||||
use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged;
|
||||
use GraphQL\Validator\Rules\PossibleFragmentSpreads;
|
||||
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
||||
use GraphQL\Validator\Rules\ScalarLeafs;
|
||||
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
||||
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
||||
@ -45,23 +46,28 @@ class DocumentValidator
|
||||
{
|
||||
if (null === self::$allRules) {
|
||||
self::$allRules = [
|
||||
new ArgumentsOfCorrectType(),
|
||||
new DefaultValuesOfCorrectType(),
|
||||
new FieldsOnCorrectType(),
|
||||
new FragmentsOnCompositeTypes(),
|
||||
new KnownArgumentNames(),
|
||||
new KnownDirectives(),
|
||||
new KnownFragmentNames(),
|
||||
new KnownTypeNames(),
|
||||
new NoFragmentCycles(),
|
||||
new NoUndefinedVariables(),
|
||||
new NoUnusedFragments(),
|
||||
new NoUnusedVariables(),
|
||||
new OverlappingFieldsCanBeMerged(),
|
||||
new PossibleFragmentSpreads(),
|
||||
new ScalarLeafs(),
|
||||
new VariablesAreInputTypes(),
|
||||
new VariablesInAllowedPosition()
|
||||
// new UniqueOperationNames,
|
||||
// new LoneAnonymousOperation,
|
||||
new KnownTypeNames,
|
||||
new FragmentsOnCompositeTypes,
|
||||
new VariablesAreInputTypes,
|
||||
new ScalarLeafs,
|
||||
new FieldsOnCorrectType,
|
||||
// new UniqueFragmentNames,
|
||||
new KnownFragmentNames,
|
||||
new NoUnusedFragments,
|
||||
new PossibleFragmentSpreads,
|
||||
new NoFragmentCycles,
|
||||
new NoUndefinedVariables,
|
||||
new NoUnusedVariables,
|
||||
new KnownDirectives,
|
||||
new KnownArgumentNames,
|
||||
// new UniqueArgumentNames,
|
||||
new ArgumentsOfCorrectType,
|
||||
new ProvidedNonNullArguments,
|
||||
new DefaultValuesOfCorrectType,
|
||||
new VariablesInAllowedPosition,
|
||||
new OverlappingFieldsCanBeMerged,
|
||||
];
|
||||
}
|
||||
return self::$allRules;
|
||||
@ -70,13 +76,7 @@ class DocumentValidator
|
||||
public static function validate(Schema $schema, Document $ast, array $rules = null)
|
||||
{
|
||||
$errors = self::visitUsingRules($schema, $ast, $rules ?: self::allRules());
|
||||
$isValid = empty($errors);
|
||||
|
||||
$result = [
|
||||
'isValid' => $isValid,
|
||||
'errors' => $isValid ? null : array_map(['GraphQL\Error', 'formatError'], $errors)
|
||||
];
|
||||
return $result;
|
||||
return $errors;
|
||||
}
|
||||
|
||||
static function isError($value)
|
||||
@ -121,7 +121,7 @@ class DocumentValidator
|
||||
// Lists accept a non-list value as a list of one.
|
||||
if ($type instanceof ListOfType) {
|
||||
$itemType = $type->getWrappedType();
|
||||
if ($valueAST instanceof ArrayValue) {
|
||||
if ($valueAST instanceof ListValue) {
|
||||
foreach($valueAST->values as $itemAST) {
|
||||
if (!self::isValidLiteralValue($itemAST, $itemType)) {
|
||||
return false;
|
||||
@ -133,10 +133,10 @@ class DocumentValidator
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar/Enum input checks to ensure the type can coerce the value to
|
||||
// Scalar/Enum input checks to ensure the type can serialize the value to
|
||||
// a non-null value.
|
||||
if ($type instanceof ScalarType || $type instanceof EnumType) {
|
||||
return $type->coerceLiteral($valueAST) !== null;
|
||||
return $type->parseLiteral($valueAST) !== null;
|
||||
}
|
||||
|
||||
// Input objects check each defined field, ensuring it is of the correct
|
||||
@ -294,7 +294,7 @@ class DocumentValidator
|
||||
if ($result->doBreak) {
|
||||
$instances[$i] = null;
|
||||
}
|
||||
} if (self::isError($result)) {
|
||||
} else if (self::isError($result)) {
|
||||
self::append($errors, $result);
|
||||
} else if ($result !== null) {
|
||||
throw new \Exception("Config cannot edit document.");
|
||||
|
@ -16,53 +16,23 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class ArgumentsOfCorrectType
|
||||
{
|
||||
static function badValueMessage($argName, $type, $value)
|
||||
{
|
||||
return "Argument \"$argName\" expected type \"$type\" but got: $value.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::FIELD => function(Field $fieldAST) use ($context) {
|
||||
$fieldDef = $context->getFieldDef();
|
||||
if (!$fieldDef) {
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
$errors = [];
|
||||
$argASTs = $fieldAST->arguments ?: [];
|
||||
$argASTMap = Utils::keyMap($argASTs, function (Argument $arg) {
|
||||
return $arg->name->value;
|
||||
});
|
||||
|
||||
foreach ($fieldDef->args as $argDef) {
|
||||
$argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
|
||||
if (!$argAST && $argDef->getType() instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
Messages::missingArgMessage(
|
||||
$fieldAST->name->value,
|
||||
$argDef->name,
|
||||
$argDef->getType()
|
||||
),
|
||||
[$fieldAST]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$argDefMap = Utils::keyMap($fieldDef->args, function ($def) {
|
||||
return $def->name;
|
||||
});
|
||||
foreach ($argASTs as $argAST) {
|
||||
$argDef = $argDefMap[$argAST->name->value];
|
||||
Node::ARGUMENT => function(Argument $argAST) use ($context) {
|
||||
$argDef = $context->getArgument();
|
||||
if ($argDef && !DocumentValidator::isValidLiteralValue($argAST->value, $argDef->getType())) {
|
||||
$errors[] = new Error(
|
||||
Messages::badValueMessage(
|
||||
$argAST->name->value,
|
||||
$argDef->getType(),
|
||||
Printer::doPrint($argAST->value)
|
||||
),
|
||||
return new Error(
|
||||
self::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value)),
|
||||
[$argAST->value]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($errors) ? $errors : null;
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -6,35 +6,43 @@ use GraphQL\Error;
|
||||
use GraphQL\Language\AST\FragmentDefinition;
|
||||
use GraphQL\Language\AST\InlineFragment;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Type\Definition\CompositeType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class FragmentsOnCompositeTypes
|
||||
{
|
||||
static function inlineFragmentOnNonCompositeErrorMessage($type)
|
||||
{
|
||||
return "Fragment cannot condition on non composite type \"$type\".";
|
||||
}
|
||||
|
||||
static function fragmentOnNonCompositeErrorMessage($fragName, $type)
|
||||
{
|
||||
return "Fragment \"$fragName\" cannot condition on non composite type \"$type\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::INLINE_FRAGMENT => function(InlineFragment $node) use ($context) {
|
||||
$typeName = $node->typeCondition->value;
|
||||
$type = $context->getSchema()->getType($typeName);
|
||||
$isCompositeType = $type instanceof CompositeType;
|
||||
$type = $context->getType();
|
||||
|
||||
if (!$isCompositeType) {
|
||||
if ($type && !Type::isCompositeType($type)) {
|
||||
return new Error(
|
||||
"Fragment cannot condition on non composite type \"$typeName\".",
|
||||
self::inlineFragmentOnNonCompositeErrorMessage($type),
|
||||
[$node->typeCondition]
|
||||
);
|
||||
}
|
||||
},
|
||||
Node::FRAGMENT_DEFINITION => function(FragmentDefinition $node) use ($context) {
|
||||
$typeName = $node->typeCondition->value;
|
||||
$type = $context->getSchema()->getType($typeName);
|
||||
$isCompositeType = $type instanceof CompositeType;
|
||||
$type = $context->getType();
|
||||
|
||||
if (!$isCompositeType) {
|
||||
if ($type && !Type::isCompositeType($type)) {
|
||||
return new Error(
|
||||
Messages::fragmentOnNonCompositeErrorMessage($node->name->value, $typeName),
|
||||
self::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)),
|
||||
[$node->typeCondition]
|
||||
);
|
||||
}
|
||||
|
@ -11,29 +11,59 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class KnownArgumentNames
|
||||
{
|
||||
public static function unknownArgMessage($argName, $fieldName, $type)
|
||||
{
|
||||
return "Unknown argument \"$argName\" on field \"$fieldName\" of type \"$type\".";
|
||||
}
|
||||
|
||||
public static function unknownDirectiveArgMessage($argName, $directiveName)
|
||||
{
|
||||
return "Unknown argument \"$argName\" on directive \"@$directiveName\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::ARGUMENT => function(Argument $node) use ($context) {
|
||||
Node::ARGUMENT => function(Argument $node, $key, $parent, $path, $ancestors) use ($context) {
|
||||
$argumentOf = $ancestors[count($ancestors) - 1];
|
||||
if ($argumentOf->kind === Node::FIELD) {
|
||||
$fieldDef = $context->getFieldDef();
|
||||
|
||||
if ($fieldDef) {
|
||||
$argDef = null;
|
||||
$fieldArgDef = null;
|
||||
foreach ($fieldDef->args as $arg) {
|
||||
if ($arg->name === $node->name->value) {
|
||||
$argDef = $arg;
|
||||
$fieldArgDef = $arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$argDef) {
|
||||
if (!$fieldArgDef) {
|
||||
$parentType = $context->getParentType();
|
||||
Utils::invariant($parentType);
|
||||
return new Error(
|
||||
Messages::unknownArgMessage($node->name->value, $fieldDef->name, $parentType->name),
|
||||
self::unknownArgMessage($node->name->value, $fieldDef->name, $parentType->name),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if ($argumentOf->kind === Node::DIRECTIVE) {
|
||||
$directive = $context->getDirective();
|
||||
if ($directive) {
|
||||
$directiveArgDef = null;
|
||||
foreach ($directive->args as $arg) {
|
||||
if ($arg->name === $node->name->value) {
|
||||
$directiveArgDef = $arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$directiveArgDef) {
|
||||
return new Error(
|
||||
self::unknownDirectiveArgMessage($node->name->value, $directive->name),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -15,6 +15,16 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class KnownDirectives
|
||||
{
|
||||
static function unknownDirectiveMessage($directiveName)
|
||||
{
|
||||
return "Unknown directive \"$directiveName\".";
|
||||
}
|
||||
|
||||
static function misplacedDirectiveMessage($directiveName, $placement)
|
||||
{
|
||||
return "Directive \"$directiveName\" may not be used on \"$placement\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
@ -29,7 +39,7 @@ class KnownDirectives
|
||||
|
||||
if (!$directiveDef) {
|
||||
return new Error(
|
||||
Messages::unknownDirectiveMessage($node->name->value),
|
||||
self::unknownDirectiveMessage($node->name->value),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
@ -37,13 +47,13 @@ class KnownDirectives
|
||||
|
||||
if ($appliedTo instanceof OperationDefinition && !$directiveDef->onOperation) {
|
||||
return new Error(
|
||||
Messages::misplacedDirectiveMessage($node->name->value, 'operation'),
|
||||
self::misplacedDirectiveMessage($node->name->value, 'operation'),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
if ($appliedTo instanceof Field && !$directiveDef->onField) {
|
||||
return new Error(
|
||||
Messages::misplacedDirectiveMessage($node->name->value, 'field'),
|
||||
self::misplacedDirectiveMessage($node->name->value, 'field'),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
@ -56,7 +66,7 @@ class KnownDirectives
|
||||
|
||||
if ($fragmentKind && !$directiveDef->onFragment) {
|
||||
return new Error(
|
||||
Messages::misplacedDirectiveMessage($node->name->value, 'fragment'),
|
||||
self::misplacedDirectiveMessage($node->name->value, 'fragment'),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,11 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class KnownFragmentNames
|
||||
{
|
||||
static function unknownFragmentMessage($fragName)
|
||||
{
|
||||
return "Unknown fragment \"$fragName\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
@ -17,7 +22,7 @@ class KnownFragmentNames
|
||||
$fragment = $context->getFragment($fragmentName);
|
||||
if (!$fragment) {
|
||||
return new Error(
|
||||
"Undefined fragment $fragmentName.",
|
||||
self::unknownFragmentMessage($fragmentName),
|
||||
[$node->name]
|
||||
);
|
||||
}
|
||||
|
@ -4,22 +4,27 @@ namespace GraphQL\Validator\Rules;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class KnownTypeNames
|
||||
{
|
||||
static function unknownTypeMessage($type)
|
||||
{
|
||||
return "Unknown type \"$type\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::NAME => function(Name $node, $key) use ($context) {
|
||||
|
||||
Node::NAMED_TYPE => function(NamedType $node, $key) use ($context) {
|
||||
if ($key === 'type' || $key === 'typeCondition') {
|
||||
$typeName = $node->value;
|
||||
$typeName = $node->name->value;
|
||||
$type = $context->getSchema()->getType($typeName);
|
||||
if (!$type) {
|
||||
return new Error(Messages::unknownTypeMessage($typeName), [$node]);
|
||||
return new Error(self::unknownTypeMessage($typeName), [$node]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,16 @@ use GraphQL\Language\AST\FragmentDefinition;
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class NoFragmentCycles
|
||||
{
|
||||
static function cycleErrorMessage($fragName, array $spreadNames = [])
|
||||
{
|
||||
$via = !empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : '';
|
||||
return "Cannot spread fragment \"$fragName\" within itself$via.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
// Gather all the fragment spreads ASTs for each fragment definition.
|
||||
@ -67,7 +72,7 @@ class NoFragmentCycles
|
||||
$knownToLeadToCycle[$spread] = true;
|
||||
}
|
||||
$errors[] = new Error(
|
||||
Messages::cycleErrorMessage($initialName, array_map(function ($s) {
|
||||
self::cycleErrorMessage($initialName, array_map(function ($s) {
|
||||
return $s->name->value;
|
||||
}, $spreadPath)),
|
||||
$cyclePath
|
||||
|
@ -23,6 +23,16 @@ use GraphQL\Validator\ValidationContext;
|
||||
*/
|
||||
class NoUndefinedVariables
|
||||
{
|
||||
static function undefinedVarMessage($varName)
|
||||
{
|
||||
return "Variable \"$$varName\" is not defined.";
|
||||
}
|
||||
|
||||
static function undefinedVarByOpMessage($varName, $opName)
|
||||
{
|
||||
return "Variable \"$$varName\" is not defined by operation \"$opName\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$operation = null;
|
||||
@ -53,12 +63,12 @@ class NoUndefinedVariables
|
||||
}
|
||||
if ($withinFragment && $operation && $operation->name) {
|
||||
return new Error(
|
||||
Messages::undefinedVarByOpMessage($varName, $operation->name->value),
|
||||
self::undefinedVarByOpMessage($varName, $operation->name->value),
|
||||
[$variable, $operation]
|
||||
);
|
||||
}
|
||||
return new Error(
|
||||
Messages::undefinedVarMessage($varName),
|
||||
self::undefinedVarMessage($varName),
|
||||
[$variable]
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,11 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class NoUnusedFragments
|
||||
{
|
||||
static function unusedFragMessage($fragName)
|
||||
{
|
||||
return "Fragment \"$fragName\" is never used.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$fragmentDefs = [];
|
||||
@ -43,7 +48,7 @@ class NoUnusedFragments
|
||||
foreach ($fragmentDefs as $def) {
|
||||
if (empty($fragmentNameUsed[$def->name->value])) {
|
||||
$errors[] = new Error(
|
||||
Messages::unusedFragMessage($def->name->value),
|
||||
self::unusedFragMessage($def->name->value),
|
||||
[$def]
|
||||
);
|
||||
}
|
||||
@ -59,6 +64,8 @@ class NoUnusedFragments
|
||||
foreach ($spreads as $fragName => $fragment) {
|
||||
if (empty($fragmentNameUsed[$fragName])) {
|
||||
$fragmentNameUsed[$fragName] = true;
|
||||
|
||||
if (isset($fragAdjacencies->{$fragName})) {
|
||||
$this->reduceSpreadFragments(
|
||||
$fragAdjacencies->{$fragName},
|
||||
$fragmentNameUsed,
|
||||
@ -68,3 +75,4 @@ class NoUnusedFragments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,11 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class NoUnusedVariables
|
||||
{
|
||||
static function unusedVariableMessage($varName)
|
||||
{
|
||||
return "Variable \"$$varName\" is never used.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$visitedFragmentNames = new \stdClass();
|
||||
@ -30,7 +35,7 @@ class NoUnusedVariables
|
||||
foreach ($variableDefs as $def) {
|
||||
if (empty($variableNameUsed->{$def->variable->name->value})) {
|
||||
$errors[] = new Error(
|
||||
Messages::unusedVariableMessage($def->variable->name->value),
|
||||
self::unusedVariableMessage($def->variable->name->value),
|
||||
[$def]
|
||||
);
|
||||
}
|
||||
|
@ -3,19 +3,40 @@ namespace GraphQL\Validator\Rules;
|
||||
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Directive;
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\InlineFragment;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\SelectionSet;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Utils\PairSet;
|
||||
use GraphQL\Utils\TypeInfo;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class OverlappingFieldsCanBeMerged
|
||||
{
|
||||
static function fieldsConflictMessage($responseName, $reason)
|
||||
{
|
||||
$reasonMessage = self::reasonMessage($reason);
|
||||
return "Fields \"$responseName\" conflict because $reasonMessage.";
|
||||
}
|
||||
|
||||
static function reasonMessage($reason)
|
||||
{
|
||||
if (is_array($reason)) {
|
||||
$tmp = array_map(function ($tmp) {
|
||||
list($responseName, $subReason) = $tmp;
|
||||
$reasonMessage = self::reasonMessage($subReason);
|
||||
return "subfields \"$responseName\" conflict because $reasonMessage";
|
||||
}, $reason);
|
||||
return implode(' and ', $tmp);
|
||||
}
|
||||
return $reason;
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$comparedSet = new PairSet();
|
||||
@ -27,7 +48,7 @@ class OverlappingFieldsCanBeMerged
|
||||
'leave' => function(SelectionSet $selectionSet) use ($context, $comparedSet) {
|
||||
$fieldMap = $this->collectFieldASTsAndDefs(
|
||||
$context,
|
||||
$context->getType(),
|
||||
$context->getParentType(),
|
||||
$selectionSet
|
||||
);
|
||||
|
||||
@ -37,11 +58,11 @@ class OverlappingFieldsCanBeMerged
|
||||
return array_map(function ($conflict) {
|
||||
$responseName = $conflict[0][0];
|
||||
$reason = $conflict[0][1];
|
||||
$blameNodes = $conflict[1];
|
||||
$fields = $conflict[1];
|
||||
|
||||
return new Error(
|
||||
Messages::fieldsConflictMessage($responseName, $reason),
|
||||
$blameNodes
|
||||
self::fieldsConflictMessage($responseName, $reason),
|
||||
$fields
|
||||
);
|
||||
}, $conflicts);
|
||||
|
||||
@ -101,7 +122,7 @@ class OverlappingFieldsCanBeMerged
|
||||
$type1 = isset($def1) ? $def1->getType() : null;
|
||||
$type2 = isset($def2) ? $def2->getType() : null;
|
||||
|
||||
if (!$this->sameType($type1, $type2)) {
|
||||
if ($type1 && $type2 && !$this->sameType($type1, $type2)) {
|
||||
return [
|
||||
[$responseName, "they return differing types $type1 and $type2"],
|
||||
[$ast1, $ast2]
|
||||
@ -111,7 +132,7 @@ class OverlappingFieldsCanBeMerged
|
||||
$args1 = isset($ast1->arguments) ? $ast1->arguments : [];
|
||||
$args2 = isset($ast2->arguments) ? $ast2->arguments : [];
|
||||
|
||||
if (!$this->sameNameValuePairs($args1, $args2)) {
|
||||
if (!$this->sameArguments($args1, $args2)) {
|
||||
return [
|
||||
[$responseName, 'they have differing arguments'],
|
||||
[$ast1, $ast2]
|
||||
@ -121,7 +142,7 @@ class OverlappingFieldsCanBeMerged
|
||||
$directives1 = isset($ast1->directives) ? $ast1->directives : [];
|
||||
$directives2 = isset($ast2->directives) ? $ast2->directives : [];
|
||||
|
||||
if (!$this->sameNameValuePairs($directives1, $directives2)) {
|
||||
if (!$this->sameDirectives($directives1, $directives2)) {
|
||||
return [
|
||||
[$responseName, 'they have differing directives'],
|
||||
[$ast1, $ast2]
|
||||
@ -136,13 +157,13 @@ class OverlappingFieldsCanBeMerged
|
||||
|
||||
$subfieldMap = $this->collectFieldASTsAndDefs(
|
||||
$context,
|
||||
$type1,
|
||||
Type::getNamedType($type1),
|
||||
$selectionSet1,
|
||||
$visitedFragmentNames
|
||||
);
|
||||
$subfieldMap = $this->collectFieldASTsAndDefs(
|
||||
$context,
|
||||
$type2,
|
||||
Type::getNamedType($type2),
|
||||
$selectionSet2,
|
||||
$visitedFragmentNames,
|
||||
$subfieldMap
|
||||
@ -152,7 +173,7 @@ class OverlappingFieldsCanBeMerged
|
||||
if (!empty($conflicts)) {
|
||||
return [
|
||||
[$responseName, array_map(function ($conflict) { return $conflict[0]; }, $conflicts)],
|
||||
array_reduce($conflicts, function ($list, $conflict) { return array_merge($list, $conflict[1]); }, [$ast1, $ast2])
|
||||
array_reduce($conflicts, function ($allFields, $conflict) { return array_merge($allFields, $conflict[1]); }, [$ast1, $ast2])
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -183,8 +204,7 @@ class OverlappingFieldsCanBeMerged
|
||||
|
||||
switch ($selection->kind) {
|
||||
case Node::FIELD:
|
||||
$fieldAST = $selection;
|
||||
$fieldName = $fieldAST->name->value;
|
||||
$fieldName = $selection->name->value;
|
||||
$fieldDef = null;
|
||||
if ($parentType && method_exists($parentType, 'getFields')) {
|
||||
$tmp = $parentType->getFields();
|
||||
@ -192,28 +212,26 @@ class OverlappingFieldsCanBeMerged
|
||||
$fieldDef = $tmp[$fieldName];
|
||||
}
|
||||
}
|
||||
$responseName = $fieldAST->alias ? $fieldAST->alias->value : $fieldName;
|
||||
$responseName = $selection->alias ? $selection->alias->value : $fieldName;
|
||||
|
||||
if (!isset($_astAndDefs[$responseName])) {
|
||||
$_astAndDefs[$responseName] = new \ArrayObject();
|
||||
}
|
||||
$_astAndDefs[$responseName][] = [$fieldAST, $fieldDef];
|
||||
$_astAndDefs[$responseName][] = [$selection, $fieldDef];
|
||||
break;
|
||||
case Node::INLINE_FRAGMENT:
|
||||
/** @var InlineFragment $inlineFragment */
|
||||
$inlineFragment = $selection;
|
||||
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
||||
$context,
|
||||
TypeInfo::typeFromAST($context->getSchema(), $inlineFragment->typeCondition),
|
||||
$inlineFragment->selectionSet,
|
||||
TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
|
||||
$selection->selectionSet,
|
||||
$_visitedFragmentNames,
|
||||
$_astAndDefs
|
||||
);
|
||||
break;
|
||||
case Node::FRAGMENT_SPREAD:
|
||||
/** @var FragmentSpread $fragmentSpread */
|
||||
$fragmentSpread = $selection;
|
||||
$fragName = $fragmentSpread->name->value;
|
||||
/** @var FragmentSpread $selection */
|
||||
$fragName = $selection->name->value;
|
||||
if (!empty($_visitedFragmentNames[$fragName])) {
|
||||
continue;
|
||||
}
|
||||
@ -235,29 +253,53 @@ class OverlappingFieldsCanBeMerged
|
||||
return $_astAndDefs;
|
||||
}
|
||||
|
||||
private function sameDirectives(array $directives1, array $directives2)
|
||||
{
|
||||
if (count($directives1) !== count($directives2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($directives1 as $directive1) {
|
||||
$directive2 = null;
|
||||
foreach ($directives2 as $tmp) {
|
||||
if ($tmp->name->value === $directive1->name->value) {
|
||||
$directive2 = $tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$directive2) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->sameArguments($directive1->arguments, $directive2->arguments)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Array<Argument | Directive> $pairs1
|
||||
* @param Array<Argument | Directive> $pairs2
|
||||
* @return bool|string
|
||||
*/
|
||||
private function sameNameValuePairs(array $pairs1, array $pairs2)
|
||||
private function sameArguments(array $arguments1, array $arguments2)
|
||||
{
|
||||
if (count($pairs1) !== count($pairs2)) {
|
||||
if (count($arguments1) !== count($arguments2)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($pairs1 as $pair1) {
|
||||
$matchedPair2 = null;
|
||||
foreach ($pairs2 as $pair2) {
|
||||
if ($pair2->name->value === $pair1->name->value) {
|
||||
$matchedPair2 = $pair2;
|
||||
foreach ($arguments1 as $arg1) {
|
||||
$arg2 = null;
|
||||
foreach ($arguments2 as $arg) {
|
||||
if ($arg->name->value === $arg1->name->value) {
|
||||
$arg2 = $arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$matchedPair2) {
|
||||
if (!$arg2) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->sameValue($pair1->value, $matchedPair2->value)) {
|
||||
if (!$this->sameValue($arg1->value, $arg2->value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -271,6 +313,6 @@ class OverlappingFieldsCanBeMerged
|
||||
|
||||
function sameType($type1, $type2)
|
||||
{
|
||||
return (!$type1 && !$type2) || (string) $type1 === (string) $type2;
|
||||
return (string) $type1 === (string) $type2;
|
||||
}
|
||||
}
|
||||
|
@ -11,32 +11,41 @@ use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class PossibleFragmentSpreads
|
||||
{
|
||||
static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
|
||||
{
|
||||
return "Fragment \"$fragName\" cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
|
||||
}
|
||||
|
||||
static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
|
||||
{
|
||||
return "Fragment cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::INLINE_FRAGMENT => function(InlineFragment $node) use ($context) {
|
||||
$fragType = Type::getUnmodifiedType($context->getType());
|
||||
$fragType = Type::getNamedType($context->getType());
|
||||
$parentType = $context->getParentType();
|
||||
if ($fragType && $parentType && !$this->doTypesOverlap($fragType, $parentType)) {
|
||||
return new Error(
|
||||
Messages::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
||||
self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
},
|
||||
Node::FRAGMENT_SPREAD => function(FragmentSpread $node) use ($context) {
|
||||
$fragName = $node->name->value;
|
||||
$fragType = Type::getUnmodifiedType($this->getFragmentType($context, $fragName));
|
||||
$fragType = Type::getNamedType($this->getFragmentType($context, $fragName));
|
||||
$parentType = $context->getParentType();
|
||||
|
||||
if ($fragType && $parentType && !$this->doTypesOverlap($fragType, $parentType)) {
|
||||
return new Error(
|
||||
Messages::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
||||
self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
@ -47,7 +56,7 @@ class PossibleFragmentSpreads
|
||||
private function getFragmentType(ValidationContext $context, $name)
|
||||
{
|
||||
$frag = $context->getFragment($name);
|
||||
return $frag ? $context->getSchema()->getType($frag->typeCondition->value) : null;
|
||||
return $frag ? Utils\TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition) : null;
|
||||
}
|
||||
|
||||
private function doTypesOverlap($t1, $t2)
|
||||
|
87
src/Validator/Rules/ProvidedNonNullArguments.php
Normal file
87
src/Validator/Rules/ProvidedNonNullArguments.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
namespace GraphQL\Validator\Rules;
|
||||
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Directive;
|
||||
use GraphQL\Language\AST\Field;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class ProvidedNonNullArguments
|
||||
{
|
||||
static function missingFieldArgMessage($fieldName, $argName, $type)
|
||||
{
|
||||
return "Field \"$fieldName\" argument \"$argName\" of type \"$type\" is required but not provided.";
|
||||
}
|
||||
|
||||
static function missingDirectiveArgMessage($directiveName, $argName, $type)
|
||||
{
|
||||
return "Directive \"@$directiveName\" argument \"$argName\" of type \"$type\" is required but not provided.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::FIELD => [
|
||||
'leave' => function(Field $fieldAST) use ($context) {
|
||||
$fieldDef = $context->getFieldDef();
|
||||
|
||||
if (!$fieldDef) {
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
$errors = [];
|
||||
$argASTs = $fieldAST->arguments ?: [];
|
||||
|
||||
$argASTMap = [];
|
||||
foreach ($argASTs as $argAST) {
|
||||
$argASTMap[$argAST->name->value] = $argASTs;
|
||||
}
|
||||
foreach ($fieldDef->args as $argDef) {
|
||||
$argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
|
||||
if (!$argAST && $argDef->getType() instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
self::missingFieldArgMessage($fieldAST->name->value, $argDef->name, $argDef->getType()),
|
||||
[$fieldAST]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
],
|
||||
Node::DIRECTIVE => [
|
||||
'leave' => function(Directive $directiveAST) use ($context) {
|
||||
$directiveDef = $context->getDirective();
|
||||
if (!$directiveDef) {
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
$errors = [];
|
||||
$argASTs = $directiveAST->arguments ?: [];
|
||||
$argASTMap = [];
|
||||
foreach ($argASTs as $argAST) {
|
||||
$argASTMap[$argAST->name->value] = $argASTs;
|
||||
}
|
||||
|
||||
foreach ($directiveDef->args as $argDef) {
|
||||
$argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
|
||||
if (!$argAST && $argDef->getType() instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
self::missingDirectiveArgMessage($directiveAST->name->value, $argDef->name, $argDef->getType()),
|
||||
[$directiveAST]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
@ -11,6 +11,16 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class ScalarLeafs
|
||||
{
|
||||
static function noSubselectionAllowedMessage($field, $type)
|
||||
{
|
||||
return "Field \"$field\" of type \"$type\" must not have a sub selection.";
|
||||
}
|
||||
|
||||
static function requiredSubselectionMessage($field, $type)
|
||||
{
|
||||
return "Field \"$field\" of type \"$type\" must have a sub selection.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
@ -20,13 +30,13 @@ class ScalarLeafs
|
||||
if (Type::isLeafType($type)) {
|
||||
if ($node->selectionSet) {
|
||||
return new Error(
|
||||
Messages::noSubselectionAllowedMessage($node->name->value, $type),
|
||||
self::noSubselectionAllowedMessage($node->name->value, $type),
|
||||
[$node->selectionSet]
|
||||
);
|
||||
}
|
||||
} else if (!$node->selectionSet) {
|
||||
return new Error(
|
||||
Messages::requiredSubselectionMessage($node->name->value, $type),
|
||||
self::requiredSubselectionMessage($node->name->value, $type),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
|
@ -4,40 +4,35 @@ namespace GraphQL\Validator\Rules;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\Type;
|
||||
use GraphQL\Language\AST\VariableDefinition;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class VariablesAreInputTypes
|
||||
{
|
||||
static function nonInputTypeOnVarMessage($variableName, $typeName)
|
||||
{
|
||||
return "Variable \"\$$variableName\" cannot be non-input type \"$typeName\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
Node::VARIABLE_DEFINITION => function(VariableDefinition $node) use ($context) {
|
||||
$typeName = $this->getTypeASTName($node->type);
|
||||
$type = $context->getSchema()->getType($typeName);
|
||||
$type = Utils\TypeInfo::typeFromAST($context->getSchema(), $node->type);
|
||||
|
||||
if (!($type instanceof InputType)) {
|
||||
// If the variable type is not an input type, return an error.
|
||||
if ($type && !Type::isInputType($type)) {
|
||||
$variableName = $node->variable->name->value;
|
||||
return new Error(
|
||||
Messages::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
|
||||
self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
|
||||
[ $node->type ]
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private function getTypeASTName(Type $typeAST)
|
||||
{
|
||||
if ($typeAST->kind === Node::NAME) {
|
||||
return $typeAST->value;
|
||||
}
|
||||
Utils::invariant($typeAST->type, 'Must be wrapping type');
|
||||
return $this->getTypeASTName($typeAST->type);
|
||||
}
|
||||
}
|
||||
|
@ -113,4 +113,14 @@ class ValidationContext
|
||||
{
|
||||
return $this->_typeInfo->getFieldDef();
|
||||
}
|
||||
|
||||
function getDirective()
|
||||
{
|
||||
return $this->_typeInfo->getDirective();
|
||||
}
|
||||
|
||||
function getArgument()
|
||||
{
|
||||
return $this->_typeInfo->getArgument();
|
||||
}
|
||||
}
|
||||
|
244
tests/Executor/AbstractTest.php
Normal file
244
tests/Executor/AbstractTest.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
|
||||
class AbstractTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
// Execute: Handles execution of abstract types
|
||||
public function testIsTypeOfUsedToResolveRuntimeTypeForInterface()
|
||||
{
|
||||
// isTypeOf used to resolve runtime type for Interface
|
||||
$petType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
]);
|
||||
|
||||
// Added to interface type when defined
|
||||
$dogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$petType],
|
||||
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
]
|
||||
]);
|
||||
|
||||
$catType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$petType],
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
]);
|
||||
|
||||
$schema = new Schema(
|
||||
new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($petType),
|
||||
'resolve' => function () {
|
||||
return [new Dog('Odie', true), new Cat('Garfield', false)];
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$query = '{
|
||||
pets {
|
||||
name
|
||||
... on Dog {
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
meows
|
||||
}
|
||||
}
|
||||
}';
|
||||
|
||||
$expected = new ExecutionResult([
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
|
||||
}
|
||||
|
||||
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion()
|
||||
{
|
||||
// isTypeOf used to resolve runtime type for Union
|
||||
|
||||
$dogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'isTypeOf' => function($obj) { return $obj instanceof Dog; },
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
]
|
||||
]);
|
||||
|
||||
$catType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'isTypeOf' => function ($obj) {
|
||||
return $obj instanceof Cat;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
]);
|
||||
|
||||
$petType = new UnionType([
|
||||
'name' => 'Pet',
|
||||
'types' => [$dogType, $catType]
|
||||
]);
|
||||
|
||||
$schema = new Schema(new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($petType),
|
||||
'resolve' => function() {
|
||||
return [ new Dog('Odie', true), new Cat('Garfield', false) ];
|
||||
}
|
||||
]
|
||||
]
|
||||
]));
|
||||
|
||||
$query = '{
|
||||
pets {
|
||||
name
|
||||
... on Dog {
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
meows
|
||||
}
|
||||
}
|
||||
}';
|
||||
|
||||
$expected = new ExecutionResult([
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
|
||||
}
|
||||
|
||||
function testResolveTypeOnInterfaceYieldsUsefulError()
|
||||
{
|
||||
$DogType = null;
|
||||
$CatType = null;
|
||||
$HumanType = null;
|
||||
|
||||
$PetType = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
|
||||
if ($obj instanceof Dog) {
|
||||
return $DogType;
|
||||
}
|
||||
if ($obj instanceof Cat) {
|
||||
return $CatType;
|
||||
}
|
||||
if ($obj instanceof Human) {
|
||||
return $HumanType;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
]);
|
||||
|
||||
$HumanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
]
|
||||
]);
|
||||
|
||||
$DogType = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'woofs' => ['type' => Type::boolean()],
|
||||
]
|
||||
]);
|
||||
|
||||
$CatType = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'interfaces' => [$PetType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
]
|
||||
]);
|
||||
|
||||
$schema = new Schema(new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'pets' => [
|
||||
'type' => Type::listOf($PetType),
|
||||
'resolve' => function () {
|
||||
return [
|
||||
new Dog('Odie', true),
|
||||
new Cat('Garfield', false),
|
||||
new Human('Jon')
|
||||
];
|
||||
}
|
||||
]
|
||||
]
|
||||
]));
|
||||
|
||||
|
||||
$query = '{
|
||||
pets {
|
||||
name
|
||||
... on Dog {
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
meows
|
||||
}
|
||||
}
|
||||
}';
|
||||
|
||||
$expected = [
|
||||
'data' => [
|
||||
'pets' => [
|
||||
['name' => 'Odie', 'woofs' => true],
|
||||
['name' => 'Garfield', 'meows' => false],
|
||||
null
|
||||
]
|
||||
],
|
||||
'errors' => [
|
||||
[ 'message' => 'Runtime Object type "Human" is not a possible type for "Pet".' ]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))->toArray());
|
||||
}
|
||||
|
||||
}
|
@ -19,16 +19,16 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
public function testWorksOnScalars()
|
||||
{
|
||||
// if true includes scalar
|
||||
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @if:true }'));
|
||||
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @include(if: true) }'));
|
||||
|
||||
// if false omits on scalar
|
||||
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @if:false }'));
|
||||
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }'));
|
||||
|
||||
// unless false includes scalar
|
||||
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @unless:false }'));
|
||||
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @skip(if: false) }'));
|
||||
|
||||
// unless true omits scalar
|
||||
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @unless:true }'));
|
||||
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @skip(if: true) }'));
|
||||
}
|
||||
|
||||
public function testWorksOnFragmentSpreads()
|
||||
@ -37,7 +37,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
...Frag @if:false
|
||||
...Frag @include(if: false)
|
||||
}
|
||||
fragment Frag on TestType {
|
||||
b
|
||||
@ -49,7 +49,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
...Frag @if:true
|
||||
...Frag @include(if: true)
|
||||
}
|
||||
fragment Frag on TestType {
|
||||
b
|
||||
@ -61,7 +61,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
...Frag @unless:false
|
||||
...Frag @skip(if: false)
|
||||
}
|
||||
fragment Frag on TestType {
|
||||
b
|
||||
@ -73,7 +73,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
...Frag @unless:true
|
||||
...Frag @skip(if: true)
|
||||
}
|
||||
fragment Frag on TestType {
|
||||
b
|
||||
@ -88,7 +88,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
... on TestType @if:false {
|
||||
... on TestType @include(if: false) {
|
||||
b
|
||||
}
|
||||
}
|
||||
@ -102,7 +102,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
... on TestType @if:true {
|
||||
... on TestType @include(if: true) {
|
||||
b
|
||||
}
|
||||
}
|
||||
@ -116,7 +116,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
... on TestType @unless:false {
|
||||
... on TestType @skip(if: false) {
|
||||
b
|
||||
}
|
||||
}
|
||||
@ -130,7 +130,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
$q = '
|
||||
query Q {
|
||||
a
|
||||
... on TestType @unless:true {
|
||||
... on TestType @skip(if: true) {
|
||||
b
|
||||
}
|
||||
}
|
||||
@ -149,7 +149,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
a
|
||||
...Frag
|
||||
}
|
||||
fragment Frag on TestType @if:false {
|
||||
fragment Frag on TestType @include(if: false) {
|
||||
b
|
||||
}
|
||||
';
|
||||
@ -161,7 +161,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
a
|
||||
...Frag
|
||||
}
|
||||
fragment Frag on TestType @if:true {
|
||||
fragment Frag on TestType @include(if: true) {
|
||||
b
|
||||
}
|
||||
';
|
||||
@ -173,7 +173,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
a
|
||||
...Frag
|
||||
}
|
||||
fragment Frag on TestType @unless:false {
|
||||
fragment Frag on TestType @skip(if: false) {
|
||||
b
|
||||
}
|
||||
';
|
||||
@ -185,7 +185,7 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
a
|
||||
...Frag
|
||||
}
|
||||
fragment Frag on TestType @unless:true {
|
||||
fragment Frag on TestType @skip(if: true) {
|
||||
b
|
||||
}
|
||||
';
|
||||
@ -213,13 +213,13 @@ class DirectivesTest extends \PHPUnit_Framework_TestCase
|
||||
private static function getData()
|
||||
{
|
||||
return self::$data ?: (self::$data = [
|
||||
'a' => function() { return 'a'; },
|
||||
'b' => function() { return 'b'; }
|
||||
'a' => 'a',
|
||||
'b' => 'b'
|
||||
]);
|
||||
}
|
||||
|
||||
private function executeTestQuery($doc)
|
||||
{
|
||||
return Executor::execute(self::getSchema(), self::getData(), Parser::parse($doc));
|
||||
return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray();
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ class ExecutorSchemaTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($BlogSchema, null, Parser::parse($request), '', []));
|
||||
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
|
||||
}
|
||||
|
||||
private function article($id)
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\FormattedError;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
@ -132,7 +133,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]);
|
||||
$schema = new Schema($dataType);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, $data, $ast, 'Example', ['size' => 100]));
|
||||
$this->assertEquals($expected, Executor::execute($schema, $ast, $data, ['size' => 100], 'Example')->toArray());
|
||||
}
|
||||
|
||||
public function testMergesParallelFragments()
|
||||
@ -187,11 +188,12 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($schema, $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testThreadsContextCorrectly()
|
||||
{
|
||||
// threads context correctly
|
||||
$doc = 'query Example { a }';
|
||||
|
||||
$gotHere = false;
|
||||
@ -214,7 +216,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]));
|
||||
|
||||
Executor::execute($schema, $data, $ast, 'Example', []);
|
||||
Executor::execute($schema, $ast, $data, [], 'Example');
|
||||
$this->assertEquals(true, $gotHere);
|
||||
}
|
||||
|
||||
@ -246,7 +248,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
]));
|
||||
Executor::execute($schema, null, $docAst, 'Example', []);
|
||||
Executor::execute($schema, $docAst, null, [], 'Example');
|
||||
$this->assertSame($gotHere, true);
|
||||
}
|
||||
|
||||
@ -255,6 +257,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
$doc = '{
|
||||
sync,
|
||||
syncError,
|
||||
syncRawError,
|
||||
async,
|
||||
asyncReject,
|
||||
asyncError
|
||||
@ -265,9 +268,11 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
return 'sync';
|
||||
},
|
||||
'syncError' => function () {
|
||||
throw new \Exception('Error getting syncError');
|
||||
throw new Error('Error getting syncError');
|
||||
},
|
||||
'syncRawError' => function() {
|
||||
throw new \Exception('Error getting syncRawError');
|
||||
},
|
||||
|
||||
// Following are inherited from JS reference implementation, but make no sense in this PHP impl
|
||||
// leaving them just to simplify migrations from newer js versions
|
||||
'async' => function() {
|
||||
@ -287,6 +292,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
'fields' => [
|
||||
'sync' => ['type' => Type::string()],
|
||||
'syncError' => ['type' => Type::string()],
|
||||
'syncRawError' => [ 'type' => Type::string() ],
|
||||
'async' => ['type' => Type::string()],
|
||||
'asyncReject' => ['type' => Type::string() ],
|
||||
'asyncError' => ['type' => Type::string()],
|
||||
@ -297,20 +303,22 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
'data' => [
|
||||
'sync' => 'sync',
|
||||
'syncError' => null,
|
||||
'syncRawError' => null,
|
||||
'async' => 'async',
|
||||
'asyncReject' => null,
|
||||
'asyncError' => null,
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError('Error getting syncError', [new SourceLocation(3, 7)]),
|
||||
new FormattedError('Error getting asyncReject', [new SourceLocation(5, 7)]),
|
||||
new FormattedError('Error getting asyncError', [new SourceLocation(6, 7)])
|
||||
FormattedError::create('Error getting syncError', [new SourceLocation(3, 7)]),
|
||||
FormattedError::create('Error getting syncRawError', [new SourceLocation(4, 7)]),
|
||||
FormattedError::create('Error getting asyncReject', [new SourceLocation(6, 7)]),
|
||||
FormattedError::create('Error getting asyncError', [new SourceLocation(7, 7)])
|
||||
]
|
||||
];
|
||||
|
||||
$result = Executor::execute($schema, $data, $docAst);
|
||||
$result = Executor::execute($schema, $docAst, $data);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
}
|
||||
|
||||
public function testUsesTheInlineOperationIfNoOperationIsProvided()
|
||||
@ -326,9 +334,9 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]));
|
||||
|
||||
$ex = Executor::execute($schema, $data, $ast);
|
||||
$ex = Executor::execute($schema, $ast, $data);
|
||||
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $ex);
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $ex->toArray());
|
||||
}
|
||||
|
||||
public function testUsesTheOnlyOperationIfNoOperationIsProvided()
|
||||
@ -343,8 +351,8 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]));
|
||||
|
||||
$ex = Executor::execute($schema, $data, $ast);
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $ex);
|
||||
$ex = Executor::execute($schema, $ast, $data);
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $ex->toArray());
|
||||
}
|
||||
|
||||
public function testThrowsIfNoOperationIsProvidedWithMultipleOperations()
|
||||
@ -359,15 +367,12 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]));
|
||||
|
||||
$ex = Executor::execute($schema, $data, $ast);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
'data' => null,
|
||||
'errors' => [new FormattedError('Must provide operation name if query contains multiple operations')]
|
||||
],
|
||||
$ex
|
||||
);
|
||||
try {
|
||||
Executor::execute($schema, $ast, $data);
|
||||
$this->fail('Expected exception is not thrown');
|
||||
} catch (Error $err) {
|
||||
$this->assertEquals('Must provide operation name if query contains multiple operations.', $err->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testUsesTheQuerySchemaForQueries()
|
||||
@ -390,8 +395,8 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
])
|
||||
);
|
||||
|
||||
$queryResult = Executor::execute($schema, $data, $ast, 'Q');
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $queryResult);
|
||||
$queryResult = Executor::execute($schema, $ast, $data, [], 'Q');
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $queryResult->toArray());
|
||||
}
|
||||
|
||||
public function testUsesTheMutationSchemaForMutations()
|
||||
@ -413,8 +418,8 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
])
|
||||
);
|
||||
$mutationResult = Executor::execute($schema, $data, $ast, 'M');
|
||||
$this->assertEquals(['data' => ['c' => 'd']], $mutationResult);
|
||||
$mutationResult = Executor::execute($schema, $ast, $data, [], 'M');
|
||||
$this->assertEquals(['data' => ['c' => 'd']], $mutationResult->toArray());
|
||||
}
|
||||
|
||||
public function testAvoidsRecursion()
|
||||
@ -440,8 +445,8 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]));
|
||||
|
||||
$queryResult = Executor::execute($schema, $data, $ast, 'Q');
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $queryResult);
|
||||
$queryResult = Executor::execute($schema, $ast, $data, [], 'Q');
|
||||
$this->assertEquals(['data' => ['a' => 'b']], $queryResult->toArray());
|
||||
}
|
||||
|
||||
public function testDoesNotIncludeIllegalFieldsInOutput()
|
||||
@ -464,7 +469,106 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
])
|
||||
);
|
||||
$mutationResult = Executor::execute($schema, null, $ast);
|
||||
$this->assertEquals(['data' => []], $mutationResult);
|
||||
$mutationResult = Executor::execute($schema, $ast);
|
||||
$this->assertEquals(['data' => []], $mutationResult->toArray());
|
||||
}
|
||||
|
||||
public function testDoesNotIncludeArgumentsThatWereNotSet()
|
||||
{
|
||||
$schema = new Schema(
|
||||
new ObjectType([
|
||||
'name' => 'Type',
|
||||
'fields' => [
|
||||
'field' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => function($data, $args) {return $args ? json_encode($args) : '';},
|
||||
'args' => [
|
||||
'a' => ['type' => Type::boolean()],
|
||||
'b' => ['type' => Type::boolean()],
|
||||
'c' => ['type' => Type::boolean()],
|
||||
'd' => ['type' => Type::int()],
|
||||
'e' => ['type' => Type::int()]
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$query = Parser::parse('{ field(a: true, c: false, e: 0) }');
|
||||
$result = Executor::execute($schema, $query);
|
||||
$expected = [
|
||||
'data' => [
|
||||
'field' => '{"a":true,"c":false,"e":0}'
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result->toArray());
|
||||
|
||||
/*
|
||||
var query = parse('{ field(a: true, c: false, e: 0) }');
|
||||
var result = await execute(schema, query);
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
data: {
|
||||
field: '{"a":true,"c":false,"e":0}'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when an isTypeOf check is not met', async () => {
|
||||
class Special {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class NotSpecial {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
var SpecialType = new GraphQLObjectType({
|
||||
name: 'SpecialType',
|
||||
isTypeOf(obj) {
|
||||
return obj instanceof Special;
|
||||
},
|
||||
fields: {
|
||||
value: { type: GraphQLString }
|
||||
}
|
||||
});
|
||||
|
||||
var schema = new GraphQLSchema({
|
||||
query: new GraphQLObjectType({
|
||||
name: 'Query',
|
||||
fields: {
|
||||
specials: {
|
||||
type: new GraphQLList(SpecialType),
|
||||
resolve: rootValue => rootValue.specials
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var query = parse('{ specials { value } }');
|
||||
var value = {
|
||||
specials: [ new Special('foo'), new NotSpecial('bar') ]
|
||||
};
|
||||
var result = await execute(schema, query, value);
|
||||
|
||||
expect(result.data).to.deep.equal({
|
||||
specials: [
|
||||
{ value: 'foo' },
|
||||
null
|
||||
]
|
||||
});
|
||||
expect(result.errors).to.have.lengthOf(1);
|
||||
expect(result.errors).to.containSubset([
|
||||
{ message:
|
||||
'Expected value of type "SpecialType" but got: [object Object].',
|
||||
locations: [ { line: 1, column: 3 } ] }
|
||||
]);
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\FormattedError;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
@ -24,7 +25,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['nest' => ['list' => [1,2]]]];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesListsOfNonNullsWhenTheyReturnNonNullValues()
|
||||
@ -47,7 +48,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesNonNullListsOfWhenTheyReturnNonNullValues()
|
||||
@ -69,7 +70,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesNonNullListsOfNonNullsWhenTheyReturnNonNullValues()
|
||||
@ -91,7 +92,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesListsWhenTheyReturnNullAsAValue()
|
||||
@ -113,7 +114,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesListsOfNonNullsWhenTheyReturnNullAsAValue()
|
||||
@ -135,13 +136,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
'Cannot return null for non-nullable type.',
|
||||
[new SourceLocation(4, 11)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesNonNullListsOfWhenTheyReturnNullAsAValue()
|
||||
@ -160,7 +161,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
'nest' => ['nonNullListContainsNull' => [1, null, 2]]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesNonNullListsOfNonNullsWhenTheyReturnNullAsAValue()
|
||||
@ -180,13 +181,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
'nest' => null
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
'Cannot return null for non-nullable type.',
|
||||
[new SourceLocation(4, 11)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesListsWhenTheyReturnNull()
|
||||
@ -208,7 +209,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesListsOfNonNullsWhenTheyReturnNull()
|
||||
@ -230,7 +231,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesNonNullListsOfWhenTheyReturnNull()
|
||||
@ -250,13 +251,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
'nest' => null,
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
'Cannot return null for non-nullable type.',
|
||||
[new SourceLocation(4, 11)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testHandlesNonNullListsOfNonNullsWhenTheyReturnNull()
|
||||
@ -276,13 +277,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase
|
||||
'nest' => null
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
'Cannot return null for non-nullable type.',
|
||||
[new SourceLocation(4, 11)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $this->data(), $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
|
||||
|
@ -31,7 +31,7 @@ class MutationsTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}';
|
||||
$ast = Parser::parse($doc);
|
||||
$mutationResult = Executor::execute($this->schema(), new Root(6), $ast, 'M');
|
||||
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6), null, 'M');
|
||||
$expected = [
|
||||
'data' => [
|
||||
'first' => [
|
||||
@ -51,7 +51,7 @@ class MutationsTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($mutationResult, $expected);
|
||||
$this->assertEquals($expected, $mutationResult->toArray());
|
||||
}
|
||||
|
||||
public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation()
|
||||
@ -77,7 +77,7 @@ class MutationsTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}';
|
||||
$ast = Parser::parse($doc);
|
||||
$mutationResult = Executor::execute($this->schema(), new Root(6), $ast, 'M');
|
||||
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6), null, 'M');
|
||||
$expected = [
|
||||
'data' => [
|
||||
'first' => [
|
||||
@ -96,17 +96,17 @@ class MutationsTest extends \PHPUnit_Framework_TestCase
|
||||
'sixth' => null,
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
'Cannot change the number',
|
||||
[new SourceLocation(8, 7)]
|
||||
),
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
'Cannot change the number',
|
||||
[new SourceLocation(17, 7)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, $mutationResult);
|
||||
$this->assertEquals($expected, $mutationResult->toArray());
|
||||
}
|
||||
|
||||
private function schema()
|
||||
|
@ -86,13 +86,13 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
'sync' => null,
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
$this->syncError->message,
|
||||
[new SourceLocation(3, 9)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->throwingData, $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously()
|
||||
@ -113,10 +113,10 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
'nest' => null
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError($this->nonNullSyncError->message, [new SourceLocation(4, 11)])
|
||||
FormattedError::create($this->nonNullSyncError->message, [new SourceLocation(4, 11)])
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->throwingData, $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testNullsAComplexTreeOfNullableFieldsThatThrow()
|
||||
@ -144,11 +144,11 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError($this->syncError->message, [new SourceLocation(4, 11)]),
|
||||
new FormattedError($this->syncError->message, [new SourceLocation(6, 13)]),
|
||||
FormattedError::create($this->syncError->message, [new SourceLocation(4, 11)]),
|
||||
FormattedError::create($this->syncError->message, [new SourceLocation(6, 13)]),
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->throwingData, $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testNullsANullableFieldThatSynchronouslyReturnsNull()
|
||||
@ -166,7 +166,7 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
'sync' => null,
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->nullingData, $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function test4()
|
||||
@ -187,10 +187,10 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
'nest' => null
|
||||
],
|
||||
'errors' => [
|
||||
new FormattedError('Cannot return null for non-nullable type.', [new SourceLocation(4, 11)])
|
||||
FormattedError::create('Cannot return null for non-nullable type.', [new SourceLocation(4, 11)])
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->nullingData, $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function test5()
|
||||
@ -226,7 +226,7 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
],
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->nullingData, $ast, 'Q', []));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, [], 'Q')->toArray());
|
||||
}
|
||||
|
||||
public function testNullsTheTopLevelIfSyncNonNullableFieldThrows()
|
||||
@ -238,10 +238,10 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError($this->nonNullSyncError->message, [new SourceLocation(2, 17)])
|
||||
FormattedError::create($this->nonNullSyncError->message, [new SourceLocation(2, 17)])
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->throwingData, Parser::parse($doc)));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray());
|
||||
}
|
||||
|
||||
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull()
|
||||
@ -254,9 +254,9 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError('Cannot return null for non-nullable type.', [new SourceLocation(2, 17)]),
|
||||
FormattedError::create('Cannot return null for non-nullable type.', [new SourceLocation(2, 17)]),
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->nullingData, Parser::parse($doc)));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray());
|
||||
}
|
||||
}
|
||||
|
78
tests/Executor/TestClasses.php
Normal file
78
tests/Executor/TestClasses.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
class Dog
|
||||
{
|
||||
function __construct($name, $woofs)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->woofs = $woofs;
|
||||
}
|
||||
}
|
||||
|
||||
class Cat
|
||||
{
|
||||
function __construct($name, $meows)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->meows = $meows;
|
||||
}
|
||||
}
|
||||
|
||||
class Human
|
||||
{
|
||||
function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
class Person
|
||||
{
|
||||
public $name;
|
||||
public $pets;
|
||||
public $friends;
|
||||
|
||||
function __construct($name, $pets = null, $friends = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->pets = $pets;
|
||||
$this->friends = $friends;
|
||||
}
|
||||
}
|
||||
|
||||
class ComplexScalar extends ScalarType
|
||||
{
|
||||
public static function create()
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public $name = 'ComplexScalar';
|
||||
|
||||
public function serialize($value)
|
||||
{
|
||||
if ($value === 'DeserializedValue') {
|
||||
return 'SerializedValue';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
if ($value === 'SerializedValue') {
|
||||
return 'DeserializedValue';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function parseLiteral($valueAST)
|
||||
{
|
||||
if ($valueAST->value === 'SerializedValue') {
|
||||
return 'DeserializedValue';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\Config;
|
||||
@ -31,7 +33,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
'interfaces' => [$NamedType],
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'barks' => ['type' => Type::boolean()]
|
||||
'woofs' => ['type' => Type::boolean()]
|
||||
],
|
||||
'isTypeOf' => function ($value) {
|
||||
return $value instanceof Dog;
|
||||
@ -143,7 +145,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testExecutesUsingUnionTypes()
|
||||
@ -156,7 +158,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
pets {
|
||||
__typename
|
||||
name
|
||||
barks
|
||||
woofs
|
||||
meows
|
||||
}
|
||||
}
|
||||
@ -167,12 +169,12 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
}
|
||||
|
||||
public function testExecutesUnionTypesWithInlineFragments()
|
||||
@ -186,7 +188,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
__typename
|
||||
... on Dog {
|
||||
name
|
||||
barks
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
name
|
||||
@ -201,12 +203,12 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
}
|
||||
|
||||
public function testExecutesUsingInterfaceTypes()
|
||||
@ -219,7 +221,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
friends {
|
||||
__typename
|
||||
name
|
||||
barks
|
||||
woofs
|
||||
meows
|
||||
}
|
||||
}
|
||||
@ -230,12 +232,12 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
'name' => 'John',
|
||||
'friends' => [
|
||||
['__typename' => 'Person', 'name' => 'Liz'],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
}
|
||||
|
||||
public function testExecutesInterfaceTypesWithInlineFragments()
|
||||
@ -249,7 +251,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
__typename
|
||||
name
|
||||
... on Dog {
|
||||
barks
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
meows
|
||||
@ -263,12 +265,12 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
'name' => 'John',
|
||||
'friends' => [
|
||||
['__typename' => 'Person', 'name' => 'Liz'],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsFragmentConditionsToBeAbstractTypes()
|
||||
@ -285,7 +287,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
__typename
|
||||
... on Dog {
|
||||
name
|
||||
barks
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
name
|
||||
@ -297,7 +299,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
__typename
|
||||
name
|
||||
... on Dog {
|
||||
barks
|
||||
woofs
|
||||
}
|
||||
... on Cat {
|
||||
meows
|
||||
@ -311,54 +313,15 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
'name' => 'John',
|
||||
'pets' => [
|
||||
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
],
|
||||
'friends' => [
|
||||
['__typename' => 'Person', 'name' => 'Liz'],
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]
|
||||
['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Dog
|
||||
{
|
||||
public $name;
|
||||
public $barks;
|
||||
|
||||
function __construct($name, $barks)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->barks = $barks;
|
||||
}
|
||||
}
|
||||
|
||||
class Cat
|
||||
{
|
||||
public $name;
|
||||
public $meows;
|
||||
|
||||
function __construct($name, $meows)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->meows = $meows;
|
||||
}
|
||||
}
|
||||
|
||||
class Person
|
||||
{
|
||||
public $name;
|
||||
public $pets;
|
||||
public $friends;
|
||||
|
||||
function __construct($name, $pets = null, $friends = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->pets = $pets;
|
||||
$this->friends = $friends;
|
||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
<?php
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
require_once __DIR__ . '/TestClasses.php';
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\FormattedError;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
class VariablesTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
// Execute: Handles input objects
|
||||
// Execute: Handles inputs
|
||||
// Handles objects and nullability
|
||||
|
||||
public function testUsingInlineStructs()
|
||||
@ -29,9 +33,9 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
|
||||
// properly coerces single value to array:
|
||||
// properly parses single value to list:
|
||||
$doc = '
|
||||
{
|
||||
fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"})
|
||||
@ -40,7 +44,23 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testDoesNotUseIncorrectValue()
|
||||
{
|
||||
$doc = '
|
||||
{
|
||||
fieldWithObjectInput(input: ["foo", "bar", "baz"])
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$result = Executor::execute($this->schema(), $ast)->toArray();
|
||||
|
||||
$expected = [
|
||||
'data' => ['fieldWithObjectInput' => null]
|
||||
];
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public function testUsingVariables()
|
||||
@ -57,46 +77,100 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
['data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']],
|
||||
Executor::execute($schema, null, $ast, null, $params)
|
||||
Executor::execute($schema, $ast, null, $params)->toArray()
|
||||
);
|
||||
|
||||
// properly coerces single value to array:
|
||||
// uses default value when not provided:
|
||||
$withDefaultsAST = Parser::parse('
|
||||
query q($input: TestInputObject = {a: "foo", b: ["bar"], c: "baz"}) {
|
||||
fieldWithObjectInput(input: $input)
|
||||
}
|
||||
');
|
||||
|
||||
$result = Executor::execute($this->schema(), $withDefaultsAST)->toArray();
|
||||
$expected = [
|
||||
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']
|
||||
];
|
||||
$this->assertEquals($expected, $result);
|
||||
|
||||
|
||||
// properly parses single value to array:
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz']];
|
||||
$this->assertEquals(
|
||||
['data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']],
|
||||
Executor::execute($schema, null, $ast, null, $params)
|
||||
Executor::execute($schema, $ast, null, $params)->toArray()
|
||||
);
|
||||
|
||||
// executes with complex scalar input:
|
||||
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ];
|
||||
$result = Executor::execute($schema, $ast, null, $params)->toArray();
|
||||
$expected = [
|
||||
'data' => [
|
||||
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, $result);
|
||||
|
||||
// errors on null for nested non-null:
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]];
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type ' .
|
||||
'TestInputObject but got: ' .
|
||||
'{"a":"foo","b":"bar","c":null}.',
|
||||
[new SourceLocation(2, 17)]
|
||||
)
|
||||
]
|
||||
];
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, null, $ast, null, $params));
|
||||
try {
|
||||
Executor::execute($schema, $ast, null, $params);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $err) {
|
||||
$this->assertEquals($expected, Error::formatError($err));
|
||||
}
|
||||
|
||||
// errors on incorrect type:
|
||||
$params = [ 'input' => 'foo bar' ];
|
||||
|
||||
try {
|
||||
Executor::execute($schema, $ast, null, $params);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $error) {
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type TestInputObject but got: "foo bar".',
|
||||
[new SourceLocation(2, 17)]
|
||||
);
|
||||
$this->assertEquals($expected, Error::formatError($error));
|
||||
}
|
||||
|
||||
// errors on omission of nested non-null:
|
||||
$params = ['input' => ['a' => 'foo', 'b' => 'bar']];
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
|
||||
try {
|
||||
Executor::execute($schema, $ast, null, $params);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $e) {
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type ' .
|
||||
'TestInputObject but got: {"a":"foo","b":"bar"}.',
|
||||
[new SourceLocation(2, 17)]
|
||||
)
|
||||
]
|
||||
];
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($schema, null, $ast, null, $params));
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
|
||||
// errors on addition of unknown input field
|
||||
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'd' => 'dog' ]];
|
||||
|
||||
try {
|
||||
Executor::execute($schema, $ast, null, $params);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $e) {
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type TestInputObject but ' .
|
||||
'got: {"a":"foo","b":"bar","c":"baz","d":"dog"}.',
|
||||
[new SourceLocation(2, 17)]
|
||||
);
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -110,10 +184,10 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => ['fieldWithNullableStringInput' => 'null']
|
||||
'data' => ['fieldWithNullableStringInput' => null]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNullableInputsToBeOmittedInAVariable()
|
||||
@ -124,9 +198,9 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => 'null']];
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => null]];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable()
|
||||
@ -137,8 +211,8 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => 'null']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => null]];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNullableInputsToBeSetToNullInAVariable()
|
||||
@ -149,21 +223,9 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => 'null']];
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => null]];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['value' => null]));
|
||||
}
|
||||
|
||||
public function testAllowsNullableInputsToBeSetToNullDirectly()
|
||||
{
|
||||
$doc = '
|
||||
{
|
||||
fieldWithNullableStringInput(input: null)
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => 'null']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['value' => null])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNullableInputsToBeSetToAValueInAVariable()
|
||||
@ -175,7 +237,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['value' => 'a']));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['value' => 'a'])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNullableInputsToBeSetToAValueDirectly()
|
||||
@ -187,7 +249,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNullableStringInput' => '"a"']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
|
||||
@ -201,16 +263,16 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
try {
|
||||
Executor::execute($this->schema(), $ast);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $e) {
|
||||
$expected = FormattedError::create(
|
||||
'Variable $value expected value of type String! but got: null.',
|
||||
[new SourceLocation(2, 31)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
);
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable()
|
||||
@ -222,16 +284,17 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
|
||||
try {
|
||||
Executor::execute($this->schema(), $ast, null, ['value' => null]);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $e) {
|
||||
$expected = FormattedError::create(
|
||||
'Variable $value expected value of type String! but got: null.',
|
||||
[new SourceLocation(2, 31)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['value' => null]));
|
||||
);
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
public function testAllowsNonNullableInputsToBeSetToAValueInAVariable()
|
||||
@ -243,7 +306,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['value' => 'a']));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['value' => 'a'])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNonNullableInputsToBeSetToAValueDirectly()
|
||||
@ -256,7 +319,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
public function testPassesAlongNullForNonNullableInputsIfExplcitlySetInTheQuery()
|
||||
@ -267,8 +330,8 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithNonNullableStringInput' => 'null']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast));
|
||||
$expected = ['data' => ['fieldWithNonNullableStringInput' => null]];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
}
|
||||
|
||||
// Handles lists and nullability
|
||||
@ -280,9 +343,9 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['list' => 'null']];
|
||||
$expected = ['data' => ['list' => null]];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => null]));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => null])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsListsToContainValues()
|
||||
@ -294,7 +357,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['list' => '["A"]']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A']]));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => ['A']])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsListsToContainNull()
|
||||
@ -306,7 +369,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['list' => '["A",null,"B"]']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A',null,'B']]));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => ['A',null,'B']])->toArray());
|
||||
}
|
||||
|
||||
public function testDoesNotAllowNonNullListsToBeNull()
|
||||
@ -317,17 +380,17 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type [String]! but got: null.',
|
||||
[new SourceLocation(2, 17)]
|
||||
)
|
||||
]
|
||||
];
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => null]));
|
||||
try {
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => null])->toArray());
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $e) {
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
public function testAllowsNonNullListsToContainValues()
|
||||
@ -339,7 +402,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['nnList' => '["A"]']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => 'A']));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => 'A'])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsNonNullListsToContainNull()
|
||||
@ -352,7 +415,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['nnList' => '["A",null,"B"]']];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A',null,'B']]));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => ['A',null,'B']])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsListsOfNonNullsToBeNull()
|
||||
@ -363,8 +426,8 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['listNN' => 'null']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => null]));
|
||||
$expected = ['data' => ['listNN' => null]];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => null])->toArray());
|
||||
}
|
||||
|
||||
public function testAllowsListsOfNonNullsToContainValues()
|
||||
@ -377,7 +440,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['listNN' => '["A"]']];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => 'A']));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => 'A'])->toArray());
|
||||
}
|
||||
|
||||
public function testDoesNotAllowListsOfNonNullsToContainNull()
|
||||
@ -388,16 +451,17 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type [String!] but got: ["A",null,"B"].',
|
||||
[new SourceLocation(2, 17)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A', null, 'B']]));
|
||||
);
|
||||
|
||||
try {
|
||||
Executor::execute($this->schema(), $ast, null, ['input' => ['A', null, 'B']]);
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (Error $e) {
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
public function testDoesNotAllowNonNullListsOfNonNullsToBeNull()
|
||||
@ -408,16 +472,15 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type [String!]! but got: null.',
|
||||
[new SourceLocation(2, 17)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => null]));
|
||||
);
|
||||
try {
|
||||
Executor::execute($this->schema(), $ast, null, ['input' => null]);
|
||||
} catch (Error $e) {
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
public function testAllowsNonNullListsOfNonNullsToContainValues()
|
||||
@ -429,7 +492,7 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['nnListNN' => '["A"]']];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A']]));
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast, null, ['input' => ['A']])->toArray());
|
||||
}
|
||||
|
||||
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull()
|
||||
@ -440,27 +503,29 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = [
|
||||
'data' => null,
|
||||
'errors' => [
|
||||
new FormattedError(
|
||||
$expected = FormattedError::create(
|
||||
'Variable $input expected value of type [String!]! but got: ["A",null,"B"].',
|
||||
[new SourceLocation(2, 17)]
|
||||
)
|
||||
]
|
||||
];
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A',null,'B']]));
|
||||
);
|
||||
try {
|
||||
Executor::execute($this->schema(), $ast, null, ['input' => ['A', null, 'B']]);
|
||||
} catch (Error $e) {
|
||||
$this->assertEquals($expected, Error::formatError($e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function schema()
|
||||
{
|
||||
$ComplexScalarType = ComplexScalar::create();
|
||||
|
||||
$TestInputObject = new InputObjectType([
|
||||
'name' => 'TestInputObject',
|
||||
'fields' => [
|
||||
'a' => ['type' => Type::string()],
|
||||
'b' => ['type' => Type::listOf(Type::string())],
|
||||
'c' => ['type' => Type::nonNull(Type::string())]
|
||||
'c' => ['type' => Type::nonNull(Type::string())],
|
||||
'd' => ['type' => $ComplexScalarType],
|
||||
]
|
||||
]);
|
||||
|
||||
@ -471,49 +536,56 @@ class InputObjectTest extends \PHPUnit_Framework_TestCase
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => $TestInputObject]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'fieldWithNullableStringInput' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => Type::string()]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'fieldWithNonNullableStringInput' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => Type::nonNull(Type::string())]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'fieldWithDefaultArgumentValue' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [ 'input' => [ 'type' => Type::string(), 'defaultValue' => 'Hello World' ]],
|
||||
'resolve' => function($_, $args) {
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'list' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => Type::listOf(Type::string())]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'nnList' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => Type::nonNull(Type::listOf(Type::string()))]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'listNN' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => Type::listOf(Type::nonNull(Type::string()))]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
'nnListNN' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['input' => ['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]],
|
||||
'resolve' => function ($_, $args) {
|
||||
return json_encode($args['input']);
|
||||
return isset($args['input']) ? json_encode($args['input']) : null;
|
||||
}
|
||||
],
|
||||
]
|
@ -245,7 +245,7 @@ class StarWarsSchema
|
||||
]
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return StarWarsData::getHero($args['episode']);
|
||||
return StarWarsData::getHero(isset($args['episode']) ? $args['episode'] : null);
|
||||
},
|
||||
],
|
||||
'human' => [
|
||||
|
@ -28,8 +28,8 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
appearsIn
|
||||
}
|
||||
';
|
||||
$result = $this->validationResult($query);
|
||||
$this->assertEquals(true, $result['isValid']);
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(true, empty($errors));
|
||||
}
|
||||
|
||||
public function testThatNonExistentFieldsAreInvalid()
|
||||
@ -42,7 +42,8 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
';
|
||||
$this->assertEquals(false, $this->validationResult($query)['isValid']);
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(false, empty($errors));
|
||||
}
|
||||
|
||||
public function testRequiresFieldsOnObjects()
|
||||
@ -52,7 +53,9 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
hero
|
||||
}
|
||||
';
|
||||
$this->assertEquals(false, $this->validationResult($query)['isValid']);
|
||||
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(false, empty($errors));
|
||||
}
|
||||
|
||||
public function testDisallowsFieldsOnScalars()
|
||||
@ -67,7 +70,8 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
';
|
||||
$this->assertEquals(false, $this->validationResult($query)['isValid']);
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(false, empty($errors));
|
||||
}
|
||||
|
||||
public function testDisallowsObjectFieldsOnInterfaces()
|
||||
@ -80,7 +84,8 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
';
|
||||
$this->assertEquals(false, $this->validationResult($query)['isValid']);
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(false, empty($errors));
|
||||
}
|
||||
|
||||
public function testAllowsObjectFieldsInFragments()
|
||||
@ -97,7 +102,8 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
primaryFunction
|
||||
}
|
||||
';
|
||||
$this->assertEquals(true, $this->validationResult($query)['isValid']);
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(true, empty($errors));
|
||||
}
|
||||
|
||||
public function testAllowsObjectFieldsInInlineFragments()
|
||||
@ -112,13 +118,14 @@ class StartWarsValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
';
|
||||
$this->assertEquals(true, $this->validationResult($query)['isValid']);
|
||||
$errors = $this->validationErrors($query);
|
||||
$this->assertEquals(true, empty($errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a query and the expected response.
|
||||
*/
|
||||
private function validationResult($query)
|
||||
private function validationErrors($query)
|
||||
{
|
||||
$ast = Parser::parse($query);
|
||||
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
|
||||
|
@ -6,7 +6,6 @@ use GraphQL\Type\Definition\Config;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\IntType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
@ -67,7 +66,10 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->objectType = new ObjectType(['name' => 'Object']);
|
||||
$this->objectType = new ObjectType([
|
||||
'name' => 'Object',
|
||||
'isTypeOf' => function() {return true;}
|
||||
]);
|
||||
$this->interfaceType = new InterfaceType(['name' => 'Interface']);
|
||||
$this->unionType = new UnionType(['name' => 'Union', 'types' => [$this->objectType]]);
|
||||
$this->enumType = new EnumType(['name' => 'Enum']);
|
||||
@ -176,20 +178,83 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertSame('writeArticle', $writeMutation->name);
|
||||
}
|
||||
|
||||
public function testIncludesNestedInputObjectInTheMap()
|
||||
{
|
||||
$nestedInputObject = new InputObjectType([
|
||||
'name' => 'NestedInputObject',
|
||||
'fields' => ['value' => ['type' => Type::string()]]
|
||||
]);
|
||||
$someInputObject = new InputObjectType([
|
||||
'name' => 'SomeInputObject',
|
||||
'fields' => ['nested' => ['type' => $nestedInputObject]]
|
||||
]);
|
||||
$someMutation = new ObjectType([
|
||||
'name' => 'SomeMutation',
|
||||
'fields' => [
|
||||
'mutateSomething' => [
|
||||
'type' => $this->blogArticle,
|
||||
'args' => ['input' => ['type' => $someInputObject]]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$schema = new Schema($this->blogQuery, $someMutation);
|
||||
$this->assertSame($nestedInputObject, $schema->getType('NestedInputObject'));
|
||||
}
|
||||
|
||||
public function testIncludesInterfaceSubtypesInTheTypeMap()
|
||||
{
|
||||
$someInterface = new InterfaceType([
|
||||
'name' => 'SomeInterface',
|
||||
'fields' => []
|
||||
'fields' => [
|
||||
'f' => ['type' => Type::int()]
|
||||
]
|
||||
]);
|
||||
|
||||
$someSubtype = new ObjectType([
|
||||
'name' => 'SomeSubtype',
|
||||
'fields' => [],
|
||||
'interfaces' => [$someInterface]
|
||||
'fields' => [
|
||||
'f' => ['type' => Type::int()]
|
||||
],
|
||||
'interfaces' => [$someInterface],
|
||||
'isTypeOf' => function() {return true;}
|
||||
]);
|
||||
|
||||
$schema = new Schema($someInterface);
|
||||
$schema = new Schema(new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'iface' => ['type' => $someInterface]
|
||||
]
|
||||
]));
|
||||
$this->assertSame($someSubtype, $schema->getType('SomeSubtype'));
|
||||
}
|
||||
|
||||
public function testIncludesInterfacesThunkSubtypesInTheTypeMap()
|
||||
{
|
||||
// includes interfaces' thunk subtypes in the type map
|
||||
$someInterface = new InterfaceType([
|
||||
'name' => 'SomeInterface',
|
||||
'fields' => [
|
||||
'f' => ['type' => Type::int()]
|
||||
]
|
||||
]);
|
||||
|
||||
$someSubtype = new ObjectType([
|
||||
'name' => 'SomeSubtype',
|
||||
'fields' => [
|
||||
'f' => ['type' => Type::int()]
|
||||
],
|
||||
'interfaces' => function() use ($someInterface) { return [$someInterface]; },
|
||||
'isTypeOf' => function() {return true;}
|
||||
]);
|
||||
|
||||
$schema = new Schema(new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'iface' => ['type' => $someInterface]
|
||||
]
|
||||
]));
|
||||
|
||||
$this->assertSame($someSubtype, $schema->getType('SomeSubtype'));
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
namespace GraphQL\Type;
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class ScalarCoercionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCoercesOutputInt()
|
||||
{
|
||||
$intType = Type::int();
|
||||
|
||||
$this->assertSame(1, $intType->coerce(1));
|
||||
$this->assertSame(0, $intType->coerce(0));
|
||||
$this->assertSame(0, $intType->coerce(0.1));
|
||||
$this->assertSame(1, $intType->coerce(1.1));
|
||||
$this->assertSame(-1, $intType->coerce(-1.1));
|
||||
$this->assertSame(100000, $intType->coerce(1e5));
|
||||
$this->assertSame(null, $intType->coerce(1e100));
|
||||
$this->assertSame(null, $intType->coerce(-1e100));
|
||||
$this->assertSame(-1, $intType->coerce('-1.1'));
|
||||
$this->assertSame(null, $intType->coerce('one'));
|
||||
$this->assertSame(0, $intType->coerce(false));
|
||||
}
|
||||
|
||||
public function testCoercesOutputFloat()
|
||||
{
|
||||
$floatType = Type::float();
|
||||
|
||||
$this->assertSame(1.0, $floatType->coerce(1));
|
||||
$this->assertSame(-1.0, $floatType->coerce(-1));
|
||||
$this->assertSame(0.1, $floatType->coerce(0.1));
|
||||
$this->assertSame(1.1, $floatType->coerce(1.1));
|
||||
$this->assertSame(-1.1, $floatType->coerce(-1.1));
|
||||
$this->assertSame(null, $floatType->coerce('one'));
|
||||
$this->assertSame(0.0, $floatType->coerce(false));
|
||||
$this->assertSame(1.0, $floatType->coerce(true));
|
||||
}
|
||||
|
||||
public function testCoercesOutputStrings()
|
||||
{
|
||||
$stringType = Type::string();
|
||||
|
||||
$this->assertSame('string', $stringType->coerce('string'));
|
||||
$this->assertSame('1', $stringType->coerce(1));
|
||||
$this->assertSame('-1.1', $stringType->coerce(-1.1));
|
||||
$this->assertSame('true', $stringType->coerce(true));
|
||||
$this->assertSame('false', $stringType->coerce(false));
|
||||
}
|
||||
|
||||
public function testCoercesOutputBoolean()
|
||||
{
|
||||
$boolType = Type::boolean();
|
||||
|
||||
$this->assertSame(true, $boolType->coerce('string'));
|
||||
$this->assertSame(false, $boolType->coerce(''));
|
||||
$this->assertSame(true, $boolType->coerce(1));
|
||||
$this->assertSame(false, $boolType->coerce(0));
|
||||
$this->assertSame(true, $boolType->coerce(true));
|
||||
$this->assertSame(false, $boolType->coerce(false));
|
||||
|
||||
// TODO: how should it behaive on '0'?
|
||||
}
|
||||
}
|
63
tests/Type/ScalarSerializationTest.php
Normal file
63
tests/Type/ScalarSerializationTest.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace GraphQL\Type;
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class ScalarSerializationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCoercesOutputInt()
|
||||
{
|
||||
$intType = Type::int();
|
||||
|
||||
$this->assertSame(1, $intType->serialize(1));
|
||||
$this->assertSame(0, $intType->serialize(0));
|
||||
$this->assertSame(0, $intType->serialize(0.1));
|
||||
$this->assertSame(1, $intType->serialize(1.1));
|
||||
$this->assertSame(-1, $intType->serialize(-1.1));
|
||||
$this->assertSame(100000, $intType->serialize(1e5));
|
||||
$this->assertSame(null, $intType->serialize(1e100));
|
||||
$this->assertSame(null, $intType->serialize(-1e100));
|
||||
$this->assertSame(-1, $intType->serialize('-1.1'));
|
||||
$this->assertSame(null, $intType->serialize('one'));
|
||||
$this->assertSame(0, $intType->serialize(false));
|
||||
}
|
||||
|
||||
public function testCoercesOutputFloat()
|
||||
{
|
||||
$floatType = Type::float();
|
||||
|
||||
$this->assertSame(1.0, $floatType->serialize(1));
|
||||
$this->assertSame(-1.0, $floatType->serialize(-1));
|
||||
$this->assertSame(0.1, $floatType->serialize(0.1));
|
||||
$this->assertSame(1.1, $floatType->serialize(1.1));
|
||||
$this->assertSame(-1.1, $floatType->serialize(-1.1));
|
||||
$this->assertSame(null, $floatType->serialize('one'));
|
||||
$this->assertSame(0.0, $floatType->serialize(false));
|
||||
$this->assertSame(1.0, $floatType->serialize(true));
|
||||
}
|
||||
|
||||
public function testCoercesOutputStrings()
|
||||
{
|
||||
$stringType = Type::string();
|
||||
|
||||
$this->assertSame('string', $stringType->serialize('string'));
|
||||
$this->assertSame('1', $stringType->serialize(1));
|
||||
$this->assertSame('-1.1', $stringType->serialize(-1.1));
|
||||
$this->assertSame('true', $stringType->serialize(true));
|
||||
$this->assertSame('false', $stringType->serialize(false));
|
||||
}
|
||||
|
||||
public function testCoercesOutputBoolean()
|
||||
{
|
||||
$boolType = Type::boolean();
|
||||
|
||||
$this->assertSame(true, $boolType->serialize('string'));
|
||||
$this->assertSame(false, $boolType->serialize(''));
|
||||
$this->assertSame(true, $boolType->serialize(1));
|
||||
$this->assertSame(false, $boolType->serialize(0));
|
||||
$this->assertSame(true, $boolType->serialize(true));
|
||||
$this->assertSame(false, $boolType->serialize(false));
|
||||
|
||||
// TODO: how should it behaive on '0'?
|
||||
}
|
||||
}
|
@ -29,10 +29,8 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
public function testPassesOnTheIntrospectionSchema()
|
||||
{
|
||||
$schema = new Schema(Introspection::_schema());
|
||||
$validationResult = SchemaValidator::validate($schema);
|
||||
|
||||
$this->assertSame(true, $validationResult->isValid);
|
||||
$this->assertSame(null, $validationResult->errors);
|
||||
$errors = SchemaValidator::validate($schema);
|
||||
$this->assertEmpty($errors);
|
||||
}
|
||||
|
||||
|
||||
@ -65,12 +63,11 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
$schema = new Schema($someOutputType);
|
||||
$validationResult = SchemaValidator::validate($schema, [SchemaValidator::noInputTypesAsOutputFieldsRule()]);
|
||||
|
||||
$this->assertSame(false, $validationResult->isValid);
|
||||
$this->assertSame(1, count($validationResult->errors));
|
||||
$this->assertSame(1, count($validationResult));
|
||||
$this->assertSame(
|
||||
'Field SomeOutputType.sneaky is of type SomeInputType, which is an ' .
|
||||
'input type, but field types must be output types!',
|
||||
$validationResult->errors[0]->message
|
||||
$validationResult[0]->message
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -93,17 +90,17 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
]);
|
||||
|
||||
$schema = new Schema($someOutputType);
|
||||
$validationResult = SchemaValidator::validate($schema, [$rule]);
|
||||
$this->assertSame(true, $validationResult->isValid);
|
||||
$errors = SchemaValidator::validate($schema, [$rule]);
|
||||
$this->assertEmpty($errors);
|
||||
}
|
||||
|
||||
private function checkValidationResult($validationResult, $operationType)
|
||||
private function checkValidationResult($validationErrors, $operationType)
|
||||
{
|
||||
$this->assertEquals(false, $validationResult->isValid);
|
||||
$this->assertEquals(1, count($validationResult->errors));
|
||||
$this->assertNotEmpty($validationErrors, "Should not validate");
|
||||
$this->assertEquals(1, count($validationErrors));
|
||||
$this->assertEquals(
|
||||
"Schema $operationType type SomeInputType must be an object type!",
|
||||
$validationResult->errors[0]->message
|
||||
$validationErrors[0]->message
|
||||
);
|
||||
}
|
||||
|
||||
@ -197,8 +194,8 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
private function assertAcceptingFieldArgOfType($fieldArgType)
|
||||
{
|
||||
$schema = $this->schemaWithFieldArgOfType($fieldArgType);
|
||||
$validationResult = SchemaValidator::validate($schema, [SchemaValidator::noOutputTypesAsInputArgsRule()]);
|
||||
$this->assertSame(true, $validationResult->isValid);
|
||||
$errors = SchemaValidator::validate($schema, [SchemaValidator::noOutputTypesAsInputArgsRule()]);
|
||||
$this->assertEmpty($errors);
|
||||
}
|
||||
|
||||
private function schemaWithFieldArgOfType($argType)
|
||||
@ -223,14 +220,13 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
return new Schema($queryType);
|
||||
}
|
||||
|
||||
private function expectRejectionBecauseFieldIsNotInputType($validationResult, $fieldTypeName)
|
||||
private function expectRejectionBecauseFieldIsNotInputType($errors, $fieldTypeName)
|
||||
{
|
||||
$this->assertSame(false, $validationResult->isValid);
|
||||
$this->assertSame(1, count($validationResult->errors));
|
||||
$this->assertSame(1, count($errors));
|
||||
$this->assertSame(
|
||||
"Input field SomeIncorrectInputType.val has type $fieldTypeName, " .
|
||||
"which is not an input type!",
|
||||
$validationResult->errors[0]->message
|
||||
$errors[0]->message
|
||||
);
|
||||
}
|
||||
|
||||
@ -245,40 +241,9 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testRejectsWhenAPossibleTypeDoesNotImplementTheInterface()
|
||||
{
|
||||
// rejects when a possible type does not implement the interface
|
||||
$InterfaceType = new InterfaceType([
|
||||
'name' => 'InterfaceType',
|
||||
'fields' => []
|
||||
]);
|
||||
|
||||
$SubType = new ObjectType([
|
||||
'name' => 'SubType',
|
||||
'fields' => [],
|
||||
'interfaces' => []
|
||||
]);
|
||||
|
||||
InterfaceType::addImplementationToInterfaces($SubType, [$InterfaceType]);
|
||||
|
||||
// Sanity check.
|
||||
$this->assertEquals(1, count($InterfaceType->getPossibleTypes()));
|
||||
$this->assertEquals($SubType, $InterfaceType->getPossibleTypes()[0]);
|
||||
|
||||
|
||||
$schema = new Schema($InterfaceType);
|
||||
$validationResult = SchemaValidator::validate(
|
||||
$schema,
|
||||
[SchemaValidator::interfacePossibleTypesMustImplementTheInterfaceRule()]
|
||||
);
|
||||
$this->assertSame(false, $validationResult->isValid);
|
||||
$this->assertSame(1, count($validationResult->errors));
|
||||
$this->assertSame(
|
||||
'SubType is a possible type of interface InterfaceType but does not ' .
|
||||
'implement it!',
|
||||
$validationResult->errors[0]->message
|
||||
);
|
||||
// TODO: Validation for interfaces / implementors
|
||||
}
|
||||
|
||||
|
||||
private function assertAcceptingAnInterfaceWithANormalSubtype($rule)
|
||||
{
|
||||
$interfaceType = new InterfaceType([
|
||||
@ -294,8 +259,8 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$schema = new Schema($interfaceType, $subType);
|
||||
|
||||
$validationResult = SchemaValidator::validate($schema, [$rule]);
|
||||
$this->assertSame(true, $validationResult->isValid);
|
||||
$errors = SchemaValidator::validate($schema, [$rule]);
|
||||
$this->assertEmpty($errors);
|
||||
}
|
||||
|
||||
|
||||
@ -337,28 +302,12 @@ class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
// Another sanity check.
|
||||
$this->assertSame($subType, $schema->getType('SubType'));
|
||||
|
||||
$validationResult = SchemaValidator::validate($schema, [SchemaValidator::typesInterfacesMustShowThemAsPossibleRule()]);
|
||||
$this->assertSame(false, $validationResult->isValid);
|
||||
$this->assertSame(1, count($validationResult->errors));
|
||||
$errors = SchemaValidator::validate($schema, [SchemaValidator::typesInterfacesMustShowThemAsPossibleRule()]);
|
||||
$this->assertSame(1, count($errors));
|
||||
$this->assertSame(
|
||||
'SubType implements interface InterfaceType, but InterfaceType does ' .
|
||||
'not list it as possible!',
|
||||
$validationResult->errors[0]->message
|
||||
$errors[0]->message
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
|
||||
var validationResult = validateSchema(
|
||||
schema,
|
||||
[TypesInterfacesMustShowThemAsPossible]
|
||||
);
|
||||
expect(validationResult.isValid).to.equal(false);
|
||||
expect(validationResult.errors.length).to.equal(1);
|
||||
expect(validationResult.errors[0].message).to.equal(
|
||||
'SubType implements interface InterfaceType, but InterfaceType does ' +
|
||||
'not list it as possible!'
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
{
|
||||
function missingArg($fieldName, $argName, $typeName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
return FormattedError::create(
|
||||
Messages::missingArgMessage($fieldName, $argName, $typeName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
@ -17,8 +17,8 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
|
||||
function badValue($argName, $typeName, $value, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::badValueMessage($argName, $typeName, $value),
|
||||
return FormattedError::create(
|
||||
ArgumentsOfCorrectType::badValueMessage($argName, $typeName, $value),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
@ -632,33 +632,6 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testMissingOneNonNullableArgument()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs(req2: 2)
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingArg('multipleReqs', 'req1', 'Int!', 4, 13)
|
||||
]);
|
||||
}
|
||||
|
||||
public function testMissingMultipleNonNullableArguments()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingArg('multipleReqs', 'req1', 'Int!', 4, 13),
|
||||
$this->missingArg('multipleReqs', 'req2', 'Int!', 4, 13),
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIncorrectValueAndMissingArgument()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
@ -668,7 +641,6 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingArg('multipleReqs', 'req2', 'Int!', 4, 13),
|
||||
$this->badValue('req1', 'Int!', '"one"', 4, 32),
|
||||
]);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
|
||||
private function defaultForNonNullArg($varName, $typeName, $guessTypeName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
return FormattedError::create(
|
||||
Messages::defaultForNonNullArgMessage($varName, $typeName, $guessTypeName),
|
||||
[ new SourceLocation($line, $column) ]
|
||||
);
|
||||
@ -101,7 +101,7 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
|
||||
private function badValue($varName, $typeName, $val, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
return FormattedError::create(
|
||||
Messages::badValueForDefaultArgMessage($varName, $typeName, $val),
|
||||
[ new SourceLocation($line, $column) ]
|
||||
);
|
||||
|
@ -56,6 +56,15 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
public function testIgnoresFieldsOnUnknownType()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
fragment unknownSelection on UnknownType {
|
||||
unknownField
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testFieldNotDefinedOnFragment()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
@ -184,7 +193,7 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
|
||||
private function undefinedField($field, $type, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
return FormattedError::create(
|
||||
Messages::undefinedFieldMessage($field, $type),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
|
@ -86,8 +86,8 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
}
|
||||
}
|
||||
',
|
||||
[new FormattedError(
|
||||
Messages::inlineFragmentOnNonCompositeErrorMessage('String'),
|
||||
[FormattedError::create(
|
||||
FragmentsOnCompositeTypes::inlineFragmentOnNonCompositeErrorMessage('String'),
|
||||
[new SourceLocation(3, 16)]
|
||||
)]
|
||||
);
|
||||
@ -95,8 +95,8 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
|
||||
private function error($fragName, $typeName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::fragmentOnNonCompositeErrorMessage($fragName, $typeName),
|
||||
return FormattedError::create(
|
||||
FragmentsOnCompositeTypes::fragmentOnNonCompositeErrorMessage($fragName, $typeName),
|
||||
[ new SourceLocation($line, $column) ]
|
||||
);
|
||||
}
|
||||
|
@ -26,6 +26,15 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
public function testIgnoresArgsOfUnknownFields()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
fragment argOnUnknownField on Dog {
|
||||
unknownField(unknownArg: SIT)
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testMultipleArgsInReverseOrderAreKnown()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -62,6 +71,26 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
public function testDirectiveArgsAreKnown()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
{
|
||||
dog @skip(if: true)
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testUndirectiveArgsAreInvalid()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
{
|
||||
dog @skip(unless: true)
|
||||
}
|
||||
', [
|
||||
$this->unknownDirectiveArg('unless', 'skip', 3, 19),
|
||||
]);
|
||||
}
|
||||
|
||||
public function testInvalidArgName()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
@ -106,28 +135,18 @@ class KnownArgumentNamesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testArgsMayBeOnObjectButNotInterface()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
fragment nameSometimesHasArg on Being {
|
||||
name(surname: true)
|
||||
... on Human {
|
||||
name(surname: true)
|
||||
}
|
||||
... on Dog {
|
||||
name(surname: true)
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->unknownArg('surname', 'name', 'Being', 3, 14),
|
||||
$this->unknownArg('surname', 'name', 'Dog', 8, 16)
|
||||
]);
|
||||
}
|
||||
|
||||
private function unknownArg($argName, $fieldName, $typeName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::unknownArgMessage($argName, $fieldName, $typeName),
|
||||
return FormattedError::create(
|
||||
KnownArgumentNames::unknownArgMessage($argName, $fieldName, $typeName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
||||
private function unknownDirectiveArg($argName, $directiveName, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
KnownArgumentNames::unknownDirectiveArgMessage($argName, $directiveName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -26,10 +26,10 @@ class KnownDirectivesTest extends TestCase
|
||||
{
|
||||
$this->expectPassesRule(new KnownDirectives, '
|
||||
{
|
||||
dog @if: true {
|
||||
dog @include(if: true) {
|
||||
name
|
||||
}
|
||||
human @unless: false {
|
||||
human @skip(if: true) {
|
||||
name
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@ class KnownDirectivesTest extends TestCase
|
||||
{
|
||||
$this->expectFailsRule(new KnownDirectives, '
|
||||
{
|
||||
dog @unknown: "directive" {
|
||||
dog @unknown(directive: "value") {
|
||||
name
|
||||
}
|
||||
}
|
||||
@ -53,12 +53,12 @@ class KnownDirectivesTest extends TestCase
|
||||
{
|
||||
$this->expectFailsRule(new KnownDirectives, '
|
||||
{
|
||||
dog @unknown: "directive" {
|
||||
dog @unknown(directive: "value") {
|
||||
name
|
||||
}
|
||||
human @unknown: "directive" {
|
||||
human @unknown(directive: "value") {
|
||||
name
|
||||
pets @unknown: "directive" {
|
||||
pets @unknown(directive: "value") {
|
||||
name
|
||||
}
|
||||
}
|
||||
@ -70,30 +70,42 @@ class KnownDirectivesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testWithWellPlacedDirectives()
|
||||
{
|
||||
$this->expectPassesRule(new KnownDirectives, '
|
||||
query Foo {
|
||||
name @include(if: true)
|
||||
...Frag @include(if: true)
|
||||
skippedField @skip(if: true)
|
||||
...SkippedFrag @skip(if: true)
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testWithMisplacedDirectives()
|
||||
{
|
||||
$this->expectFailsRule(new KnownDirectives, '
|
||||
query Foo @if: true {
|
||||
query Foo @include(if: true) {
|
||||
name
|
||||
...Frag
|
||||
}
|
||||
', [
|
||||
$this->misplacedDirective('if', 'operation', 2, 17)
|
||||
$this->misplacedDirective('include', 'operation', 2, 17)
|
||||
]);
|
||||
}
|
||||
|
||||
private function unknownDirective($directiveName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::unknownDirectiveMessage($directiveName),
|
||||
return FormattedError::create(
|
||||
KnownDirectives::unknownDirectiveMessage($directiveName),
|
||||
[ new SourceLocation($line, $column) ]
|
||||
);
|
||||
}
|
||||
|
||||
function misplacedDirective($directiveName, $placement, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::misplacedDirectiveMessage($directiveName, $placement),
|
||||
return FormattedError::create(
|
||||
KnownDirectives::misplacedDirectiveMessage($directiveName, $placement),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -57,8 +57,8 @@ class KnownFragmentNamesTest extends TestCase
|
||||
|
||||
private function undefFrag($fragName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
"Undefined fragment $fragName.",
|
||||
return FormattedError::create(
|
||||
KnownFragmentNames::unknownFragmentMessage($fragName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ class KnownTypeNamesTest extends TestCase
|
||||
|
||||
private function unknownType($typeName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::unknownTypeMessage($typeName),
|
||||
return FormattedError::create(
|
||||
KnownTypeNames::unknownTypeMessage($typeName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
namespace GraphQL\Validator;
|
||||
|
||||
use GraphQL\FormattedError;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Definition\Config;
|
||||
use GraphQL\Validator\Rules\NoFragmentCycles;
|
||||
|
||||
class NoFragmentCyclesTest extends TestCase
|
||||
@ -88,8 +86,8 @@ class NoFragmentCyclesTest extends TestCase
|
||||
fragment fragA on Dog { ...fragB }
|
||||
fragment fragB on Dog { ...fragA }
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::cycleErrorMessage('fragA', ['fragB']),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', ['fragB']),
|
||||
[ new SourceLocation(2, 31), new SourceLocation(3, 31) ]
|
||||
)
|
||||
]);
|
||||
@ -101,8 +99,8 @@ class NoFragmentCyclesTest extends TestCase
|
||||
fragment fragB on Dog { ...fragA }
|
||||
fragment fragA on Dog { ...fragB }
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::cycleErrorMessage('fragB', ['fragA']),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragB', ['fragA']),
|
||||
[new SourceLocation(2, 31), new SourceLocation(3, 31)]
|
||||
)
|
||||
]);
|
||||
@ -122,8 +120,8 @@ class NoFragmentCyclesTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::cycleErrorMessage('fragA', ['fragB']),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', ['fragB']),
|
||||
[new SourceLocation(4, 11), new SourceLocation(9, 11)]
|
||||
)
|
||||
]);
|
||||
@ -140,8 +138,8 @@ class NoFragmentCyclesTest extends TestCase
|
||||
fragment fragZ on Dog { ...fragO }
|
||||
fragment fragO on Dog { ...fragA, ...fragX }
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::cycleErrorMessage('fragA', ['fragB', 'fragC', 'fragO']),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', ['fragB', 'fragC', 'fragO']),
|
||||
[
|
||||
new SourceLocation(2, 31),
|
||||
new SourceLocation(3, 31),
|
||||
@ -149,8 +147,8 @@ class NoFragmentCyclesTest extends TestCase
|
||||
new SourceLocation(8, 31),
|
||||
]
|
||||
),
|
||||
new FormattedError(
|
||||
Messages::cycleErrorMessage('fragX', ['fragY', 'fragZ', 'fragO']),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragX', ['fragY', 'fragZ', 'fragO']),
|
||||
[
|
||||
new SourceLocation(5, 31),
|
||||
new SourceLocation(6, 31),
|
||||
@ -168,12 +166,12 @@ class NoFragmentCyclesTest extends TestCase
|
||||
fragment fragB on Dog { ...fragA }
|
||||
fragment fragC on Dog { ...fragA }
|
||||
', [
|
||||
new FormattedError(
|
||||
'Cannot spread fragment fragA within itself via fragB.',
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', ['fragB']),
|
||||
[new SourceLocation(2, 31), new SourceLocation(3, 31)]
|
||||
),
|
||||
new FormattedError(
|
||||
'Cannot spread fragment fragA within itself via fragC.',
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', ['fragC']),
|
||||
[new SourceLocation(2, 41), new SourceLocation(4, 31)]
|
||||
)
|
||||
]);
|
||||
@ -181,8 +179,8 @@ class NoFragmentCyclesTest extends TestCase
|
||||
|
||||
private function cycleError($fargment, $spreadNames, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::cycleErrorMessage($fargment, $spreadNames),
|
||||
return FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage($fargment, $spreadNames),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -302,16 +302,16 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
|
||||
private function undefVar($varName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::undefinedVarMessage($varName),
|
||||
return FormattedError::create(
|
||||
NoUndefinedVariables::undefinedVarMessage($varName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
||||
private function undefVarByOp($varName, $l1, $c1, $opName, $l2, $c2)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::undefinedVarByOpMessage($varName, $opName),
|
||||
return FormattedError::create(
|
||||
NoUndefinedVariables::undefinedVarByOpMessage($varName, $opName),
|
||||
[new SourceLocation($l1, $c1), new SourceLocation($l2, $c2)]
|
||||
);
|
||||
}
|
||||
|
@ -130,10 +130,27 @@ class NoUnusedFragmentsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testContainsUnknownAndUndefFragments()
|
||||
{
|
||||
|
||||
$this->expectFailsRule(new NoUnusedFragments, '
|
||||
query Foo {
|
||||
human(id: 4) {
|
||||
...bar
|
||||
}
|
||||
}
|
||||
fragment foo on Human {
|
||||
name
|
||||
}
|
||||
', [
|
||||
$this->unusedFrag('foo', 7, 7),
|
||||
]);
|
||||
}
|
||||
|
||||
private function unusedFrag($fragName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::unusedFragMessage($fragName),
|
||||
return FormattedError::create(
|
||||
NoUnusedFragments::unusedFragMessage($fragName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -213,8 +213,8 @@ class NoUnusedVariablesTest extends TestCase
|
||||
|
||||
private function unusedVar($varName, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::unusedVariableMessage($varName),
|
||||
return FormattedError::create(
|
||||
NoUnusedVariables::unusedVariableMessage($varName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -48,8 +48,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
{
|
||||
$this->expectPassesRule(new OverlappingFieldsCanBeMerged, '
|
||||
fragment mergeSameFieldsWithSameDirectives on Dog {
|
||||
name @if:true
|
||||
name @if:true
|
||||
name @include(if: true)
|
||||
name @include(if: true)
|
||||
}
|
||||
');
|
||||
}
|
||||
@ -68,8 +68,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
{
|
||||
$this->expectPassesRule(new OverlappingFieldsCanBeMerged, '
|
||||
fragment differentDirectivesWithDifferentAliases on Dog {
|
||||
nameIfTrue : name @if:true
|
||||
nameIfFalse : name @if:false
|
||||
nameIfTrue : name @include(if: true)
|
||||
nameIfFalse : name @include(if: false)
|
||||
}
|
||||
');
|
||||
}
|
||||
@ -82,8 +82,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
fido : nickname
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('fido', 'name and nickname are different fields'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('fido', 'name and nickname are different fields'),
|
||||
[new SourceLocation(3, 9), new SourceLocation(4, 9)]
|
||||
)
|
||||
]);
|
||||
@ -97,8 +97,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
name
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('name', 'nickname and name are different fields'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('name', 'nickname and name are different fields'),
|
||||
[new SourceLocation(3, 9), new SourceLocation(4, 9)]
|
||||
)
|
||||
]);
|
||||
@ -112,8 +112,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
doesKnowCommand(dogCommand: HEEL)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('doesKnowCommand', 'they have differing arguments'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('doesKnowCommand', 'they have differing arguments'),
|
||||
[new SourceLocation(3,9), new SourceLocation(4,9)]
|
||||
)
|
||||
]);
|
||||
@ -123,27 +123,43 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
{
|
||||
$this->expectFailsRule(new OverlappingFieldsCanBeMerged, '
|
||||
fragment conflictingDirectiveArgs on Dog {
|
||||
name @if: true
|
||||
name @unless: false
|
||||
name @include(if: true)
|
||||
name @skip(if: true)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('name', 'they have differing directives'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('name', 'they have differing directives'),
|
||||
[new SourceLocation(3, 9), new SourceLocation(4, 9)]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
public function testConflictingDirectiveArgs()
|
||||
{
|
||||
$this->expectFailsRule(new OverlappingFieldsCanBeMerged, '
|
||||
fragment conflictingDirectiveArgs on Dog {
|
||||
name @include(if: true)
|
||||
name @include(if: false)
|
||||
}
|
||||
', [
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('name', 'they have differing directives'),
|
||||
[new SourceLocation(3, 9), new SourceLocation(4, 9)]
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function testConflictingArgsWithMatchingDirectives()
|
||||
{
|
||||
$this->expectFailsRule(new OverlappingFieldsCanBeMerged, '
|
||||
fragment conflictingArgsWithMatchingDirectiveArgs on Dog {
|
||||
doesKnowCommand(dogCommand: SIT) @if:true
|
||||
doesKnowCommand(dogCommand: HEEL) @if:true
|
||||
doesKnowCommand(dogCommand: SIT) @include(if: true)
|
||||
doesKnowCommand(dogCommand: HEEL) @include(if: true)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('doesKnowCommand', 'they have differing arguments'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('doesKnowCommand', 'they have differing arguments'),
|
||||
[new SourceLocation(3, 9), new SourceLocation(4, 9)]
|
||||
)
|
||||
]);
|
||||
@ -153,12 +169,12 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
{
|
||||
$this->expectFailsRule(new OverlappingFieldsCanBeMerged, '
|
||||
fragment conflictingDirectiveArgsWithMatchingArgs on Dog {
|
||||
doesKnowCommand(dogCommand: SIT) @if: true
|
||||
doesKnowCommand(dogCommand: SIT) @unless: false
|
||||
doesKnowCommand(dogCommand: SIT) @include(if: true)
|
||||
doesKnowCommand(dogCommand: SIT) @skip(if: true)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('doesKnowCommand', 'they have differing directives'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('doesKnowCommand', 'they have differing directives'),
|
||||
[new SourceLocation(3, 9), new SourceLocation(4, 9)]
|
||||
)
|
||||
]);
|
||||
@ -178,8 +194,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
x: b
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('x', 'a and b are different fields'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'a and b are different fields'),
|
||||
[new SourceLocation(7, 9), new SourceLocation(10, 9)]
|
||||
)
|
||||
]);
|
||||
@ -210,16 +226,16 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
x: b
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('x', 'a and b are different fields'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'a and b are different fields'),
|
||||
[new SourceLocation(18, 9), new SourceLocation(21, 9)]
|
||||
),
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('x', 'a and c are different fields'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'a and c are different fields'),
|
||||
[new SourceLocation(18, 9), new SourceLocation(14, 11)]
|
||||
),
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('x', 'b and c are different fields'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'b and c are different fields'),
|
||||
[new SourceLocation(21, 9), new SourceLocation(14, 11)]
|
||||
)
|
||||
]);
|
||||
@ -237,8 +253,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('field', [['x', 'a and b are different fields']]),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('field', [['x', 'a and b are different fields']]),
|
||||
[
|
||||
new SourceLocation(3, 9),
|
||||
new SourceLocation(6,9),
|
||||
@ -263,8 +279,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('field', [
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('field', [
|
||||
['x', 'a and b are different fields'],
|
||||
['y', 'c and d are different fields']
|
||||
]),
|
||||
@ -296,8 +312,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('field', [['deepField', [['x', 'a and b are different fields']]]]),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('field', [['deepField', [['x', 'a and b are different fields']]]]),
|
||||
[
|
||||
new SourceLocation(3,9),
|
||||
new SourceLocation(8,9),
|
||||
@ -329,8 +345,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('deepField', [['x', 'a and b are different fields']]),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('deepField', [['x', 'a and b are different fields']]),
|
||||
[
|
||||
new SourceLocation(4,11),
|
||||
new SourceLocation(7,11),
|
||||
@ -356,8 +372,8 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::fieldsConflictMessage('scalar', 'they return differing types Int and String'),
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('scalar', 'they return differing types Int and String'),
|
||||
[ new SourceLocation(5,15), new SourceLocation(8,15) ]
|
||||
)
|
||||
]);
|
||||
@ -379,6 +395,55 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
public function testComparesDeepTypesIncludingList()
|
||||
{
|
||||
$this->expectFailsRuleWithSchema($this->getTestSchema(), new OverlappingFieldsCanBeMerged, '
|
||||
{
|
||||
connection {
|
||||
...edgeID
|
||||
edges {
|
||||
node {
|
||||
id: name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment edgeID on Connection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
', [
|
||||
FormattedError::create(
|
||||
OverlappingFieldsCanBeMerged::fieldsConflictMessage('edges', [['node', [['id', 'id and name are different fields']]]]),
|
||||
[
|
||||
new SourceLocation(14, 11), new SourceLocation(5, 13),
|
||||
new SourceLocation(15, 13), new SourceLocation(6, 15),
|
||||
new SourceLocation(16, 15), new SourceLocation(7, 17),
|
||||
]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIgnoresUnknownTypes()
|
||||
{
|
||||
$this->expectPassesRuleWithSchema($this->getTestSchema(), new OverlappingFieldsCanBeMerged, '
|
||||
{
|
||||
boxUnion {
|
||||
...on UnknownType {
|
||||
scalar
|
||||
}
|
||||
...on NonNullStringBox2 {
|
||||
scalar
|
||||
}
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
private function getTestSchema()
|
||||
{
|
||||
$StringBox = new ObjectType([
|
||||
@ -411,13 +476,37 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
|
||||
|
||||
$BoxUnion = new UnionType([
|
||||
'name' => 'BoxUnion',
|
||||
'resolveType' => function() use ($StringBox) {return $StringBox;},
|
||||
'types' => [ $StringBox, $IntBox, $NonNullStringBox1, $NonNullStringBox2 ]
|
||||
]);
|
||||
|
||||
$Connection = new ObjectType([
|
||||
'name' => 'Connection',
|
||||
'fields' => [
|
||||
'edges' => [
|
||||
'type' => Type::listOf(new ObjectType([
|
||||
'name' => 'Edge',
|
||||
'fields' => [
|
||||
'node' => [
|
||||
'type' => new ObjectType([
|
||||
'name' => 'Node',
|
||||
'fields' => [
|
||||
'id' => ['type' => Type::id()],
|
||||
'name' => ['type' => Type::string()]
|
||||
]
|
||||
])
|
||||
]
|
||||
]
|
||||
]))
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$schema = new Schema(new ObjectType([
|
||||
'name' => 'QueryRoot',
|
||||
'fields' => [
|
||||
'boxUnion' => ['type' => $BoxUnion ]
|
||||
'boxUnion' => ['type' => $BoxUnion ],
|
||||
'connection' => ['type' => $Connection]
|
||||
]
|
||||
]));
|
||||
|
||||
|
@ -210,16 +210,16 @@ class PossibleFragmentSpreadsTest extends TestCase
|
||||
|
||||
private function error($fragName, $parentType, $fragType, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
||||
return FormattedError::create(
|
||||
PossibleFragmentSpreads::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
||||
private function errorAnon($parentType, $fragType, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
||||
return FormattedError::create(
|
||||
PossibleFragmentSpreads::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
217
tests/Validator/ProvidedNonNullArgumentsTest.php
Normal file
217
tests/Validator/ProvidedNonNullArgumentsTest.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace GraphQL\Validator;
|
||||
|
||||
use GraphQL\FormattedError;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
||||
|
||||
class ProvidedNonNullArgumentsTest extends TestCase
|
||||
{
|
||||
// Validate: Provided required arguments
|
||||
public function testIgnoresUnknownArguments()
|
||||
{
|
||||
// ignores unknown arguments
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
dog {
|
||||
isHousetrained(unknownArgument: true)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
// Valid non-nullable value:
|
||||
public function testArgOnOptionalArg()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
dog {
|
||||
isHousetrained(atOtherHomes: true)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testMultipleArgs()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs(req1: 1, req2: 2)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testMultipleArgsReverseOrder()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs(req2: 2, req1: 1)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testNoArgsOnMultipleOptional()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleOpts
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testOneArgOnMultipleOptional()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleOpts(opt1: 1)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testSecondArgOnMultipleOptional()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleOpts(opt2: 1)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testMultipleReqsOnMixedList()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleOptAndReq(req1: 3, req2: 4)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testMultipleReqsAndOneOptOnMixedList()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleOptAndReq(req1: 3, req2: 4, opt1: 5)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testAllReqsAndOptsOnMixedList()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
// Invalid non-nullable value
|
||||
public function testMissingOneNonNullableArgument()
|
||||
{
|
||||
$this->expectFailsRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs(req2: 2)
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingFieldArg('multipleReqs', 'req1', 'Int!', 4, 13)
|
||||
]);
|
||||
}
|
||||
|
||||
public function testMissingMultipleNonNullableArguments()
|
||||
{
|
||||
$this->expectFailsRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingFieldArg('multipleReqs', 'req1', 'Int!', 4, 13),
|
||||
$this->missingFieldArg('multipleReqs', 'req2', 'Int!', 4, 13),
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIncorrectValueAndMissingArgument()
|
||||
{
|
||||
$this->expectFailsRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs(req1: "one")
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingFieldArg('multipleReqs', 'req2', 'Int!', 4, 13),
|
||||
]);
|
||||
}
|
||||
|
||||
// Directive arguments
|
||||
public function testIgnoresUnknownDirectives()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
dog @unknown
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testWithDirectivesOfValidTypes()
|
||||
{
|
||||
$this->expectPassesRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
dog @include(if: true) {
|
||||
name
|
||||
}
|
||||
human @skip(if: false) {
|
||||
name
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
public function testWithDirectiveWithMissingTypes()
|
||||
{
|
||||
$this->expectFailsRule(new ProvidedNonNullArguments, '
|
||||
{
|
||||
dog @include {
|
||||
name @skip
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->missingDirectiveArg('include', 'if', 'Boolean!', 3, 15),
|
||||
$this->missingDirectiveArg('skip', 'if', 'Boolean!', 4, 18)
|
||||
]);
|
||||
}
|
||||
|
||||
private function missingFieldArg($fieldName, $argName, $typeName, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
ProvidedNonNullArguments::missingFieldArgMessage($fieldName, $argName, $typeName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
||||
private function missingDirectiveArg($directiveName, $argName, $typeName, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
ProvidedNonNullArguments::missingDirectiveArgMessage($directiveName, $argName, $typeName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
}
|
@ -81,10 +81,10 @@ class ScalarLeafsTest extends TestCase
|
||||
{
|
||||
$this->expectFailsRule(new ScalarLeafs, '
|
||||
fragment scalarSelectionsNotAllowedWithDirectives on Dog {
|
||||
name @if: true { isAlsoHumanName }
|
||||
name @include(if: true) { isAlsoHumanName }
|
||||
}
|
||||
',
|
||||
[$this->noScalarSubselection('name', 'String', 3, 24)]
|
||||
[$this->noScalarSubselection('name', 'String', 3, 33)]
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,25 +92,25 @@ class ScalarLeafsTest extends TestCase
|
||||
{
|
||||
$this->expectFailsRule(new ScalarLeafs, '
|
||||
fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog {
|
||||
doesKnowCommand(dogCommand: SIT) @if: true { sinceWhen }
|
||||
doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen }
|
||||
}
|
||||
',
|
||||
[$this->noScalarSubselection('doesKnowCommand', 'Boolean', 3, 52)]
|
||||
[$this->noScalarSubselection('doesKnowCommand', 'Boolean', 3, 61)]
|
||||
);
|
||||
}
|
||||
|
||||
private function noScalarSubselection($field, $type, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::noSubselectionAllowedMessage($field, $type),
|
||||
return FormattedError::create(
|
||||
ScalarLeafs::noSubselectionAllowedMessage($field, $type),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
||||
private function missingObjSubselection($field, $type, $line, $column)
|
||||
{
|
||||
return new FormattedError(
|
||||
Messages::requiredSubselectionMessage($field, $type),
|
||||
return FormattedError::create(
|
||||
ScalarLeafs::requiredSubselectionMessage($field, $type),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -20,17 +20,25 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
protected function getDefaultSchema()
|
||||
{
|
||||
$FurColor = null;
|
||||
|
||||
$Being = new InterfaceType([
|
||||
'name' => 'Being',
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ]
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [ 'surname' => [ 'type' => Type::boolean() ] ]
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$Pet = new InterfaceType([
|
||||
'name' => 'Pet',
|
||||
'fields' => [
|
||||
'name' => [ 'type' => Type::string() ]
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [ 'surname' => [ 'type' => Type::boolean() ] ]
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
@ -45,8 +53,12 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$Dog = new ObjectType([
|
||||
'name' => 'Dog',
|
||||
'isTypeOf' => function() {return true;},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [ 'surname' => [ 'type' => Type::boolean() ] ]
|
||||
],
|
||||
'nickname' => ['type' => Type::string()],
|
||||
'barkVolume' => ['type' => Type::int()],
|
||||
'barks' => ['type' => Type::boolean()],
|
||||
@ -66,24 +78,18 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
'interfaces' => [$Being, $Pet]
|
||||
]);
|
||||
|
||||
$FurColor = new EnumType([
|
||||
'name' => 'FurColor',
|
||||
'values' => [
|
||||
'BROWN' => [ 'value' => 0 ],
|
||||
'BLACK' => [ 'value' => 1 ],
|
||||
'TAN' => [ 'value' => 2 ],
|
||||
'SPOTTED' => [ 'value' => 3 ],
|
||||
],
|
||||
]);
|
||||
|
||||
$Cat = new ObjectType([
|
||||
'name' => 'Cat',
|
||||
'isTypeOf' => function() {return true;},
|
||||
'fields' => [
|
||||
'name' => ['type' => Type::string()],
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [ 'surname' => [ 'type' => Type::boolean() ] ]
|
||||
],
|
||||
'nickname' => ['type' => Type::string()],
|
||||
'meows' => ['type' => Type::boolean()],
|
||||
'meowVolume' => ['type' => Type::int()],
|
||||
'furColor' => ['type' => $FurColor]
|
||||
'furColor' => ['type' => function() use (&$FurColor) {return $FurColor;}]
|
||||
],
|
||||
'interfaces' => [$Being, $Pet]
|
||||
]);
|
||||
@ -106,22 +112,29 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$Human = $this->humanType = new ObjectType([
|
||||
'name' => 'Human',
|
||||
'isTypeOf' => function() {return true;},
|
||||
'interfaces' => [$Being, $Intelligent],
|
||||
'fields' => [
|
||||
'name' => [
|
||||
'args' => ['surname' => ['type' => Type::boolean()]],
|
||||
'type' => Type::string()
|
||||
'type' => Type::string(),
|
||||
'args' => ['surname' => ['type' => Type::boolean()]]
|
||||
],
|
||||
'pets' => ['type' => Type::listOf($Pet)],
|
||||
'relatives' => ['type' => function() {return Type::listOf($this->humanType); }]
|
||||
'relatives' => ['type' => function() {return Type::listOf($this->humanType); }],
|
||||
'iq' => ['type' => Type::int()]
|
||||
]
|
||||
]);
|
||||
|
||||
$Alien = new ObjectType([
|
||||
'name' => 'Alien',
|
||||
'isTypeOf' => function() {return true;},
|
||||
'interfaces' => [$Being, $Intelligent],
|
||||
'fields' => [
|
||||
'iq' => ['type' => Type::int()],
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['surname' => ['type' => Type::boolean()]]
|
||||
],
|
||||
'numEyes' => ['type' => Type::int()]
|
||||
]
|
||||
]);
|
||||
@ -144,6 +157,16 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
]);
|
||||
|
||||
$FurColor = new EnumType([
|
||||
'name' => 'FurColor',
|
||||
'values' => [
|
||||
'BROWN' => [ 'value' => 0 ],
|
||||
'BLACK' => [ 'value' => 1 ],
|
||||
'TAN' => [ 'value' => 2 ],
|
||||
'SPOTTED' => [ 'value' => 3 ],
|
||||
],
|
||||
]);
|
||||
|
||||
$ComplexInput = new InputObjectType([
|
||||
'name' => 'ComplexInput',
|
||||
'fields' => [
|
||||
@ -260,19 +283,20 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
function expectValid($schema, $rules, $queryString)
|
||||
{
|
||||
$this->assertEquals(
|
||||
['isValid' => true, 'errors' => null],
|
||||
DocumentValidator::validate($schema, Parser::parse($queryString), $rules)
|
||||
[],
|
||||
DocumentValidator::validate($schema, Parser::parse($queryString), $rules),
|
||||
'Should validate'
|
||||
);
|
||||
}
|
||||
|
||||
function expectInvalid($schema, $rules, $queryString, $errors)
|
||||
function expectInvalid($schema, $rules, $queryString, $expectedErrors)
|
||||
{
|
||||
$result = DocumentValidator::validate($schema, Parser::parse($queryString), $rules);
|
||||
$errors = DocumentValidator::validate($schema, Parser::parse($queryString), $rules);
|
||||
|
||||
$this->assertEquals(false, $result['isValid'], 'GraphQL should not validate');
|
||||
$this->assertEquals($errors, $result['errors']);
|
||||
$this->assertNotEmpty($errors, 'GraphQL should not validate');
|
||||
$this->assertEquals($expectedErrors, array_map(['GraphQL\Error', 'formatError'], $errors));
|
||||
|
||||
return $result;
|
||||
return $errors;
|
||||
}
|
||||
|
||||
function expectPassesRule($rule, $queryString)
|
||||
|
@ -20,20 +20,20 @@ class VariablesAreInputTypesTest extends TestCase
|
||||
public function testOutputTypesAreInvalid()
|
||||
{
|
||||
$this->expectFailsRule(new VariablesAreInputTypes, '
|
||||
query Foo($a: Dog, $b: [[DogOrCat!]]!, $c: Pet) {
|
||||
query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) {
|
||||
field(a: $a, b: $b, c: $c)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
Messages::nonInputTypeOnVarMessage('a', 'Dog'),
|
||||
FormattedError::create(
|
||||
VariablesAreInputTypes::nonInputTypeOnVarMessage('a', 'Dog'),
|
||||
[new SourceLocation(2, 21)]
|
||||
),
|
||||
new FormattedError(
|
||||
Messages::nonInputTypeOnVarMessage('b', '[[DogOrCat!]]!'),
|
||||
FormattedError::create(
|
||||
VariablesAreInputTypes::nonInputTypeOnVarMessage('b', '[[CatOrDog!]]!'),
|
||||
[new SourceLocation(2, 30)]
|
||||
),
|
||||
new FormattedError(
|
||||
Messages::nonInputTypeOnVarMessage('c', 'Pet'),
|
||||
FormattedError::create(
|
||||
VariablesAreInputTypes::nonInputTypeOnVarMessage('c', 'Pet'),
|
||||
[new SourceLocation(2, 50)]
|
||||
)
|
||||
]
|
||||
|
@ -177,7 +177,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
$this->expectPassesRule(new VariablesInAllowedPosition, '
|
||||
query Query($boolVar: Boolean!)
|
||||
{
|
||||
dog @if: $boolVar
|
||||
dog @include(if: $boolVar)
|
||||
}
|
||||
');
|
||||
}
|
||||
@ -188,7 +188,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
$this->expectPassesRule(new VariablesInAllowedPosition, '
|
||||
query Query($boolVar: Boolean = false)
|
||||
{
|
||||
dog @if: $boolVar
|
||||
dog @include(if: $boolVar)
|
||||
}
|
||||
');
|
||||
}
|
||||
@ -204,7 +204,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('intArg', 'Int', 'Int!'),
|
||||
[new SourceLocation(5, 45)]
|
||||
)
|
||||
@ -226,7 +226,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('intArg', 'Int', 'Int!'),
|
||||
[new SourceLocation(3, 43)]
|
||||
)
|
||||
@ -252,7 +252,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('intArg', 'Int', 'Int!'),
|
||||
[new SourceLocation(7,43)]
|
||||
)
|
||||
@ -270,7 +270,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('stringVar', 'String', 'Boolean'),
|
||||
[new SourceLocation(5,39)]
|
||||
)
|
||||
@ -288,7 +288,7 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('stringVar', 'String', '[String]'),
|
||||
[new SourceLocation(5,45)]
|
||||
)
|
||||
@ -301,12 +301,12 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
$this->expectFailsRule(new VariablesInAllowedPosition, '
|
||||
query Query($boolVar: Boolean)
|
||||
{
|
||||
dog @if: $boolVar
|
||||
dog @include(if: $boolVar)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('boolVar', 'Boolean', 'Boolean!'),
|
||||
[new SourceLocation(4,18)]
|
||||
[new SourceLocation(4,26)]
|
||||
)
|
||||
]);
|
||||
}
|
||||
@ -317,12 +317,12 @@ class VariablesInAllowedPositionTest extends TestCase
|
||||
$this->expectFailsRule(new VariablesInAllowedPosition, '
|
||||
query Query($stringVar: String)
|
||||
{
|
||||
dog @if: $stringVar
|
||||
dog @include(if: $stringVar)
|
||||
}
|
||||
', [
|
||||
new FormattedError(
|
||||
FormattedError::create(
|
||||
Messages::badVarPosMessage('stringVar', 'String', 'Boolean!'),
|
||||
[new SourceLocation(4,18)]
|
||||
[new SourceLocation(4,26)]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user