mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-21 20:36:05 +03:00
Merge branch 'master' of https://github.com/webonyx/graphql-php
This commit is contained in:
commit
e6e9d9ea22
@ -20,7 +20,7 @@ build:
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 600
|
||||
timeout: 900
|
||||
|
||||
build_failure_conditions:
|
||||
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
|
||||
|
19
.travis.yml
19
.travis.yml
@ -4,12 +4,16 @@ language: php
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4snapshot
|
||||
- nightly
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- EXECUTOR=coroutine
|
||||
- EXECUTOR= DEPENDENCIES=--prefer-lowest
|
||||
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
|
||||
- EXECUTOR=
|
||||
- EXECUTOR=coroutine
|
||||
|
||||
|
||||
cache:
|
||||
@ -26,13 +30,13 @@ script: ./vendor/bin/phpunit --group default,ReactPromise
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
- php: 7.4snapshot
|
||||
- php: nightly
|
||||
|
||||
include:
|
||||
- stage: Test
|
||||
env: DEPENDENCIES=low
|
||||
install:
|
||||
- travis_retry composer update --prefer-dist --prefer-lowest
|
||||
- travis_retry composer update --prefer-dist {$DEPENDENCIES}
|
||||
|
||||
- stage: Test
|
||||
env: COVERAGE
|
||||
@ -40,10 +44,12 @@ jobs:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
|
||||
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
|
||||
script:
|
||||
- ./vendor/bin/phpunit --coverage-clover clover.xml
|
||||
- ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov
|
||||
- EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover clover.xml
|
||||
- ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml
|
||||
- wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml
|
||||
|
||||
- stage: Code Quality
|
||||
php: 7.1
|
||||
@ -57,3 +63,4 @@ jobs:
|
||||
env: STATIC_ANALYSIS
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script: composer static-analysis
|
||||
|
||||
|
@ -14,7 +14,7 @@ composer require webonyx/graphql-php
|
||||
```
|
||||
|
||||
## Documentation
|
||||
Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well
|
||||
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
|
||||
as in the [docs](docs/) folder of the distribution.
|
||||
|
||||
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
|
||||
|
@ -78,6 +78,9 @@ Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
|
||||
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
|
||||
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
|
||||
|
||||
### Breaking: new constructors
|
||||
|
||||
`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array.
|
||||
|
||||
## Upgrade v0.11.x > v0.12.x
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
"API"
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.1",
|
||||
"php": "^7.1||^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
@ -19,6 +19,7 @@
|
||||
"phpstan/phpstan": "0.10.5",
|
||||
"phpstan/phpstan-phpunit": "0.10.0",
|
||||
"phpstan/phpstan-strict-rules": "0.10.1",
|
||||
"phpunit/phpcov": "^5.0",
|
||||
"phpunit/phpunit": "^7.2",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*"
|
||||
|
@ -1,22 +1,28 @@
|
||||
# Integrations
|
||||
|
||||
* [Integration with Relay](https://github.com/ivome/graphql-relay-php)
|
||||
* [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse)
|
||||
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
|
||||
* Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)) via [Standard Server](executing-queries.md/#using-server)
|
||||
* [Standard Server](executing-queries.md/#using-server) – Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
|
||||
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) – Helps construct Relay related schema definitions.
|
||||
* Laravel
|
||||
- [Laravel GraphQL](https://github.com/Folkloreatelier/laravel-graphql) – Integration with Laravel 5
|
||||
- [laravel-graphql-relay](https://github.com/nuwave/laravel-graphql-relay) – Relay Helpers for Laravel
|
||||
- [Lighthouse](https://github.com/nuwave/lighthouse) – GraphQL Server for Laravel
|
||||
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony
|
||||
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
|
||||
|
||||
# GraphQL PHP Tools
|
||||
|
||||
* Define types with Doctrine ORM annotations ([for PHP7.1](https://github.com/Ecodev/graphql-doctrine), for [earlier PHP versions](https://github.com/rahuljayaraman/doctrine-graphql))
|
||||
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
|
||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
|
||||
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) - Simple library that provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
|
||||
* [GraphQLite](https://graphqlite.thecodingmachine.io) – Define your complete schema with annotations
|
||||
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations
|
||||
* [DataLoaderPHP](https://github.com/overblog/dataloader-php) – as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) – A PSR-15 middleware to support file uploads in GraphQL.
|
||||
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) – Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
|
||||
* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) – Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar
|
||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_
|
||||
|
||||
# General GraphQL Tools
|
||||
|
||||
* [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
|
||||
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) – GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
|
||||
* [GraphiQL](https://github.com/graphql/graphiql) – An in-browser IDE for exploring GraphQL
|
||||
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
||||
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
|
||||
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) –
|
||||
GraphiQL as Google Chrome extension
|
||||
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) - GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
|
||||
|
@ -7,7 +7,7 @@
|
||||
# About GraphQL
|
||||
|
||||
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
|
||||
It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**).
|
||||
It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**).
|
||||
|
||||
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
|
||||
engineers. Various implementations of this specification were written
|
||||
|
@ -11,7 +11,7 @@ use Throwable;
|
||||
|
||||
class Deferred
|
||||
{
|
||||
/** @var SplQueue */
|
||||
/** @var SplQueue|null */
|
||||
private static $queue;
|
||||
|
||||
/** @var callable */
|
||||
@ -20,21 +20,6 @@ class Deferred
|
||||
/** @var SyncPromise */
|
||||
public $promise;
|
||||
|
||||
public static function getQueue()
|
||||
{
|
||||
return self::$queue ?: self::$queue = new SplQueue();
|
||||
}
|
||||
|
||||
public static function runQueue()
|
||||
{
|
||||
$q = self::$queue;
|
||||
while ($q && ! $q->isEmpty()) {
|
||||
/** @var self $dfd */
|
||||
$dfd = $q->dequeue();
|
||||
$dfd->run();
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
@ -42,6 +27,25 @@ class Deferred
|
||||
self::getQueue()->enqueue($this);
|
||||
}
|
||||
|
||||
public static function getQueue() : SplQueue
|
||||
{
|
||||
if (self::$queue === null) {
|
||||
self::$queue = new SplQueue();
|
||||
}
|
||||
|
||||
return self::$queue;
|
||||
}
|
||||
|
||||
public static function runQueue() : void
|
||||
{
|
||||
$queue = self::getQueue();
|
||||
while (! $queue->isEmpty()) {
|
||||
/** @var self $dequeuedNodeValue */
|
||||
$dequeuedNodeValue = $queue->dequeue();
|
||||
$dequeuedNodeValue->run();
|
||||
}
|
||||
}
|
||||
|
||||
public function then($onFulfilled = null, $onRejected = null)
|
||||
{
|
||||
return $this->promise->then($onFulfilled, $onRejected);
|
||||
|
@ -74,7 +74,7 @@ class Executor
|
||||
* execution are collected in `$result->errors`.
|
||||
*
|
||||
* @param mixed|null $rootValue
|
||||
* @param mixed[]|null $contextValue
|
||||
* @param mixed|null $contextValue
|
||||
* @param mixed[]|ArrayAccess|null $variableValues
|
||||
* @param string|null $operationName
|
||||
*
|
||||
@ -119,8 +119,8 @@ class Executor
|
||||
*
|
||||
* Useful for async PHP platforms.
|
||||
*
|
||||
* @param mixed[]|null $rootValue
|
||||
* @param mixed[]|null $contextValue
|
||||
* @param mixed|null $rootValue
|
||||
* @param mixed|null $contextValue
|
||||
* @param mixed[]|null $variableValues
|
||||
* @param string|null $operationName
|
||||
*
|
||||
@ -163,7 +163,7 @@ class Executor
|
||||
*
|
||||
* @param mixed $source
|
||||
* @param mixed[] $args
|
||||
* @param mixed[]|null $context
|
||||
* @param mixed|null $context
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
|
@ -115,8 +115,8 @@ class ReferenceExecutor implements ExecutorImplementation
|
||||
* Constructs an ExecutionContext object from the arguments passed to
|
||||
* execute, which we will pass throughout the other execution methods.
|
||||
*
|
||||
* @param mixed[] $rootValue
|
||||
* @param mixed[] $contextValue
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param mixed[]|Traversable $rawVariableValues
|
||||
* @param string|null $operationName
|
||||
*
|
||||
@ -153,7 +153,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $operation) {
|
||||
if ($operation === null) {
|
||||
if ($operationName) {
|
||||
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
|
||||
} else {
|
||||
@ -165,7 +165,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
||||
);
|
||||
}
|
||||
$variableValues = null;
|
||||
if ($operation) {
|
||||
if ($operation !== null) {
|
||||
[$coercionErrors, $coercedVariableValues] = Values::getVariableValues(
|
||||
$schema,
|
||||
$operation->variableDefinitions ?: [],
|
||||
|
@ -152,8 +152,8 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
||||
|
||||
if (is_array($value)) {
|
||||
$array = [];
|
||||
foreach ($value as $item) {
|
||||
$array[] = self::resultToArray($item);
|
||||
foreach ($value as $key => $item) {
|
||||
$array[$key] = self::resultToArray($item);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ abstract class Node
|
||||
public $loc;
|
||||
|
||||
/**
|
||||
* @param (string|NameNode|NodeList|SelectionSetNode|Location|null)[] $vars
|
||||
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
|
||||
*/
|
||||
public function __construct(array $vars)
|
||||
{
|
||||
|
@ -46,6 +46,12 @@ class OperationParams
|
||||
*/
|
||||
public $variables;
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @var mixed[]|null
|
||||
*/
|
||||
public $extensions;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $originalInput;
|
||||
|
||||
@ -76,25 +82,39 @@ class OperationParams
|
||||
'id' => null, // alias to queryid
|
||||
'operationname' => null,
|
||||
'variables' => null,
|
||||
'extensions' => null,
|
||||
];
|
||||
|
||||
if ($params['variables'] === '') {
|
||||
$params['variables'] = null;
|
||||
}
|
||||
|
||||
if (is_string($params['variables'])) {
|
||||
$tmp = json_decode($params['variables'], true);
|
||||
if (! json_last_error()) {
|
||||
$params['variables'] = $tmp;
|
||||
// Some parameters could be provided as serialized JSON.
|
||||
foreach (['extensions', 'variables'] as $param) {
|
||||
if (! is_string($params[$param])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmp = json_decode($params[$param], true);
|
||||
if (json_last_error()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$params[$param] = $tmp;
|
||||
}
|
||||
|
||||
$instance->query = $params['query'];
|
||||
$instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
|
||||
$instance->operation = $params['operationname'];
|
||||
$instance->variables = $params['variables'];
|
||||
$instance->extensions = $params['extensions'];
|
||||
$instance->readOnly = (bool) $readonly;
|
||||
|
||||
// Apollo server/client compatibility: look for the queryid in extensions
|
||||
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
|
||||
$instance->queryId = $instance->extensions['persistedQuery']['sha256Hash'];
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ use function sprintf;
|
||||
/**
|
||||
* Class EnumType
|
||||
*/
|
||||
class EnumType extends Type implements InputType, OutputType, LeafType, NamedType
|
||||
class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
|
||||
{
|
||||
/** @var EnumTypeDefinitionNode|null */
|
||||
public $astNode;
|
||||
|
@ -18,7 +18,7 @@ use function sprintf;
|
||||
/**
|
||||
* Class InputObjectType
|
||||
*/
|
||||
class InputObjectType extends Type implements InputType, NamedType
|
||||
class InputObjectType extends Type implements InputType, NullableType, NamedType
|
||||
{
|
||||
/** @var InputObjectTypeDefinitionNode|null */
|
||||
public $astNode;
|
||||
|
@ -15,7 +15,7 @@ use function sprintf;
|
||||
/**
|
||||
* Class InterfaceType
|
||||
*/
|
||||
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType
|
||||
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
||||
{
|
||||
/** @var InterfaceTypeDefinitionNode|null */
|
||||
public $astNode;
|
||||
@ -107,7 +107,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
|
||||
* @param object $objectValue
|
||||
* @param mixed[] $context
|
||||
*
|
||||
* @return callable|null
|
||||
* @return Type|null
|
||||
*/
|
||||
public function resolveType($objectValue, $context, ResolveInfo $info)
|
||||
{
|
||||
|
@ -7,7 +7,7 @@ namespace GraphQL\Type\Definition;
|
||||
/**
|
||||
* Class ListOfType
|
||||
*/
|
||||
class ListOfType extends Type implements WrappingType, OutputType, InputType
|
||||
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
|
||||
{
|
||||
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
|
||||
public $ofType;
|
||||
|
@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
@ -13,13 +11,11 @@ use GraphQL\Utils\Utils;
|
||||
*/
|
||||
class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
{
|
||||
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType */
|
||||
/** @var NullableType */
|
||||
private $ofType;
|
||||
|
||||
/**
|
||||
* @param callable|Type $type
|
||||
*
|
||||
* @throws Exception
|
||||
* @param NullableType $type
|
||||
*/
|
||||
public function __construct($type)
|
||||
{
|
||||
@ -29,7 +25,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
/**
|
||||
* @param mixed $type
|
||||
*
|
||||
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
|
||||
* @return NullableType
|
||||
*/
|
||||
public static function assertNullableType($type)
|
||||
{
|
||||
@ -67,9 +63,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
/**
|
||||
* @param bool $recurse
|
||||
*
|
||||
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
|
||||
*
|
||||
* @throws InvariantViolation
|
||||
* @return Type
|
||||
*/
|
||||
public function getWrappedType($recurse = false)
|
||||
{
|
||||
|
20
src/Type/Definition/NullableType.php
Normal file
20
src/Type/Definition/NullableType.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
/*
|
||||
export type GraphQLNullableType =
|
||||
| GraphQLScalarType
|
||||
| GraphQLObjectType
|
||||
| GraphQLInterfaceType
|
||||
| GraphQLUnionType
|
||||
| GraphQLEnumType
|
||||
| GraphQLInputObjectType
|
||||
| GraphQLList<any>;
|
||||
*/
|
||||
|
||||
interface NullableType
|
||||
{
|
||||
}
|
@ -54,7 +54,7 @@ use function sprintf;
|
||||
* }
|
||||
* ]);
|
||||
*/
|
||||
class ObjectType extends Type implements OutputType, CompositeType, NamedType
|
||||
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType
|
||||
{
|
||||
/** @var ObjectTypeDefinitionNode|null */
|
||||
public $astNode;
|
||||
|
@ -23,7 +23,7 @@ class ResolveInfo
|
||||
* The name of the field being resolved
|
||||
*
|
||||
* @api
|
||||
* @var string|null
|
||||
* @var string
|
||||
*/
|
||||
public $fieldName;
|
||||
|
||||
@ -31,9 +31,9 @@ class ResolveInfo
|
||||
* AST of all nodes referencing this field in the query.
|
||||
*
|
||||
* @api
|
||||
* @var FieldNode[]|null
|
||||
* @var FieldNode[]
|
||||
*/
|
||||
public $fieldNodes;
|
||||
public $fieldNodes = [];
|
||||
|
||||
/**
|
||||
* Expected return type of the field being resolved
|
||||
@ -47,7 +47,7 @@ class ResolveInfo
|
||||
* Parent type of the field being resolved
|
||||
*
|
||||
* @api
|
||||
* @var ObjectType|null
|
||||
* @var ObjectType
|
||||
*/
|
||||
public $parentType;
|
||||
|
||||
@ -55,7 +55,7 @@ class ResolveInfo
|
||||
* Path to this field from the very root value
|
||||
*
|
||||
* @api
|
||||
* @var string[]
|
||||
* @var string[][]
|
||||
*/
|
||||
public $path;
|
||||
|
||||
@ -71,9 +71,9 @@ class ResolveInfo
|
||||
* AST of all fragments defined in query
|
||||
*
|
||||
* @api
|
||||
* @var FragmentDefinitionNode[]|null
|
||||
* @var FragmentDefinitionNode[]
|
||||
*/
|
||||
public $fragments;
|
||||
public $fragments = [];
|
||||
|
||||
/**
|
||||
* Root value passed to query execution
|
||||
@ -95,21 +95,29 @@ class ResolveInfo
|
||||
* Array of variables passed to query execution
|
||||
*
|
||||
* @api
|
||||
* @var mixed[]|null
|
||||
* @var mixed[]
|
||||
*/
|
||||
public $variableValues;
|
||||
public $variableValues = [];
|
||||
|
||||
/**
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
|
||||
* @param string[][] $path
|
||||
* @param FragmentDefinitionNode[] $fragments
|
||||
* @param mixed|null $rootValue
|
||||
* @param mixed[] $variableValues
|
||||
*/
|
||||
public function __construct(
|
||||
string $fieldName,
|
||||
$fieldNodes,
|
||||
$returnType,
|
||||
ObjectType $parentType,
|
||||
$path,
|
||||
array $path,
|
||||
Schema $schema,
|
||||
$fragments,
|
||||
array $fragments,
|
||||
$rootValue,
|
||||
?OperationDefinitionNode $operation,
|
||||
$variableValues
|
||||
array $variableValues
|
||||
) {
|
||||
$this->fieldName = $fieldName;
|
||||
$this->fieldNodes = $fieldNodes;
|
||||
|
@ -27,7 +27,7 @@ use function is_string;
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType
|
||||
abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NullableType, NamedType
|
||||
{
|
||||
/** @var ScalarTypeDefinitionNode|null */
|
||||
public $astNode;
|
||||
|
@ -137,7 +137,7 @@ abstract class Type implements JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType $wrappedType
|
||||
* @param NullableType $wrappedType
|
||||
*
|
||||
* @return NonNull
|
||||
*
|
||||
@ -338,7 +338,7 @@ abstract class Type implements JsonSerializable
|
||||
/**
|
||||
* @param Type $type
|
||||
*
|
||||
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
|
||||
* @return NullableType
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
|
@ -17,7 +17,7 @@ use function sprintf;
|
||||
/**
|
||||
* Class UnionType
|
||||
*/
|
||||
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType
|
||||
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
||||
{
|
||||
/** @var UnionTypeDefinitionNode */
|
||||
public $astNode;
|
||||
|
@ -112,27 +112,31 @@ class BreakingChangesFinder
|
||||
* @return string[][]
|
||||
*/
|
||||
public static function findTypesThatChangedKind(
|
||||
Schema $oldSchema,
|
||||
Schema $newSchema
|
||||
) {
|
||||
$oldTypeMap = $oldSchema->getTypeMap();
|
||||
$newTypeMap = $newSchema->getTypeMap();
|
||||
Schema $schemaA,
|
||||
Schema $schemaB
|
||||
) : iterable {
|
||||
$schemaATypeMap = $schemaA->getTypeMap();
|
||||
$schemaBTypeMap = $schemaB->getTypeMap();
|
||||
|
||||
$breakingChanges = [];
|
||||
foreach ($oldTypeMap as $typeName => $oldType) {
|
||||
if (! isset($newTypeMap[$typeName])) {
|
||||
foreach ($schemaATypeMap as $typeName => $schemaAType) {
|
||||
if (! isset($schemaBTypeMap[$typeName])) {
|
||||
continue;
|
||||
}
|
||||
$newType = $newTypeMap[$typeName];
|
||||
if ($oldType instanceof $newType) {
|
||||
$schemaBType = $schemaBTypeMap[$typeName];
|
||||
if ($schemaAType instanceof $schemaBType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldTypeKindName = self::typeKindName($oldType);
|
||||
$newTypeKindName = self::typeKindName($newType);
|
||||
if ($schemaBType instanceof $schemaAType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schemaATypeKindName = self::typeKindName($schemaAType);
|
||||
$schemaBTypeKindName = self::typeKindName($schemaBType);
|
||||
$breakingChanges[] = [
|
||||
'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND,
|
||||
'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}.",
|
||||
'description' => "${typeName} changed from ${schemaATypeKindName} to ${schemaBTypeKindName}.",
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -513,7 +513,14 @@ class SchemaExtender
|
||||
$schemaExtensions[] = $def;
|
||||
} elseif ($def instanceof TypeDefinitionNode) {
|
||||
$typeName = isset($def->name) ? $def->name->value : null;
|
||||
if ($schema->getType($typeName)) {
|
||||
|
||||
try {
|
||||
$type = $schema->getType($typeName);
|
||||
} catch (Error $error) {
|
||||
$type = null;
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]);
|
||||
}
|
||||
$typeDefinitionMap[$typeName] = $def;
|
||||
|
@ -8,6 +8,7 @@ use Exception;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\BooleanValueNode;
|
||||
use GraphQL\Language\AST\EnumValueNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FloatValueNode;
|
||||
use GraphQL\Language\AST\IntValueNode;
|
||||
use GraphQL\Language\AST\ListValueNode;
|
||||
@ -21,6 +22,7 @@ use GraphQL\Language\Printer;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\EnumValueDefinition;
|
||||
use GraphQL\Type\Definition\FieldArgument;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
@ -46,8 +48,14 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
{
|
||||
public function getVisitor(ValidationContext $context)
|
||||
{
|
||||
$fieldName = '';
|
||||
return [
|
||||
NodeKind::NULL => static function (NullValueNode $node) use ($context) {
|
||||
NodeKind::FIELD => [
|
||||
'enter' => static function (FieldNode $node) use (&$fieldName) {
|
||||
$fieldName = $node->name->value;
|
||||
},
|
||||
],
|
||||
NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) {
|
||||
$type = $context->getInputType();
|
||||
if (! ($type instanceof NonNull)) {
|
||||
return;
|
||||
@ -55,30 +63,31 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
|
||||
$context->reportError(
|
||||
new Error(
|
||||
self::badValueMessage((string) $type, Printer::doPrint($node)),
|
||||
self::getBadValueMessage((string) $type, Printer::doPrint($node), null, $context, $fieldName),
|
||||
$node
|
||||
)
|
||||
);
|
||||
},
|
||||
NodeKind::LST => function (ListValueNode $node) use ($context) {
|
||||
NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) {
|
||||
// Note: TypeInfo will traverse into a list's item type, so look to the
|
||||
// parent input type to check if it is a list.
|
||||
$type = Type::getNullableType($context->getParentInputType());
|
||||
if (! $type instanceof ListOfType) {
|
||||
$this->isValidScalar($context, $node);
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
},
|
||||
NodeKind::OBJECT => function (ObjectValueNode $node) use ($context) {
|
||||
NodeKind::OBJECT => function (ObjectValueNode $node) use ($context, &$fieldName) {
|
||||
// Note: TypeInfo will traverse into a list's item type, so look to the
|
||||
// parent input type to check if it is a list.
|
||||
$type = Type::getNamedType($context->getInputType());
|
||||
if (! $type instanceof InputObjectType) {
|
||||
$this->isValidScalar($context, $node);
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
unset($fieldName);
|
||||
// Ensure every required field exists.
|
||||
$inputFields = $type->getFields();
|
||||
$nodeFields = iterator_to_array($node->fields);
|
||||
@ -127,34 +136,36 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
)
|
||||
);
|
||||
},
|
||||
NodeKind::ENUM => function (EnumValueNode $node) use ($context) {
|
||||
NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) {
|
||||
$type = Type::getNamedType($context->getInputType());
|
||||
if (! $type instanceof EnumType) {
|
||||
$this->isValidScalar($context, $node);
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
} elseif (! $type->getValue($node->value)) {
|
||||
$context->reportError(
|
||||
new Error(
|
||||
self::badValueMessage(
|
||||
self::getBadValueMessage(
|
||||
$type->name,
|
||||
Printer::doPrint($node),
|
||||
$this->enumTypeSuggestion($type, $node)
|
||||
$this->enumTypeSuggestion($type, $node),
|
||||
$context,
|
||||
$fieldName
|
||||
),
|
||||
$node
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
NodeKind::INT => function (IntValueNode $node) use ($context) {
|
||||
$this->isValidScalar($context, $node);
|
||||
NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) {
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
},
|
||||
NodeKind::FLOAT => function (FloatValueNode $node) use ($context) {
|
||||
$this->isValidScalar($context, $node);
|
||||
NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) {
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
},
|
||||
NodeKind::STRING => function (StringValueNode $node) use ($context) {
|
||||
$this->isValidScalar($context, $node);
|
||||
NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) {
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
},
|
||||
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) {
|
||||
$this->isValidScalar($context, $node);
|
||||
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) {
|
||||
$this->isValidScalar($context, $node, $fieldName);
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -165,7 +176,7 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
($message ? "; ${message}" : '.');
|
||||
}
|
||||
|
||||
private function isValidScalar(ValidationContext $context, ValueNode $node)
|
||||
private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName)
|
||||
{
|
||||
// Report any error at the full type expected by the location.
|
||||
$locationType = $context->getInputType();
|
||||
@ -179,10 +190,12 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
if (! $type instanceof ScalarType) {
|
||||
$context->reportError(
|
||||
new Error(
|
||||
self::badValueMessage(
|
||||
self::getBadValueMessage(
|
||||
(string) $locationType,
|
||||
Printer::doPrint($node),
|
||||
$this->enumTypeSuggestion($type, $node)
|
||||
$this->enumTypeSuggestion($type, $node),
|
||||
$context,
|
||||
$fieldName
|
||||
),
|
||||
$node
|
||||
)
|
||||
@ -199,32 +212,28 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
// Ensure a reference to the original error is maintained.
|
||||
$context->reportError(
|
||||
new Error(
|
||||
self::badValueMessage(
|
||||
self::getBadValueMessage(
|
||||
(string) $locationType,
|
||||
Printer::doPrint($node),
|
||||
$error->getMessage()
|
||||
$error->getMessage(),
|
||||
$context,
|
||||
$fieldName
|
||||
),
|
||||
$node,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$error
|
||||
$node
|
||||
)
|
||||
);
|
||||
} catch (Throwable $error) {
|
||||
// Ensure a reference to the original error is maintained.
|
||||
$context->reportError(
|
||||
new Error(
|
||||
self::badValueMessage(
|
||||
self::getBadValueMessage(
|
||||
(string) $locationType,
|
||||
Printer::doPrint($node),
|
||||
$error->getMessage()
|
||||
$error->getMessage(),
|
||||
$context,
|
||||
$fieldName
|
||||
),
|
||||
$node,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$error
|
||||
$node
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -247,6 +256,12 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
}
|
||||
}
|
||||
|
||||
public static function badArgumentValueMessage($typeName, $valueName, $fieldName, $argName, $message = null)
|
||||
{
|
||||
return sprintf('Field "%s" argument "%s" requires type %s, found %s', $fieldName, $argName, $typeName, $valueName) .
|
||||
($message ? sprintf('; %s', $message) : '.');
|
||||
}
|
||||
|
||||
public static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName)
|
||||
{
|
||||
return sprintf('Field %s.%s of required type %s was not provided.', $typeName, $fieldName, $fieldTypeName);
|
||||
@ -257,4 +272,15 @@ class ValuesOfCorrectType extends ValidationRule
|
||||
return sprintf('Field "%s" is not defined by type %s', $fieldName, $typeName) .
|
||||
($message ? sprintf('; %s', $message) : '.');
|
||||
}
|
||||
|
||||
private static function getBadValueMessage($typeName, $valueName, $message = null, $context = null, $fieldName = null)
|
||||
{
|
||||
if ($context) {
|
||||
$arg = $context->getArgument();
|
||||
if ($arg) {
|
||||
return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message);
|
||||
}
|
||||
}
|
||||
return self::badValueMessage($typeName, $valueName, $message);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace GraphQL\Tests\Executor;
|
||||
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Type\Definition\CustomScalarType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
@ -20,6 +21,13 @@ class ExecutorSchemaTest extends TestCase
|
||||
*/
|
||||
public function testExecutesUsingASchema() : void
|
||||
{
|
||||
$BlogSerializableValueType = new CustomScalarType([
|
||||
'name' => 'JsonSerializableValueScalar',
|
||||
'serialize' => static function ($value) {
|
||||
return $value;
|
||||
},
|
||||
]);
|
||||
|
||||
$BlogArticle = null;
|
||||
$BlogImage = new ObjectType([
|
||||
'name' => 'Image',
|
||||
@ -57,6 +65,7 @@ class ExecutorSchemaTest extends TestCase
|
||||
'title' => ['type' => Type::string()],
|
||||
'body' => ['type' => Type::string()],
|
||||
'keywords' => ['type' => Type::listOf(Type::string())],
|
||||
'meta' => ['type' => $BlogSerializableValueType],
|
||||
],
|
||||
]);
|
||||
|
||||
@ -113,6 +122,7 @@ class ExecutorSchemaTest extends TestCase
|
||||
keywords
|
||||
}
|
||||
}
|
||||
meta
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,6 +201,7 @@ class ExecutorSchemaTest extends TestCase
|
||||
'keywords' => ['foo', 'bar', '1', 'true', null],
|
||||
],
|
||||
],
|
||||
'meta' => [ 'title' => 'My Article 1 | My Blog' ],
|
||||
],
|
||||
],
|
||||
];
|
||||
@ -210,6 +221,7 @@ class ExecutorSchemaTest extends TestCase
|
||||
'body' => 'This is a post',
|
||||
'hidden' => 'This data is not exposed in the schema',
|
||||
'keywords' => ['foo', 'bar', 1, true, null],
|
||||
'meta' => ['title' => 'My Article 1 | My Blog'],
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,7 @@ class RequestParsingTest extends TestCase
|
||||
];
|
||||
|
||||
foreach ($parsed as $source => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, null, null, $source);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, null, null, null, $source);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $source);
|
||||
}
|
||||
}
|
||||
@ -91,6 +91,7 @@ class RequestParsingTest extends TestCase
|
||||
$queryId = null,
|
||||
$variables = null,
|
||||
$operation = null,
|
||||
$extensions = null,
|
||||
$message = ''
|
||||
) {
|
||||
self::assertInstanceOf(OperationParams::class, $params, $message);
|
||||
@ -99,6 +100,7 @@ class RequestParsingTest extends TestCase
|
||||
self::assertSame($queryId, $params->queryId, $message);
|
||||
self::assertSame($variables, $params->variables, $message);
|
||||
self::assertSame($operation, $params->operation, $message);
|
||||
self::assertSame($extensions, $params->extensions, $message);
|
||||
}
|
||||
|
||||
public function testParsesUrlencodedRequest() : void
|
||||
@ -118,7 +120,7 @@ class RequestParsingTest extends TestCase
|
||||
];
|
||||
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $method);
|
||||
}
|
||||
}
|
||||
@ -175,7 +177,7 @@ class RequestParsingTest extends TestCase
|
||||
];
|
||||
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
|
||||
self::assertTrue($parsedBody->isReadonly(), $method);
|
||||
}
|
||||
}
|
||||
@ -230,7 +232,7 @@ class RequestParsingTest extends TestCase
|
||||
];
|
||||
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $method);
|
||||
}
|
||||
}
|
||||
@ -286,19 +288,21 @@ class RequestParsingTest extends TestCase
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $method);
|
||||
}
|
||||
}
|
||||
|
||||
public function testParsesVariablesAsJSON() : void
|
||||
public function testParsesParamsAsJSON() : void
|
||||
{
|
||||
$query = '{my query}';
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$variables = ['test1' => 1, 'test2' => 2];
|
||||
$extensions = ['test3' => 3, 'test4' => 4];
|
||||
$operation = 'op';
|
||||
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'extensions' => json_encode($extensions),
|
||||
'variables' => json_encode($variables),
|
||||
'operationName' => $operation,
|
||||
];
|
||||
@ -307,7 +311,7 @@ class RequestParsingTest extends TestCase
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $extensions, $method);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $method);
|
||||
}
|
||||
}
|
||||
@ -328,7 +332,29 @@ class RequestParsingTest extends TestCase
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||
self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $method);
|
||||
}
|
||||
}
|
||||
|
||||
public function testParsesApolloPersistedQueryJSONRequest() : void
|
||||
{
|
||||
$queryId = 'my-query-id';
|
||||
$extensions = ['persistedQuery' => ['sha256Hash' => $queryId]];
|
||||
$variables = ['test' => 1, 'test2' => 2];
|
||||
$operation = 'op';
|
||||
|
||||
$body = [
|
||||
'extensions' => $extensions,
|
||||
'variables' => $variables,
|
||||
'operationName' => $operation,
|
||||
];
|
||||
$parsed = [
|
||||
'raw' => $this->parseRawRequest('application/json', json_encode($body)),
|
||||
'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
|
||||
];
|
||||
foreach ($parsed as $method => $parsedBody) {
|
||||
self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $extensions, $method);
|
||||
self::assertFalse($parsedBody->isReadOnly(), $method);
|
||||
}
|
||||
}
|
||||
@ -360,6 +386,7 @@ class RequestParsingTest extends TestCase
|
||||
null,
|
||||
$body[0]['variables'],
|
||||
$body[0]['operationName'],
|
||||
null,
|
||||
$method
|
||||
);
|
||||
self::assertValidOperationParams(
|
||||
@ -368,6 +395,7 @@ class RequestParsingTest extends TestCase
|
||||
$body[1]['queryId'],
|
||||
$body[1]['variables'],
|
||||
$body[1]['operationName'],
|
||||
null,
|
||||
$method
|
||||
);
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromEnum: "GREEN") }',
|
||||
null,
|
||||
[
|
||||
'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?',
|
||||
'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found "GREEN"; Did you mean the enum value GREEN?',
|
||||
'locations' => [new SourceLocation(1, 23)],
|
||||
]
|
||||
);
|
||||
@ -268,7 +268,7 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromEnum: GREENISH) }',
|
||||
null,
|
||||
[
|
||||
'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?',
|
||||
'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found GREENISH; Did you mean the enum value GREEN?',
|
||||
'locations' => [new SourceLocation(1, 23)],
|
||||
]
|
||||
);
|
||||
@ -283,7 +283,7 @@ class EnumTypeTest extends TestCase
|
||||
'{ colorEnum(fromEnum: green) }',
|
||||
null,
|
||||
[
|
||||
'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?',
|
||||
'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found green; Did you mean the enum value GREEN?',
|
||||
'locations' => [new SourceLocation(1, 23)],
|
||||
]
|
||||
);
|
||||
@ -313,7 +313,7 @@ class EnumTypeTest extends TestCase
|
||||
$this->expectFailure(
|
||||
'{ colorEnum(fromEnum: 1) }',
|
||||
null,
|
||||
'Expected type Color, found 1.'
|
||||
'Field "colorEnum" argument "fromEnum" requires type Color, found 1.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ class EnumTypeTest extends TestCase
|
||||
$this->expectFailure(
|
||||
'{ colorEnum(fromInt: GREEN) }',
|
||||
null,
|
||||
'Expected type Int, found GREEN.'
|
||||
'Field "colorEnum" argument "fromInt" requires type Int, found GREEN.'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -125,6 +125,47 @@ class BreakingChangesFinderTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to compare type of class A (old type) and type of class B (new type)
|
||||
* Class B extends A but are evaluated as same types (if all properties match).
|
||||
* The reason is that when constructing schema from remote schema,
|
||||
* we have no certain way to get information about our classes.
|
||||
* Thus object types from remote schema are constructed as Object Type
|
||||
* while their local counterparts are usually a subclass of Object Type.
|
||||
*
|
||||
* @see https://github.com/webonyx/graphql-php/pull/431
|
||||
*/
|
||||
public function testShouldNotMarkTypesWithInheritedClassesAsChanged() : void
|
||||
{
|
||||
$objectTypeConstructedFromRemoteSchema = new ObjectType([
|
||||
'name' => 'ObjectType',
|
||||
'fields' => [
|
||||
'field1' => ['type' => Type::string()],
|
||||
],
|
||||
]);
|
||||
|
||||
$localObjectType = new class([
|
||||
'name' => 'ObjectType',
|
||||
'fields' => [
|
||||
'field1' => ['type' => Type::string()],
|
||||
],
|
||||
]) extends ObjectType{
|
||||
};
|
||||
|
||||
$schemaA = new Schema([
|
||||
'query' => $this->queryType,
|
||||
'types' => [$objectTypeConstructedFromRemoteSchema],
|
||||
]);
|
||||
|
||||
$schemaB = new Schema([
|
||||
'query' => $this->queryType,
|
||||
'types' => [$localObjectType],
|
||||
]);
|
||||
|
||||
self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaA, $schemaB));
|
||||
self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaB, $schemaA));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('should detect if a field on a type was deleted or changed type')
|
||||
*/
|
||||
|
@ -24,6 +24,7 @@ use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Utils\BuildSchema;
|
||||
use GraphQL\Utils\SchemaExtender;
|
||||
use GraphQL\Utils\SchemaPrinter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -1962,4 +1963,48 @@ extend type Query {
|
||||
$result = GraphQL::executeQuery($extendedSchema, $query);
|
||||
self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see https://github.com/webonyx/graphql-php/issues/180
|
||||
*/
|
||||
public function testShouldBeAbleToIntroduceNewTypesThroughExtension()
|
||||
{
|
||||
$sdl = '
|
||||
type Query {
|
||||
defaultValue: String
|
||||
}
|
||||
type Foo {
|
||||
value: Int
|
||||
}
|
||||
';
|
||||
|
||||
$documentNode = Parser::parse($sdl);
|
||||
$schema = BuildSchema::build($documentNode);
|
||||
|
||||
$extensionSdl = '
|
||||
type Bar {
|
||||
foo: Foo
|
||||
}
|
||||
';
|
||||
|
||||
$extendedDocumentNode = Parser::parse($extensionSdl);
|
||||
$extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode);
|
||||
|
||||
$expected = '
|
||||
type Bar {
|
||||
foo: Foo
|
||||
}
|
||||
|
||||
type Foo {
|
||||
value: Int
|
||||
}
|
||||
|
||||
type Query {
|
||||
defaultValue: String
|
||||
}
|
||||
';
|
||||
|
||||
static::assertEquals($this->dedent($expected), SchemaPrinter::doPrint($extendedSchema));
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class ValidationTest extends ValidatorTestCase
|
||||
';
|
||||
|
||||
$expectedError = [
|
||||
'message' => 'Expected type Invalid, found "bad value"; Invalid scalar is always invalid: bad value',
|
||||
'message' => 'Field "invalidArg" argument "arg" requires type Invalid, found "bad value"; Invalid scalar is always invalid: bad value',
|
||||
'locations' => [['line' => 3, 'column' => 25]],
|
||||
];
|
||||
|
||||
|
@ -240,7 +240,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('String', '1', 4, 39),
|
||||
$this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found 1.', 4, 39),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -257,6 +257,11 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function badValueWithMessage($message, $line, $column)
|
||||
{
|
||||
return FormattedError::create($message, [new SourceLocation($line, $column)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('Float into String')
|
||||
*/
|
||||
@ -272,7 +277,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('String', '1.0', 4, 39),
|
||||
$this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found 1.0.', 4, 39),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -294,7 +299,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('String', 'true', 4, 39),
|
||||
$this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found true.', 4, 39),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -314,7 +319,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('String', 'BAR', 4, 39),
|
||||
$this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found BAR.', 4, 39),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -334,7 +339,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int', '"3"', 4, 33),
|
||||
$this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found "3".', 4, 33),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -354,7 +359,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int', '829384293849283498239482938', 4, 33),
|
||||
$this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 829384293849283498239482938.', 4, 33),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -376,7 +381,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int', 'FOO', 4, 33),
|
||||
$this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found FOO.', 4, 33),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -396,7 +401,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int', '3.0', 4, 33),
|
||||
$this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 3.0.', 4, 33),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -416,7 +421,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int', '3.333', 4, 33),
|
||||
$this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 3.333.', 4, 33),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -436,7 +441,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Float', '"3.333"', 4, 37),
|
||||
$this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found "3.333".', 4, 37),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -456,7 +461,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Float', 'true', 4, 37),
|
||||
$this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found true.', 4, 37),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -478,7 +483,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Float', 'FOO', 4, 37),
|
||||
$this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found FOO.', 4, 37),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -498,7 +503,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Boolean', '2', 4, 41),
|
||||
$this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found 2.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -518,7 +523,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Boolean', '1.0', 4, 41),
|
||||
$this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found 1.0.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -540,7 +545,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Boolean', '"true"', 4, 41),
|
||||
$this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found "true".', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -560,7 +565,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Boolean', 'TRUE', 4, 41),
|
||||
$this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found TRUE.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -580,7 +585,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('ID', '1.0', 4, 31),
|
||||
$this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found 1.0.', 4, 31),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -600,7 +605,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('ID', 'true', 4, 31),
|
||||
$this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found true.', 4, 31),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -622,7 +627,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('ID', 'SOMETHING', 4, 31),
|
||||
$this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found SOMETHING.', 4, 31),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -642,7 +647,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('DogCommand', '2', 4, 41),
|
||||
$this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found 2.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -662,7 +667,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('DogCommand', '1.0', 4, 41),
|
||||
$this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found 1.0.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -684,13 +689,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue(
|
||||
'DogCommand',
|
||||
'"SIT"',
|
||||
4,
|
||||
41,
|
||||
'Did you mean the enum value SIT?'
|
||||
),
|
||||
$this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found "SIT"; Did you mean the enum value SIT?', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -710,7 +709,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('DogCommand', 'true', 4, 41),
|
||||
$this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found true.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -730,7 +729,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('DogCommand', 'JUGGLE', 4, 41),
|
||||
$this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found JUGGLE.', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -750,13 +749,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue(
|
||||
'DogCommand',
|
||||
'sit',
|
||||
4,
|
||||
41,
|
||||
'Did you mean the enum value SIT?'
|
||||
),
|
||||
$this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found sit; Did you mean the enum value SIT?', 4, 41),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -846,7 +839,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('String', '2', 4, 55),
|
||||
$this->badValueWithMessage('Field "stringListArgField" argument "stringListArg" requires type String, found 2.', 4, 55),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -866,7 +859,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('[String]', '1', 4, 47),
|
||||
$this->badValueWithMessage('Field "stringListArgField" argument "stringListArg" requires type [String], found 1.', 4, 47),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -1060,8 +1053,8 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int!', '"two"', 4, 32),
|
||||
$this->badValue('Int!', '"one"', 4, 45),
|
||||
$this->badValueWithMessage('Field "multipleReqs" argument "req2" requires type Int!, found "two".', 4, 32),
|
||||
$this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found "one".', 4, 45),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -1081,7 +1074,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int!', '"one"', 4, 32),
|
||||
$this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found "one".', 4, 32),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -1103,7 +1096,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Int!', 'null', 4, 32),
|
||||
$this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found null.', 4, 32),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -1277,7 +1270,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('String', '2', 5, 40),
|
||||
$this->badValueWithMessage('Field "complexArgField" argument "complexArg" requires type String, found 2.', 5, 40),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -1340,19 +1333,13 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue(
|
||||
'Invalid',
|
||||
'123',
|
||||
3,
|
||||
27,
|
||||
'Invalid scalar is always invalid: 123'
|
||||
),
|
||||
$this->badValueWithMessage('Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123', 3, 27),
|
||||
]
|
||||
);
|
||||
|
||||
self::assertEquals(
|
||||
'Invalid scalar is always invalid: 123',
|
||||
$errors[0]->getPrevious()->getMessage()
|
||||
'Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123',
|
||||
$errors[0]->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@ -1411,8 +1398,8 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
||||
}
|
||||
',
|
||||
[
|
||||
$this->badValue('Boolean!', '"yes"', 3, 28),
|
||||
$this->badValue('Boolean!', 'ENUM', 4, 28),
|
||||
$this->badValueWithMessage('Field "dog" argument "if" requires type Boolean!, found "yes".', 3, 28),
|
||||
$this->badValueWithMessage('Field "name" argument "if" requires type Boolean!, found ENUM.', 4, 28),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user