mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 06:16:05 +03:00
BuildSchema and SchemaPrinter
This commit is contained in:
parent
0bd7c9d405
commit
fc629a292d
@ -94,7 +94,7 @@ class Values
|
|||||||
* @return array
|
* @return array
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public static function getArgumentValues($def, $node, $variableValues)
|
public static function getArgumentValues($def, $node, $variableValues = null)
|
||||||
{
|
{
|
||||||
$argDefs = $def->args;
|
$argDefs = $def->args;
|
||||||
$argNodes = $node->arguments;
|
$argNodes = $node->arguments;
|
||||||
|
@ -107,7 +107,7 @@ class Directive
|
|||||||
new FieldArgument([
|
new FieldArgument([
|
||||||
'name' => 'if',
|
'name' => 'if',
|
||||||
'type' => Type::nonNull(Type::boolean()),
|
'type' => Type::nonNull(Type::boolean()),
|
||||||
'description' => 'Skipped when true'
|
'description' => 'Skipped when true.'
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
]),
|
]),
|
||||||
|
@ -235,8 +235,8 @@ EOD;
|
|||||||
'description' =>
|
'description' =>
|
||||||
'A GraphQL Schema defines the capabilities of a GraphQL ' .
|
'A GraphQL Schema defines the capabilities of a GraphQL ' .
|
||||||
'server. It exposes all available types and directives on ' .
|
'server. It exposes all available types and directives on ' .
|
||||||
'the server, as well as the entry points for query and ' .
|
'the server, as well as the entry points for query, mutation, and ' .
|
||||||
'mutation operations.',
|
'subscription operations.',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'types' => [
|
'types' => [
|
||||||
'description' => 'A list of all types supported by this server.',
|
'description' => 'A list of all types supported by this server.',
|
||||||
@ -288,7 +288,7 @@ EOD;
|
|||||||
'name' => '__Directive',
|
'name' => '__Directive',
|
||||||
'description' => 'A Directive provides a way to describe alternate runtime execution and ' .
|
'description' => 'A Directive provides a way to describe alternate runtime execution and ' .
|
||||||
'type validation behavior in a GraphQL document.' .
|
'type validation behavior in a GraphQL document.' .
|
||||||
'\n\nIn some cases, you need to provide options to alter GraphQL’s ' .
|
"\n\nIn some cases, you need to provide options to alter GraphQL's " .
|
||||||
'execution behavior in ways field arguments will not suffice, such as ' .
|
'execution behavior in ways field arguments will not suffice, such as ' .
|
||||||
'conditionally including or skipping a field. Directives provide this by ' .
|
'conditionally including or skipping a field. Directives provide this by ' .
|
||||||
'describing additional information to the executor.',
|
'describing additional information to the executor.',
|
||||||
@ -664,7 +664,7 @@ EOD;
|
|||||||
if (!isset(self::$map['__TypeKind'])) {
|
if (!isset(self::$map['__TypeKind'])) {
|
||||||
self::$map['__TypeKind'] = new EnumType([
|
self::$map['__TypeKind'] = new EnumType([
|
||||||
'name' => '__TypeKind',
|
'name' => '__TypeKind',
|
||||||
'description' => 'An enum describing what kind of type a given __Type is.',
|
'description' => 'An enum describing what kind of type a given `__Type` is.',
|
||||||
'values' => [
|
'values' => [
|
||||||
'SCALAR' => [
|
'SCALAR' => [
|
||||||
'value' => TypeKind::SCALAR,
|
'value' => TypeKind::SCALAR,
|
||||||
|
@ -180,6 +180,14 @@ class Utils
|
|||||||
return $grouped;
|
return $grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function keyValMap($traversable, callable $keyFn, callable $valFn)
|
||||||
|
{
|
||||||
|
return array_reduce($traversable, function ($map, $item) use ($keyFn, $valFn) {
|
||||||
|
$map[$keyFn($item)] = $valFn($item);
|
||||||
|
return $map;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $traversable
|
* @param $traversable
|
||||||
* @param callable $predicate
|
* @param callable $predicate
|
||||||
|
530
src/Utils/BuildSchema.php
Normal file
530
src/Utils/BuildSchema.php
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Executor\Values;
|
||||||
|
use GraphQL\Language\AST\DirectiveDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
|
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\TypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\TypeNode;
|
||||||
|
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\Token;
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\Directive;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
use GraphQL\Type\Definition\FieldArgument;
|
||||||
|
use GraphQL\Type\Definition\NonNull;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\CustomScalarType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
|
use GraphQL\Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BuildSchema
|
||||||
|
* @package GraphQL\Utils
|
||||||
|
*/
|
||||||
|
class BuildSchema
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Type $innerType
|
||||||
|
* @param TypeNode $inputTypeNode
|
||||||
|
* @return Type
|
||||||
|
*/
|
||||||
|
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
|
||||||
|
{
|
||||||
|
if ($inputTypeNode->kind == NodeKind::LIST_TYPE) {
|
||||||
|
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
|
||||||
|
}
|
||||||
|
if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) {
|
||||||
|
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
|
||||||
|
Utils::invariant(!($wrappedType instanceof NonNull), 'No nesting nonnull.');
|
||||||
|
return Type::nonNull($wrappedType);
|
||||||
|
}
|
||||||
|
return $innerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNamedTypeNode(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$namedType = $typeNode;
|
||||||
|
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
|
||||||
|
$namedType = $namedType->type;
|
||||||
|
}
|
||||||
|
return $namedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This takes the ast of a schema document produced by the parse function in
|
||||||
|
* GraphQL\Language\Parser.
|
||||||
|
*
|
||||||
|
* If no schema definition is provided, then it will look for types named Query
|
||||||
|
* and Mutation.
|
||||||
|
*
|
||||||
|
* Given that AST it constructs a GraphQLSchema. The resulting schema
|
||||||
|
* has no resolve methods, so execution will use default resolvers.
|
||||||
|
*
|
||||||
|
* @param DocumentNode $ast
|
||||||
|
* @return Schema
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public static function buildAST(DocumentNode $ast)
|
||||||
|
{
|
||||||
|
$builder = new self($ast);
|
||||||
|
return $builder->buildSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
private $ast;
|
||||||
|
private $innerTypeMap;
|
||||||
|
private $nodeMap;
|
||||||
|
|
||||||
|
public function __construct(DocumentNode $ast)
|
||||||
|
{
|
||||||
|
$this->ast = $ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildSchema()
|
||||||
|
{
|
||||||
|
$schemaDef = null;
|
||||||
|
$typeDefs = [];
|
||||||
|
$this->nodeMap = [];
|
||||||
|
$directiveDefs = [];
|
||||||
|
foreach ($this->ast->definitions as $d) {
|
||||||
|
switch ($d->kind) {
|
||||||
|
case NodeKind::SCHEMA_DEFINITION:
|
||||||
|
if ($schemaDef) {
|
||||||
|
throw new Error('Must provide only one schema definition.');
|
||||||
|
}
|
||||||
|
$schemaDef = $d;
|
||||||
|
break;
|
||||||
|
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||||
|
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||||
|
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||||
|
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||||
|
case NodeKind::UNION_TYPE_DEFINITION:
|
||||||
|
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||||
|
$typeDefs[] = $d;
|
||||||
|
$this->nodeMap[$d->name->value] = $d;
|
||||||
|
break;
|
||||||
|
case NodeKind::DIRECTIVE_DEFINITION:
|
||||||
|
$directiveDefs[] = $d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$queryTypeName = null;
|
||||||
|
$mutationTypeName = null;
|
||||||
|
$subscriptionTypeName = null;
|
||||||
|
if ($schemaDef) {
|
||||||
|
foreach ($schemaDef->operationTypes as $operationType) {
|
||||||
|
$typeName = $operationType->type->name->value;
|
||||||
|
if ($operationType->operation === 'query') {
|
||||||
|
if ($queryTypeName) {
|
||||||
|
throw new Error('Must provide only one query type in schema.');
|
||||||
|
}
|
||||||
|
if (!isset($this->nodeMap[$typeName])) {
|
||||||
|
throw new Error(
|
||||||
|
'Specified query type "' . $typeName . '" not found in document.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$queryTypeName = $typeName;
|
||||||
|
} else if ($operationType->operation === 'mutation') {
|
||||||
|
if ($mutationTypeName) {
|
||||||
|
throw new Error('Must provide only one mutation type in schema.');
|
||||||
|
}
|
||||||
|
if (!isset($this->nodeMap[$typeName])) {
|
||||||
|
throw new Error(
|
||||||
|
'Specified mutation type "' . $typeName . '" not found in document.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$mutationTypeName = $typeName;
|
||||||
|
} else if ($operationType->operation === 'subscription') {
|
||||||
|
if ($subscriptionTypeName) {
|
||||||
|
throw new Error('Must provide only one subscription type in schema.');
|
||||||
|
}
|
||||||
|
if (!isset($this->nodeMap[$typeName])) {
|
||||||
|
throw new Error(
|
||||||
|
'Specified subscription type "' . $typeName . '" not found in document.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$subscriptionTypeName = $typeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isset($this->nodeMap['Query'])) {
|
||||||
|
$queryTypeName = 'Query';
|
||||||
|
}
|
||||||
|
if (isset($this->nodeMap['Mutation'])) {
|
||||||
|
$mutationTypeName = 'Mutation';
|
||||||
|
}
|
||||||
|
if (isset($this->nodeMap['Subscription'])) {
|
||||||
|
$subscriptionTypeName = 'Subscription';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$queryTypeName) {
|
||||||
|
throw new Error(
|
||||||
|
'Must provide schema definition with query type or a type named Query.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->innerTypeMap = [
|
||||||
|
'String' => Type::string(),
|
||||||
|
'Int' => Type::int(),
|
||||||
|
'Float' => Type::float(),
|
||||||
|
'Boolean' => Type::boolean(),
|
||||||
|
'ID' => Type::id(),
|
||||||
|
'__Schema' => Introspection::_schema(),
|
||||||
|
'__Directive' => Introspection::_directive(),
|
||||||
|
'__DirectiveLocation' => Introspection::_directiveLocation(),
|
||||||
|
'__Type' => Introspection::_type(),
|
||||||
|
'__Field' => Introspection::_field(),
|
||||||
|
'__InputValue' => Introspection::_inputValue(),
|
||||||
|
'__EnumValue' => Introspection::_enumValue(),
|
||||||
|
'__TypeKind' => Introspection::_typeKind(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$types = array_map(function($def) {
|
||||||
|
return $this->typeDefNamed($def->name->value);
|
||||||
|
}, $typeDefs);
|
||||||
|
|
||||||
|
$directives = array_map([$this, 'getDirective'], $directiveDefs);
|
||||||
|
|
||||||
|
// If specified directives were not explicitly declared, add them.
|
||||||
|
$skip = array_reduce($directives, function($hasSkip, $directive) {
|
||||||
|
return $hasSkip || $directive->name == 'skip';
|
||||||
|
});
|
||||||
|
if (!$skip) {
|
||||||
|
$directives[] = Directive::skipDirective();
|
||||||
|
}
|
||||||
|
|
||||||
|
$include = array_reduce($directives, function($hasInclude, $directive) {
|
||||||
|
return $hasInclude || $directive->name == 'include';
|
||||||
|
});
|
||||||
|
if (!$include) {
|
||||||
|
$directives[] = Directive::includeDirective();
|
||||||
|
}
|
||||||
|
|
||||||
|
$deprecated = array_reduce($directives, function($hasDeprecated, $directive) {
|
||||||
|
return $hasDeprecated || $directive->name == 'deprecated';
|
||||||
|
});
|
||||||
|
if (!$deprecated) {
|
||||||
|
$directives[] = Directive::deprecatedDirective();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Schema([
|
||||||
|
'query' => $this->getObjectType($this->nodeMap[$queryTypeName]),
|
||||||
|
'mutation' => $mutationTypeName ?
|
||||||
|
$this->getObjectType($this->nodeMap[$mutationTypeName]) :
|
||||||
|
null,
|
||||||
|
'subscription' => $subscriptionTypeName ?
|
||||||
|
$this->getObjectType($this->nodeMap[$subscriptionTypeName]) :
|
||||||
|
null,
|
||||||
|
'types' => $types,
|
||||||
|
'directives' => $directives,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDirective(DirectiveDefinitionNode $directiveNode)
|
||||||
|
{
|
||||||
|
return new Directive([
|
||||||
|
'name' => $directiveNode->name->value,
|
||||||
|
'description' => $this->getDescription($directiveNode),
|
||||||
|
'locations' => array_map(function($node) {
|
||||||
|
return $node->value;
|
||||||
|
}, $directiveNode->locations),
|
||||||
|
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getObjectType(TypeDefinitionNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->typeDefNamed($typeNode->name->value);
|
||||||
|
Utils::invariant(
|
||||||
|
$type instanceof ObjectType,
|
||||||
|
'AST must provide object type.'
|
||||||
|
);
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function produceType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$typeName = $this->getNamedTypeNode($typeNode)->name->value;
|
||||||
|
$typeDef = $this->typeDefNamed($typeName);
|
||||||
|
return $this->buildWrappedType($typeDef, $typeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function produceInputType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->produceType($typeNode);
|
||||||
|
Utils::invariant(Type::isInputType($type), 'Expected Input type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function produceOutputType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->produceType($typeNode);
|
||||||
|
Utils::invariant(Type::isOutputType($type), 'Expected Input type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function produceObjectType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->produceType($typeNode);
|
||||||
|
Utils::invariant($type instanceof ObjectType, 'Expected Object type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function produceInterfaceType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->produceType($typeNode);
|
||||||
|
Utils::invariant($type instanceof InterfaceType, 'Expected Input type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function typeDefNamed($typeName)
|
||||||
|
{
|
||||||
|
if (isset($this->innerTypeMap[$typeName])) {
|
||||||
|
return $this->innerTypeMap[$typeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->nodeMap[$typeName])) {
|
||||||
|
throw new Error('Type "' . $typeName . '" not found in document.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$innerTypeDef = $this->makeSchemaDef($this->nodeMap[$typeName]);
|
||||||
|
if (!$innerTypeDef) {
|
||||||
|
throw new Error("Nothing constructed for $typeName.");
|
||||||
|
}
|
||||||
|
$this->innerTypeMap[$typeName] = $innerTypeDef;
|
||||||
|
return $innerTypeDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeSchemaDef($def)
|
||||||
|
{
|
||||||
|
if (!$def) {
|
||||||
|
throw new Error('def must be defined.');
|
||||||
|
}
|
||||||
|
switch ($def->kind) {
|
||||||
|
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||||
|
return $this->makeTypeDef($def);
|
||||||
|
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||||
|
return $this->makeInterfaceDef($def);
|
||||||
|
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||||
|
return $this->makeEnumDef($def);
|
||||||
|
case NodeKind::UNION_TYPE_DEFINITION:
|
||||||
|
return $this->makeUnionDef($def);
|
||||||
|
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||||
|
return $this->makeScalarDef($def);
|
||||||
|
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||||
|
return $this->makeInputObjectDef($def);
|
||||||
|
default:
|
||||||
|
throw new Error("Type kind of {$def->kind} not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeTypeDef(ObjectTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
$typeName = $def->name->value;
|
||||||
|
return new ObjectType([
|
||||||
|
'name' => $typeName,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'fields' => function() use ($def) { return $this->makeFieldDefMap($def); },
|
||||||
|
'interfaces' => function() use ($def) { return $this->makeImplementedInterfaces($def); }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeFieldDefMap($def)
|
||||||
|
{
|
||||||
|
return Utils::keyValMap(
|
||||||
|
$def->fields,
|
||||||
|
function ($field) {
|
||||||
|
return $field->name->value;
|
||||||
|
},
|
||||||
|
function($field) {
|
||||||
|
return [
|
||||||
|
'type' => $this->produceOutputType($field->type),
|
||||||
|
'description' => $this->getDescription($field),
|
||||||
|
'args' => $this->makeInputValues($field->arguments),
|
||||||
|
'deprecationReason' => $this->getDeprecationReason($field->directives)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return isset($def->interfaces) ? array_map([$this, 'produceInterfaceType'], $def->interfaces) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeInputValues($values)
|
||||||
|
{
|
||||||
|
return Utils::keyValMap(
|
||||||
|
$values,
|
||||||
|
function ($value) {
|
||||||
|
return $value->name->value;
|
||||||
|
},
|
||||||
|
function($value) {
|
||||||
|
$type = $this->produceInputType($value->type);
|
||||||
|
$config = [
|
||||||
|
'name' => $value->name->value,
|
||||||
|
'type' => $type,
|
||||||
|
'description' => $this->getDescription($value)
|
||||||
|
];
|
||||||
|
if (isset($value->defaultValue)) {
|
||||||
|
$config['defaultValue'] = Utils\AST::valueFromAST($value->defaultValue, $type);
|
||||||
|
}
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeInterfaceDef(InterfaceTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
$typeName = $def->name->value;
|
||||||
|
return new InterfaceType([
|
||||||
|
'name' => $typeName,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'fields' => function() use ($def) { return $this->makeFieldDefMap($def); },
|
||||||
|
'resolveType' => [$this, 'cannotExecuteSchema']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeEnumDef(EnumTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new EnumType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'values' => Utils::keyValMap(
|
||||||
|
$def->values,
|
||||||
|
function($enumValue) {
|
||||||
|
return $enumValue->name->value;
|
||||||
|
},
|
||||||
|
function($enumValue) {
|
||||||
|
return [
|
||||||
|
'description' => $this->getDescription($enumValue),
|
||||||
|
'deprecationReason' => $this->getDeprecationReason($enumValue->directives)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeUnionDef(UnionTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new UnionType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'types' => array_map([$this, 'produceObjectType'], $def->types),
|
||||||
|
'resolveType' => [$this, 'cannotExecuteSchema']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeScalarDef(ScalarTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new CustomScalarType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'serialize' => function() { return false; },
|
||||||
|
// Note: validation calls the parse functions to determine if a
|
||||||
|
// literal value is correct. Returning null would cause use of custom
|
||||||
|
// scalars to always fail validation. Returning false causes them to
|
||||||
|
// always pass validation.
|
||||||
|
'parseValue' => function() { return false; },
|
||||||
|
'parseLiteral' => function() { return false; }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeInputObjectDef(InputObjectTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new InputObjectType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'fields' => function() use ($def) { return $this->makeInputValues($def->fields); }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDeprecationReason($directives)
|
||||||
|
{
|
||||||
|
$deprecatedAST = $directives ? Utils::find(
|
||||||
|
$directives,
|
||||||
|
function($directive) {
|
||||||
|
return $directive->name->value === Directive::deprecatedDirective()->name;
|
||||||
|
}
|
||||||
|
) : null;
|
||||||
|
if (!$deprecatedAST) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return Values::getArgumentValues(
|
||||||
|
Directive::deprecatedDirective(),
|
||||||
|
$deprecatedAST
|
||||||
|
)['reason'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an ast node, returns its string description based on a contiguous
|
||||||
|
* block full-line of comments preceding it.
|
||||||
|
*/
|
||||||
|
public function getDescription($node)
|
||||||
|
{
|
||||||
|
$loc = $node->loc;
|
||||||
|
if (!$loc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$comments = [];
|
||||||
|
$minSpaces = null;
|
||||||
|
$token = $loc->startToken->prev;
|
||||||
|
while (
|
||||||
|
$token &&
|
||||||
|
$token->kind === Token::COMMENT &&
|
||||||
|
$token->next && $token->prev &&
|
||||||
|
$token->line + 1 === $token->next->line &&
|
||||||
|
$token->line !== $token->prev->line
|
||||||
|
) {
|
||||||
|
$value = $token->value;
|
||||||
|
$spaces = $this->leadingSpaces($value);
|
||||||
|
if ($minSpaces === null || $spaces < $minSpaces) {
|
||||||
|
$minSpaces = $spaces;
|
||||||
|
}
|
||||||
|
$comments[] = $value;
|
||||||
|
$token = $token->prev;
|
||||||
|
}
|
||||||
|
return implode("\n", array_map(function($comment) use ($minSpaces) {
|
||||||
|
return mb_substr(str_replace("\n", '', $comment), $minSpaces);
|
||||||
|
}, array_reverse($comments)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function to build a GraphQLSchema directly from a source
|
||||||
|
* document.
|
||||||
|
*
|
||||||
|
* @param Source|string $source
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static function build($source)
|
||||||
|
{
|
||||||
|
return self::buildAST(Parser::parse($source));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the number of spaces on the starting side of a string.
|
||||||
|
private function leadingSpaces($str)
|
||||||
|
{
|
||||||
|
return strlen($str) - strlen(ltrim($str));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cannotExecuteSchema() {
|
||||||
|
throw new Error(
|
||||||
|
'Generated Schema cannot use Interface or Union types for execution.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
304
src/Utils/SchemaPrinter.php
Normal file
304
src/Utils/SchemaPrinter.php
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Language\Printer;
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\ScalarType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Type\Definition\Directive;
|
||||||
|
use GraphQL\Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SchemaPrinter
|
||||||
|
* @package GraphQL\Utils
|
||||||
|
*/
|
||||||
|
class SchemaPrinter
|
||||||
|
{
|
||||||
|
public static function doPrint(Schema $schema)
|
||||||
|
{
|
||||||
|
return self::printFilteredSchema($schema, function($n) {
|
||||||
|
return !self::isSpecDirective($n);
|
||||||
|
}, 'self::isDefinedType');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function printIntrosepctionSchema(Schema $schema)
|
||||||
|
{
|
||||||
|
return self::printFilteredSchema($schema, [__CLASS__, 'isSpecDirective'], [__CLASS__, 'isIntrospectionType']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isSpecDirective($directiveName)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
$directiveName === 'skip' ||
|
||||||
|
$directiveName === 'include' ||
|
||||||
|
$directiveName === 'deprecated'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isDefinedType($typename)
|
||||||
|
{
|
||||||
|
return !self::isIntrospectionType($typename) && !self::isBuiltInScalar($typename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isIntrospectionType($typename)
|
||||||
|
{
|
||||||
|
return strpos($typename, '__') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isBuiltInScalar($typename)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
$typename === 'String' ||
|
||||||
|
$typename === 'Boolean' ||
|
||||||
|
$typename === 'Int' ||
|
||||||
|
$typename === 'Float' ||
|
||||||
|
$typename === 'ID'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printFilteredSchema(Schema $schema, $directiveFilter, $typeFilter)
|
||||||
|
{
|
||||||
|
$directives = array_filter($schema->getDirectives(), function($directive) use ($directiveFilter) {
|
||||||
|
return $directiveFilter($directive->name);
|
||||||
|
});
|
||||||
|
$typeMap = $schema->getTypeMap();
|
||||||
|
$types = array_filter(array_keys($typeMap), $typeFilter);
|
||||||
|
sort($types);
|
||||||
|
$types = array_map(function($typeName) use ($typeMap) { return $typeMap[$typeName]; }, $types);
|
||||||
|
|
||||||
|
return implode("\n\n", array_filter(array_merge(
|
||||||
|
[self::printSchemaDefinition($schema)],
|
||||||
|
array_map('self::printDirective', $directives),
|
||||||
|
array_map('self::printType', $types)
|
||||||
|
))) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printSchemaDefinition(Schema $schema)
|
||||||
|
{
|
||||||
|
if (self::isSchemaOfCommonNames($schema)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$operationTypes = [];
|
||||||
|
|
||||||
|
$queryType = $schema->getQueryType();
|
||||||
|
if ($queryType) {
|
||||||
|
$operationTypes[] = " query: {$queryType->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$mutationType = $schema->getMutationType();
|
||||||
|
if ($mutationType) {
|
||||||
|
$operationTypes[] = " mutation: {$mutationType->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$subscriptionType = $schema->getSubscriptionType();
|
||||||
|
if ($subscriptionType) {
|
||||||
|
$operationTypes[] = " subscription: {$subscriptionType->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "schema {\n" . implode("\n", $operationTypes) . "\n}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GraphQL schema define root types for each type of operation. These types are
|
||||||
|
* the same as any other type and can be named in any manner, however there is
|
||||||
|
* a common naming convention:
|
||||||
|
*
|
||||||
|
* schema {
|
||||||
|
* query: Query
|
||||||
|
* mutation: Mutation
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* When using this naming convention, the schema description can be omitted.
|
||||||
|
*/
|
||||||
|
private static function isSchemaOfCommonNames(Schema $schema)
|
||||||
|
{
|
||||||
|
$queryType = $schema->getQueryType();
|
||||||
|
if ($queryType && $queryType->name !== 'Query') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mutationType = $schema->getMutationType();
|
||||||
|
if ($mutationType && $mutationType->name !== 'Mutation') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subscriptionType = $schema->getSubscriptionType();
|
||||||
|
if ($subscriptionType && $subscriptionType->name !== 'Subscription') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function printType(Type $type)
|
||||||
|
{
|
||||||
|
if ($type instanceof ScalarType) {
|
||||||
|
return self::printScalar($type);
|
||||||
|
} else if ($type instanceof ObjectType) {
|
||||||
|
return self::printObject($type);
|
||||||
|
} else if ($type instanceof InterfaceType) {
|
||||||
|
return self::printInterface($type);
|
||||||
|
} else if ($type instanceof UnionType) {
|
||||||
|
return self::printUnion($type);
|
||||||
|
} else if ($type instanceof EnumType) {
|
||||||
|
return self::printEnum($type);
|
||||||
|
}
|
||||||
|
Utils::invariant($type instanceof InputObjectType);
|
||||||
|
return self::printInputObject($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printScalar(ScalarType $type)
|
||||||
|
{
|
||||||
|
return self::printDescription($type) . "scalar {$type->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printObject(ObjectType $type)
|
||||||
|
{
|
||||||
|
$interfaces = $type->getInterfaces();
|
||||||
|
$implementedInterfaces = !empty($interfaces) ?
|
||||||
|
' implements ' . implode(', ', array_map(function($i) {
|
||||||
|
return $i->name;
|
||||||
|
}, $interfaces)) : '';
|
||||||
|
return self::printDescription($type) .
|
||||||
|
"type {$type->name}$implementedInterfaces {\n" .
|
||||||
|
self::printFields($type) . "\n" .
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printInterface(InterfaceType $type)
|
||||||
|
{
|
||||||
|
return self::printDescription($type) .
|
||||||
|
"interface {$type->name} {\n" .
|
||||||
|
self::printFields($type) . "\n" .
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printUnion(UnionType $type)
|
||||||
|
{
|
||||||
|
return self::printDescription($type) .
|
||||||
|
"union {$type->name} = " . implode(" | ", $type->getTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printEnum(EnumType $type)
|
||||||
|
{
|
||||||
|
return self::printDescription($type) .
|
||||||
|
"enum {$type->name} {\n" .
|
||||||
|
self::printEnumValues($type->getValues()) . "\n" .
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printEnumValues($values)
|
||||||
|
{
|
||||||
|
return implode("\n", array_map(function($value, $i) {
|
||||||
|
return self::printDescription($value, ' ', !$i) . ' ' .
|
||||||
|
$value->name . self::printDeprecated($value);
|
||||||
|
}, $values, array_keys($values)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printInputObject(InputObjectType $type)
|
||||||
|
{
|
||||||
|
$fields = array_values($type->getFields());
|
||||||
|
return self::printDescription($type) .
|
||||||
|
"input {$type->name} {\n" .
|
||||||
|
implode("\n", array_map(function($f, $i) {
|
||||||
|
return self::printDescription($f, ' ', !$i) . ' ' . self::printInputValue($f);
|
||||||
|
}, $fields, array_keys($fields))) . "\n" .
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printFields($type)
|
||||||
|
{
|
||||||
|
$fields = array_values($type->getFields());
|
||||||
|
return implode("\n", array_map(function($f, $i) {
|
||||||
|
return self::printDescription($f, ' ', !$i) . ' ' .
|
||||||
|
$f->name . self::printArgs($f->args, ' ') . ': ' .
|
||||||
|
(string) $f->getType() . self::printDeprecated($f);
|
||||||
|
}, $fields, array_keys($fields)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printArgs($args, $indentation = '')
|
||||||
|
{
|
||||||
|
if (count($args) === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If every arg does not have a description, print them on one line.
|
||||||
|
if (Utils::every($args, function($arg) { return empty($arg->description); })) {
|
||||||
|
return '(' . implode(', ', array_map('self::printInputValue', $args)) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "(\n" . implode("\n", array_map(function($arg, $i) use ($indentation) {
|
||||||
|
return self::printDescription($arg, ' ' . $indentation, !$i) . ' ' . $indentation .
|
||||||
|
self::printInputValue($arg);
|
||||||
|
}, $args, array_keys($args))) . "\n" . $indentation . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printInputValue($arg)
|
||||||
|
{
|
||||||
|
$argDecl = $arg->name . ': ' . (string) $arg->getType();
|
||||||
|
if ($arg->defaultValueExists()) {
|
||||||
|
$argDecl .= ' = ' . Printer::doPrint(AST::astFromValue($arg->defaultValue, $arg->getType()));
|
||||||
|
}
|
||||||
|
return $argDecl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printDirective($directive)
|
||||||
|
{
|
||||||
|
return self::printDescription($directive) .
|
||||||
|
'directive @' . $directive->name . self::printArgs($directive->args) .
|
||||||
|
' on ' . implode(' | ', $directive->locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printDeprecated($fieldOrEnumVal)
|
||||||
|
{
|
||||||
|
$reason = $fieldOrEnumVal->deprecationReason;
|
||||||
|
if (empty($reason)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($reason === '' || $reason === Directive::DEFAULT_DEPRECATION_REASON) {
|
||||||
|
return ' @deprecated';
|
||||||
|
}
|
||||||
|
return ' @deprecated(reason: ' .
|
||||||
|
Printer::doPrint(AST::astFromValue($reason, Type::string())) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function printDescription($def, $indentation = '', $firstInBlock = true)
|
||||||
|
{
|
||||||
|
if (!$def->description) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$lines = explode("\n", $def->description);
|
||||||
|
$description = $indentation && !$firstInBlock ? "\n" : '';
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if ($line === '') {
|
||||||
|
$description .= $indentation . "#\n";
|
||||||
|
} else {
|
||||||
|
// For > 120 character long lines, cut at space boundaries into sublines
|
||||||
|
// of ~80 chars.
|
||||||
|
$sublines = self::breakLine($line, 120 - strlen($indentation));
|
||||||
|
foreach ($sublines as $subline) {
|
||||||
|
$description .= $indentation . '# ' . $subline . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function breakLine($line, $len)
|
||||||
|
{
|
||||||
|
if (strlen($line) < $len + 5) {
|
||||||
|
return [$line];
|
||||||
|
}
|
||||||
|
preg_match_all("/((?: |^).{15," . ($len - 40) . "}(?= |$))/", $line, $parts);
|
||||||
|
$parts = $parts[0];
|
||||||
|
return array_map(function($part) {
|
||||||
|
return trim($part);
|
||||||
|
}, $parts);
|
||||||
|
}
|
||||||
|
}
|
@ -1514,7 +1514,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
|||||||
'description' => 'A GraphQL Schema defines the capabilities of a ' .
|
'description' => 'A GraphQL Schema defines the capabilities of a ' .
|
||||||
'GraphQL server. It exposes all available types and ' .
|
'GraphQL server. It exposes all available types and ' .
|
||||||
'directives on the server, as well as the entry ' .
|
'directives on the server, as well as the entry ' .
|
||||||
'points for query and mutation operations.',
|
'points for query, mutation, and subscription operations.',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
[
|
[
|
||||||
'name' => 'types',
|
'name' => 'types',
|
||||||
@ -1571,7 +1571,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
|||||||
'data' => [
|
'data' => [
|
||||||
'typeKindType' => [
|
'typeKindType' => [
|
||||||
'name' => '__TypeKind',
|
'name' => '__TypeKind',
|
||||||
'description' => 'An enum describing what kind of type a given __Type is.',
|
'description' => 'An enum describing what kind of type a given `__Type` is.',
|
||||||
'enumValues' => [
|
'enumValues' => [
|
||||||
[
|
[
|
||||||
'description' => 'Indicates this type is a scalar.',
|
'description' => 'Indicates this type is a scalar.',
|
||||||
|
899
tests/Utils/BuildSchemaTest.php
Normal file
899
tests/Utils/BuildSchemaTest.php
Normal file
@ -0,0 +1,899 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Utils;
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Utils\BuildSchema;
|
||||||
|
use GraphQL\Utils\SchemaPrinter;
|
||||||
|
|
||||||
|
use GraphQL\Type\Definition\Directive;
|
||||||
|
use GraphQL\Type\Definition\EnumValueDefinition;
|
||||||
|
|
||||||
|
class BuildSchemaTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
// Describe: Schema Builder
|
||||||
|
|
||||||
|
private function cycleOutput($body)
|
||||||
|
{
|
||||||
|
$ast = Parser::parse($body);
|
||||||
|
$schema = BuildSchema::buildAST($ast);
|
||||||
|
return "\n" . SchemaPrinter::doPrint($schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it can use built schema for limited execution
|
||||||
|
*/
|
||||||
|
public function testUseBuiltSchemaForLimitedExecution()
|
||||||
|
{
|
||||||
|
$schema = BuildSchema::buildAST(Parser::parse('
|
||||||
|
schema { query: Query }
|
||||||
|
type Query {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
'));
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, '{ str }', ['str' => 123]);
|
||||||
|
$this->assertEquals($result['data'], ['str' => 123]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it can build a schema directly from the source
|
||||||
|
*/
|
||||||
|
public function testBuildSchemaDirectlyFromSource()
|
||||||
|
{
|
||||||
|
$schema = BuildSchema::build("
|
||||||
|
schema { query: Query }
|
||||||
|
type Query {
|
||||||
|
add(x: Int, y: Int): Int
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
$result = GraphQL::execute(
|
||||||
|
$schema,
|
||||||
|
'{ add(x: 34, y: 55) }',
|
||||||
|
[
|
||||||
|
'add' => function ($root, $args) {
|
||||||
|
return $args['x'] + $args['y'];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->assertEquals($result, ['data' => ['add' => 89]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple Type
|
||||||
|
*/
|
||||||
|
public function testSimpleType()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: HelloScalars
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelloScalars {
|
||||||
|
str: String
|
||||||
|
int: Int
|
||||||
|
float: Float
|
||||||
|
id: ID
|
||||||
|
bool: Boolean
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it With directives
|
||||||
|
*/
|
||||||
|
public function testWithDirectives()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
directive @foo(arg: Int) on FIELD
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Supports descriptions
|
||||||
|
*/
|
||||||
|
public function testSupportsDescriptions()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
# This is a directive
|
||||||
|
directive @foo(
|
||||||
|
# It has an argument
|
||||||
|
arg: Int
|
||||||
|
) on FIELD
|
||||||
|
|
||||||
|
# With an enum
|
||||||
|
enum Color {
|
||||||
|
RED
|
||||||
|
|
||||||
|
# Not a creative color
|
||||||
|
GREEN
|
||||||
|
BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
# What a great type
|
||||||
|
type Hello {
|
||||||
|
# And a field to boot
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Maintains @skip & @include
|
||||||
|
*/
|
||||||
|
public function testMaintainsSkipAndInclude()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$schema = BuildSchema::buildAST(Parser::parse($body));
|
||||||
|
$this->assertEquals(count($schema->getDirectives()), 3);
|
||||||
|
$this->assertEquals($schema->getDirective('skip'), Directive::skipDirective());
|
||||||
|
$this->assertEquals($schema->getDirective('include'), Directive::includeDirective());
|
||||||
|
$this->assertEquals($schema->getDirective('deprecated'), Directive::deprecatedDirective());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Overriding directives excludes specified
|
||||||
|
*/
|
||||||
|
public function testOverridingDirectivesExcludesSpecified()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
directive @skip on FIELD
|
||||||
|
directive @include on FIELD
|
||||||
|
directive @deprecated on FIELD_DEFINITION
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$schema = BuildSchema::buildAST(Parser::parse($body));
|
||||||
|
$this->assertEquals(count($schema->getDirectives()), 3);
|
||||||
|
$this->assertNotEquals($schema->getDirective('skip'), Directive::skipDirective());
|
||||||
|
$this->assertNotEquals($schema->getDirective('include'), Directive::includeDirective());
|
||||||
|
$this->assertNotEquals($schema->getDirective('deprecated'), Directive::deprecatedDirective());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Type modifiers
|
||||||
|
*/
|
||||||
|
public function testTypeModifiers()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: HelloScalars
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelloScalars {
|
||||||
|
nonNullStr: String!
|
||||||
|
listOfStrs: [String]
|
||||||
|
listOfNonNullStrs: [String!]
|
||||||
|
nonNullListOfStrs: [String]!
|
||||||
|
nonNullListOfNonNullStrs: [String!]!
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Recursive type
|
||||||
|
*/
|
||||||
|
public function testRecursiveType()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Recurse
|
||||||
|
}
|
||||||
|
|
||||||
|
type Recurse {
|
||||||
|
str: String
|
||||||
|
recurse: Recurse
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Two types circular
|
||||||
|
*/
|
||||||
|
public function testTwoTypesCircular()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: TypeOne
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeOne {
|
||||||
|
str: String
|
||||||
|
typeTwo: TypeTwo
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeTwo {
|
||||||
|
str: String
|
||||||
|
typeOne: TypeOne
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Single argument field
|
||||||
|
*/
|
||||||
|
public function testSingleArgumentField()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str(int: Int): String
|
||||||
|
floatToStr(float: Float): String
|
||||||
|
idToStr(id: ID): String
|
||||||
|
booleanToStr(bool: Boolean): String
|
||||||
|
strToStr(bool: String): String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple type with multiple arguments
|
||||||
|
*/
|
||||||
|
public function testSimpleTypeWithMultipleArguments()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str(int: Int, bool: Boolean): String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple type with interface
|
||||||
|
*/
|
||||||
|
public function testSimpleTypeWithInterface()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello implements WorldInterface {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorldInterface {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple output enum
|
||||||
|
*/
|
||||||
|
public function testSimpleOutputEnum()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: OutputEnumRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Hello {
|
||||||
|
WORLD
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputEnumRoot {
|
||||||
|
hello: Hello
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Multiple value enum
|
||||||
|
*/
|
||||||
|
public function testMultipleValueEnum()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: OutputEnumRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Hello {
|
||||||
|
WO
|
||||||
|
RLD
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputEnumRoot {
|
||||||
|
hello: Hello
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple Union
|
||||||
|
*/
|
||||||
|
public function testSimpleUnion()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
union Hello = World
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
hello: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type World {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Multiple Union
|
||||||
|
*/
|
||||||
|
public function testMultipleUnion()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
union Hello = WorldOne | WorldTwo
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
hello: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorldOne {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorldTwo {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it CustomScalar
|
||||||
|
*/
|
||||||
|
public function testCustomScalar()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar CustomScalar
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
customScalar: CustomScalar
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it CustomScalar
|
||||||
|
*/
|
||||||
|
public function testInputObject()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
input Input {
|
||||||
|
int: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
field(in: Input): String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple argument field with default
|
||||||
|
*/
|
||||||
|
public function testSimpleArgumentFieldWithDefault()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str(int: Int = 2): String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple type with mutation
|
||||||
|
*/
|
||||||
|
public function testSimpleTypeWithMutation()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: HelloScalars
|
||||||
|
mutation: Mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelloScalars {
|
||||||
|
str: String
|
||||||
|
int: Int
|
||||||
|
bool: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
addHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple type with subscription
|
||||||
|
*/
|
||||||
|
public function testSimpleTypeWithSubscription()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: HelloScalars
|
||||||
|
subscription: Subscription
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelloScalars {
|
||||||
|
str: String
|
||||||
|
int: Int
|
||||||
|
bool: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription {
|
||||||
|
subscribeHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unreferenced type implementing referenced interface
|
||||||
|
*/
|
||||||
|
public function testUnreferencedTypeImplementingReferencedInterface()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Concrete implements Iface {
|
||||||
|
key: String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Iface {
|
||||||
|
key: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
iface: Iface
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unreferenced type implementing referenced union
|
||||||
|
*/
|
||||||
|
public function testUnreferencedTypeImplementingReferencedUnion()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Concrete {
|
||||||
|
key: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
union: Union
|
||||||
|
}
|
||||||
|
|
||||||
|
union Union = Concrete
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Supports @deprecated
|
||||||
|
*/
|
||||||
|
public function testSupportsDeprecated()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
enum MyEnum {
|
||||||
|
VALUE
|
||||||
|
OLD_VALUE @deprecated
|
||||||
|
OTHER_VALUE @deprecated(reason: "Terrible reasons")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
field1: String @deprecated
|
||||||
|
field2: Int @deprecated(reason: "Because I said so")
|
||||||
|
enum: MyEnum
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$output = $this->cycleOutput($body);
|
||||||
|
$this->assertEquals($output, $body);
|
||||||
|
|
||||||
|
$ast = Parser::parse($body);
|
||||||
|
$schema = BuildSchema::buildAST($ast);
|
||||||
|
|
||||||
|
$this->assertEquals($schema->getType('MyEnum')->getValues(), [
|
||||||
|
new EnumValueDefinition([
|
||||||
|
'name' => 'VALUE',
|
||||||
|
'description' => '',
|
||||||
|
'deprecationReason' => null,
|
||||||
|
'value' => 'VALUE'
|
||||||
|
]),
|
||||||
|
new EnumValueDefinition([
|
||||||
|
'name' => 'OLD_VALUE',
|
||||||
|
'description' => '',
|
||||||
|
'deprecationReason' => 'No longer supported',
|
||||||
|
'value' => 'OLD_VALUE'
|
||||||
|
]),
|
||||||
|
new EnumValueDefinition([
|
||||||
|
'name' => 'OTHER_VALUE',
|
||||||
|
'description' => '',
|
||||||
|
'deprecationReason' => 'Terrible reasons',
|
||||||
|
'value' => 'OTHER_VALUE'
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rootFields = $schema->getType('Query')->getFields();
|
||||||
|
$this->assertEquals($rootFields['field1']->isDeprecated(), true);
|
||||||
|
$this->assertEquals($rootFields['field1']->deprecationReason, 'No longer supported');
|
||||||
|
|
||||||
|
$this->assertEquals($rootFields['field2']->isDeprecated(), true);
|
||||||
|
$this->assertEquals($rootFields['field2']->deprecationReason, 'Because I said so');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe: Failures
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Requires a schema definition or Query type
|
||||||
|
*/
|
||||||
|
public function testRequiresSchemaDefinitionOrQueryType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide schema definition with query type or a type named Query.');
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Allows only a single schema definition
|
||||||
|
*/
|
||||||
|
public function testAllowsOnlySingleSchemaDefinition()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one schema definition.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Requires a query type
|
||||||
|
*/
|
||||||
|
public function testRequiresQueryType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide schema definition with query type or a type named Query.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
mutation: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Allows only a single query type
|
||||||
|
*/
|
||||||
|
public function testAllowsOnlySingleQueryType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one query type in schema.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
query: Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
|
||||||
|
type Yellow {
|
||||||
|
isColor: Boolean
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Allows only a single mutation type
|
||||||
|
*/
|
||||||
|
public function testAllowsOnlySingleMutationType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one mutation type in schema.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
mutation: Hello
|
||||||
|
mutation: Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
|
||||||
|
type Yellow {
|
||||||
|
isColor: Boolean
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Allows only a single subscription type
|
||||||
|
*/
|
||||||
|
public function testAllowsOnlySingleSubscriptionType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Must provide only one subscription type in schema.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
subscription: Hello
|
||||||
|
subscription: Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
|
||||||
|
type Yellow {
|
||||||
|
isColor: Boolean
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unknown type referenced
|
||||||
|
*/
|
||||||
|
public function testUnknownTypeReferenced()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Bar" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unknown type in interface list
|
||||||
|
*/
|
||||||
|
public function testUnknownTypeInInterfaceList()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Bar" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello implements Bar { }
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unknown type in union list
|
||||||
|
*/
|
||||||
|
public function testUnknownTypeInUnionList()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Bar" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
}
|
||||||
|
|
||||||
|
union TestUnion = Bar
|
||||||
|
type Hello { testUnion: TestUnion }
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unknown query type
|
||||||
|
*/
|
||||||
|
public function testUnknownQueryType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified query type "Wat" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Wat
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unknown mutation type
|
||||||
|
*/
|
||||||
|
public function testUnknownMutationType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified mutation type "Wat" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
mutation: Wat
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Unknown subscription type
|
||||||
|
*/
|
||||||
|
public function testUnknownSubscriptionType()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified subscription type "Awesome" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Hello
|
||||||
|
mutation: Wat
|
||||||
|
subscription: Awesome
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Wat {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Does not consider operation names
|
||||||
|
*/
|
||||||
|
public function testDoesNotConsiderOperationNames()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified query type "Foo" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Foo
|
||||||
|
}
|
||||||
|
|
||||||
|
query Foo { field }
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Does not consider fragment names
|
||||||
|
*/
|
||||||
|
public function testDoesNotConsiderFragmentNames()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('GraphQL\Error\Error', 'Specified query type "Foo" not found in document.');
|
||||||
|
$body = '
|
||||||
|
schema {
|
||||||
|
query: Foo
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment Foo on Type { field }
|
||||||
|
';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
BuildSchema::buildAST($doc);
|
||||||
|
}
|
||||||
|
}
|
850
tests/Utils/SchemaPrinterTest.php
Normal file
850
tests/Utils/SchemaPrinterTest.php
Normal file
@ -0,0 +1,850 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Utils;
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\CustomScalarType;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Utils\SchemaPrinter;
|
||||||
|
|
||||||
|
class SchemaPrinterTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
// Describe: Type System Printer
|
||||||
|
|
||||||
|
private function printForTest($schema)
|
||||||
|
{
|
||||||
|
return "\n" . SchemaPrinter::doPrint($schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printSingleFieldSchema($fieldConfig)
|
||||||
|
{
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => [
|
||||||
|
'singleField' => $fieldConfig
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
return $this->printForTest(new Schema(['query' => $root]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field
|
||||||
|
*/
|
||||||
|
public function testPrintsStringField()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string()
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField: String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints [String] Field
|
||||||
|
*/
|
||||||
|
public function testPrintArrayStringField()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::listOf(Type::string())
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField: [String]
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String! Field
|
||||||
|
*/
|
||||||
|
public function testPrintNonNullStringField()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::nonNull(Type::string())
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField: String!
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints [String]! Field
|
||||||
|
*/
|
||||||
|
public function testPrintNonNullArrayStringField()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::nonNull(Type::listOf(Type::string()))
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField: [String]!
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints [String!] Field
|
||||||
|
*/
|
||||||
|
public function testPrintArrayNonNullStringField()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::listOf(Type::nonNull(Type::string()))
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField: [String!]
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints [String!]! Field
|
||||||
|
*/
|
||||||
|
public function testPrintNonNullArrayNonNullStringField()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField: [String!]!
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints Object Field
|
||||||
|
*/
|
||||||
|
public function testPrintObjectField()
|
||||||
|
{
|
||||||
|
$fooType = new ObjectType([
|
||||||
|
'name' => 'Foo',
|
||||||
|
'fields' => ['str' => ['type' => Type::string()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => ['foo' => ['type' => $fooType]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $root]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
foo: Foo
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Int Arg
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithIntArg()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => ['argOne' => ['type' => Type::int()]]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Int Arg With Default
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithIntArgWithDefault()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int = 2): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Int Arg With Default Null
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithIntArgWithDefaultNull()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int = null): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Int! Arg
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithNonNullIntArg()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int!): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Multiple Args
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithMultipleArgs()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [
|
||||||
|
'argOne' => ['type' => Type::int()],
|
||||||
|
'argTwo' => ['type' => Type::string()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int, argTwo: String): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Multiple Args, First is Default
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithMultipleArgsFirstIsDefault()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [
|
||||||
|
'argOne' => ['type' => Type::int(), 'defaultValue' => 1],
|
||||||
|
'argTwo' => ['type' => Type::string()],
|
||||||
|
'argThree' => ['type' => Type::boolean()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Multiple Args, Second is Default
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithMultipleArgsSecondIsDefault()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [
|
||||||
|
'argOne' => ['type' => Type::int()],
|
||||||
|
'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'],
|
||||||
|
'argThree' => ['type' => Type::boolean()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Prints String Field With Multiple Args, Last is Default
|
||||||
|
*/
|
||||||
|
public function testPrintsStringFieldWithMultipleArgsLastIsDefault()
|
||||||
|
{
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [
|
||||||
|
'argOne' => ['type' => Type::int()],
|
||||||
|
'argTwo' => ['type' => Type::string()],
|
||||||
|
'argThree' => ['type' => Type::boolean(), 'defaultValue' => false]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Print Interface
|
||||||
|
*/
|
||||||
|
public function testPrintInterface()
|
||||||
|
{
|
||||||
|
$fooType = new InterfaceType([
|
||||||
|
'name' => 'Foo',
|
||||||
|
'resolveType' => function() { return null; },
|
||||||
|
'fields' => ['str' => ['type' => Type::string()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$barType = new ObjectType([
|
||||||
|
'name' => 'Bar',
|
||||||
|
'fields' => ['str' => ['type' => Type::string()]],
|
||||||
|
'interfaces' => [$fooType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => ['bar' => ['type' => $barType]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => $root,
|
||||||
|
'types' => [$barType]
|
||||||
|
]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar implements Foo {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Foo {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Print Multiple Interface
|
||||||
|
*/
|
||||||
|
public function testPrintMultipleInterface()
|
||||||
|
{
|
||||||
|
$fooType = new InterfaceType([
|
||||||
|
'name' => 'Foo',
|
||||||
|
'resolveType' => function() { return null; },
|
||||||
|
'fields' => ['str' => ['type' => Type::string()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$baazType = new InterfaceType([
|
||||||
|
'name' => 'Baaz',
|
||||||
|
'resolveType' => function() { return null; },
|
||||||
|
'fields' => ['int' => ['type' => Type::int()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$barType = new ObjectType([
|
||||||
|
'name' => 'Bar',
|
||||||
|
'fields' => [
|
||||||
|
'str' => ['type' => Type::string()],
|
||||||
|
'int' => ['type' => Type::int()]
|
||||||
|
],
|
||||||
|
'interfaces' => [$fooType, $baazType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => ['bar' => ['type' => $barType]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => $root,
|
||||||
|
'types' => [$barType]
|
||||||
|
]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Baaz {
|
||||||
|
int: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar implements Foo, Baaz {
|
||||||
|
str: String
|
||||||
|
int: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Foo {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
bar: Bar
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Print Unions
|
||||||
|
*/
|
||||||
|
public function testPrintUnions()
|
||||||
|
{
|
||||||
|
$fooType = new ObjectType([
|
||||||
|
'name' => 'Foo',
|
||||||
|
'fields' => ['bool' => ['type' => Type::boolean()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$barType = new ObjectType([
|
||||||
|
'name' => 'Bar',
|
||||||
|
'fields' => ['str' => ['type' => Type::string()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$singleUnion = new UnionType([
|
||||||
|
'name' => 'SingleUnion',
|
||||||
|
'resolveType' => function() { return null; },
|
||||||
|
'types' => [$fooType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$multipleUnion = new UnionType([
|
||||||
|
'name' => 'MultipleUnion',
|
||||||
|
'resolveType' => function() { return null; },
|
||||||
|
'types' => [$fooType, $barType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => [
|
||||||
|
'single' => ['type' => $singleUnion],
|
||||||
|
'multiple' => ['type' => $multipleUnion]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $root]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar {
|
||||||
|
str: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo {
|
||||||
|
bool: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
union MultipleUnion = Foo | Bar
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
single: SingleUnion
|
||||||
|
multiple: MultipleUnion
|
||||||
|
}
|
||||||
|
|
||||||
|
union SingleUnion = Foo
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Print Input Type
|
||||||
|
*/
|
||||||
|
public function testInputType()
|
||||||
|
{
|
||||||
|
$inputType = new InputObjectType([
|
||||||
|
'name' => 'InputType',
|
||||||
|
'fields' => ['int' => ['type' => Type::int()]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => [
|
||||||
|
'str' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => ['argOne' => ['type' => $inputType]]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $root]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
input InputType {
|
||||||
|
int: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
str(argOne: InputType): String
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Custom Scalar
|
||||||
|
*/
|
||||||
|
public function testCustomScalar()
|
||||||
|
{
|
||||||
|
$oddType = new CustomScalarType([
|
||||||
|
'name' => 'Odd',
|
||||||
|
'serialize' => function($value) {
|
||||||
|
return $value % 2 === 1 ? $value : null;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => [
|
||||||
|
'odd' => ['type' => $oddType]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $root]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar Odd
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
odd: Odd
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Enum
|
||||||
|
*/
|
||||||
|
public function testEnum()
|
||||||
|
{
|
||||||
|
$RGBType = new EnumType([
|
||||||
|
'name' => 'RGB',
|
||||||
|
'values' => [
|
||||||
|
'RED' => ['value' => 0],
|
||||||
|
'GREEN' => ['value' => 1],
|
||||||
|
'BLUE' => ['value' => 2]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => [
|
||||||
|
'rgb' => ['type' => $RGBType]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $root]);
|
||||||
|
$output = $this->printForTest($schema);
|
||||||
|
$this->assertEquals($output, '
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RGB {
|
||||||
|
RED
|
||||||
|
GREEN
|
||||||
|
BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
type Root {
|
||||||
|
rgb: RGB
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Print Introspection Schema
|
||||||
|
*/
|
||||||
|
public function testPrintIntrospectionSchema()
|
||||||
|
{
|
||||||
|
$root = new ObjectType([
|
||||||
|
'name' => 'Root',
|
||||||
|
'fields' => [
|
||||||
|
'onlyField' => ['type' => Type::string()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $root]);
|
||||||
|
$output = SchemaPrinter::printIntrosepctionSchema($schema);
|
||||||
|
$introspectionSchema = <<<'EOT'
|
||||||
|
schema {
|
||||||
|
query: Root
|
||||||
|
}
|
||||||
|
|
||||||
|
# Directs the executor to include this field or fragment only when the `if` argument is true.
|
||||||
|
directive @include(
|
||||||
|
# Included when true.
|
||||||
|
if: Boolean!
|
||||||
|
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||||
|
|
||||||
|
# Directs the executor to skip this field or fragment when the `if` argument is true.
|
||||||
|
directive @skip(
|
||||||
|
# Skipped when true.
|
||||||
|
if: Boolean!
|
||||||
|
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||||
|
|
||||||
|
# Marks an element of a GraphQL schema as no longer supported.
|
||||||
|
directive @deprecated(
|
||||||
|
# Explains why this element was deprecated, usually also including a suggestion
|
||||||
|
# for how to access supported similar data. Formatted in
|
||||||
|
# [Markdown](https://daringfireball.net/projects/markdown/).
|
||||||
|
reason: String = "No longer supported"
|
||||||
|
) on FIELD_DEFINITION | ENUM_VALUE
|
||||||
|
|
||||||
|
# A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
|
||||||
|
#
|
||||||
|
# In some cases, you need to provide options to alter GraphQL's execution behavior
|
||||||
|
# in ways field arguments will not suffice, such as conditionally including or
|
||||||
|
# skipping a field. Directives provide this by describing additional information
|
||||||
|
# to the executor.
|
||||||
|
type __Directive {
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
locations: [__DirectiveLocation!]!
|
||||||
|
args: [__InputValue!]!
|
||||||
|
onOperation: Boolean! @deprecated(reason: "Use `locations`.")
|
||||||
|
onFragment: Boolean! @deprecated(reason: "Use `locations`.")
|
||||||
|
onField: Boolean! @deprecated(reason: "Use `locations`.")
|
||||||
|
}
|
||||||
|
|
||||||
|
# A Directive can be adjacent to many parts of the GraphQL language, a
|
||||||
|
# __DirectiveLocation describes one such possible adjacencies.
|
||||||
|
enum __DirectiveLocation {
|
||||||
|
# Location adjacent to a query operation.
|
||||||
|
QUERY
|
||||||
|
|
||||||
|
# Location adjacent to a mutation operation.
|
||||||
|
MUTATION
|
||||||
|
|
||||||
|
# Location adjacent to a subscription operation.
|
||||||
|
SUBSCRIPTION
|
||||||
|
|
||||||
|
# Location adjacent to a field.
|
||||||
|
FIELD
|
||||||
|
|
||||||
|
# Location adjacent to a fragment definition.
|
||||||
|
FRAGMENT_DEFINITION
|
||||||
|
|
||||||
|
# Location adjacent to a fragment spread.
|
||||||
|
FRAGMENT_SPREAD
|
||||||
|
|
||||||
|
# Location adjacent to an inline fragment.
|
||||||
|
INLINE_FRAGMENT
|
||||||
|
|
||||||
|
# Location adjacent to a schema definition.
|
||||||
|
SCHEMA
|
||||||
|
|
||||||
|
# Location adjacent to a scalar definition.
|
||||||
|
SCALAR
|
||||||
|
|
||||||
|
# Location adjacent to an object type definition.
|
||||||
|
OBJECT
|
||||||
|
|
||||||
|
# Location adjacent to a field definition.
|
||||||
|
FIELD_DEFINITION
|
||||||
|
|
||||||
|
# Location adjacent to an argument definition.
|
||||||
|
ARGUMENT_DEFINITION
|
||||||
|
|
||||||
|
# Location adjacent to an interface definition.
|
||||||
|
INTERFACE
|
||||||
|
|
||||||
|
# Location adjacent to a union definition.
|
||||||
|
UNION
|
||||||
|
|
||||||
|
# Location adjacent to an enum definition.
|
||||||
|
ENUM
|
||||||
|
|
||||||
|
# Location adjacent to an enum value definition.
|
||||||
|
ENUM_VALUE
|
||||||
|
|
||||||
|
# Location adjacent to an input object type definition.
|
||||||
|
INPUT_OBJECT
|
||||||
|
|
||||||
|
# Location adjacent to an input object field definition.
|
||||||
|
INPUT_FIELD_DEFINITION
|
||||||
|
}
|
||||||
|
|
||||||
|
# One possible value for a given Enum. Enum values are unique values, not a
|
||||||
|
# placeholder for a string or numeric value. However an Enum value is returned in
|
||||||
|
# a JSON response as a string.
|
||||||
|
type __EnumValue {
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
isDeprecated: Boolean!
|
||||||
|
deprecationReason: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# Object and Interface types are described by a list of Fields, each of which has
|
||||||
|
# a name, potentially a list of arguments, and a return type.
|
||||||
|
type __Field {
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
args: [__InputValue!]!
|
||||||
|
type: __Type!
|
||||||
|
isDeprecated: Boolean!
|
||||||
|
deprecationReason: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# Arguments provided to Fields or Directives and the input fields of an
|
||||||
|
# InputObject are represented as Input Values which describe their type and
|
||||||
|
# optionally a default value.
|
||||||
|
type __InputValue {
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
type: __Type!
|
||||||
|
|
||||||
|
# A GraphQL-formatted string representing the default value for this input value.
|
||||||
|
defaultValue: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
|
||||||
|
# available types and directives on the server, as well as the entry points for
|
||||||
|
# query, mutation, and subscription operations.
|
||||||
|
type __Schema {
|
||||||
|
# A list of all types supported by this server.
|
||||||
|
types: [__Type!]!
|
||||||
|
|
||||||
|
# The type that query operations will be rooted at.
|
||||||
|
queryType: __Type!
|
||||||
|
|
||||||
|
# If this server supports mutation, the type that mutation operations will be rooted at.
|
||||||
|
mutationType: __Type
|
||||||
|
|
||||||
|
# If this server support subscription, the type that subscription operations will be rooted at.
|
||||||
|
subscriptionType: __Type
|
||||||
|
|
||||||
|
# A list of all directives supported by this server.
|
||||||
|
directives: [__Directive!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
# The fundamental unit of any GraphQL Schema is the type. There are many kinds of
|
||||||
|
# types in GraphQL as represented by the `__TypeKind` enum.
|
||||||
|
#
|
||||||
|
# Depending on the kind of a type, certain fields describe information about that
|
||||||
|
# type. Scalar types provide no information beyond a name and description, while
|
||||||
|
# Enum types provide their values. Object and Interface types provide the fields
|
||||||
|
# they describe. Abstract types, Union and Interface, provide the Object types
|
||||||
|
# possible at runtime. List and NonNull types compose other types.
|
||||||
|
type __Type {
|
||||||
|
kind: __TypeKind!
|
||||||
|
name: String
|
||||||
|
description: String
|
||||||
|
fields(includeDeprecated: Boolean = false): [__Field!]
|
||||||
|
interfaces: [__Type!]
|
||||||
|
possibleTypes: [__Type!]
|
||||||
|
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
|
||||||
|
inputFields: [__InputValue!]
|
||||||
|
ofType: __Type
|
||||||
|
}
|
||||||
|
|
||||||
|
# An enum describing what kind of type a given `__Type` is.
|
||||||
|
enum __TypeKind {
|
||||||
|
# Indicates this type is a scalar.
|
||||||
|
SCALAR
|
||||||
|
|
||||||
|
# Indicates this type is an object. `fields` and `interfaces` are valid fields.
|
||||||
|
OBJECT
|
||||||
|
|
||||||
|
# Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.
|
||||||
|
INTERFACE
|
||||||
|
|
||||||
|
# Indicates this type is a union. `possibleTypes` is a valid field.
|
||||||
|
UNION
|
||||||
|
|
||||||
|
# Indicates this type is an enum. `enumValues` is a valid field.
|
||||||
|
ENUM
|
||||||
|
|
||||||
|
# Indicates this type is an input object. `inputFields` is a valid field.
|
||||||
|
INPUT_OBJECT
|
||||||
|
|
||||||
|
# Indicates this type is a list. `ofType` is a valid field.
|
||||||
|
LIST
|
||||||
|
|
||||||
|
# Indicates this type is a non-null. `ofType` is a valid field.
|
||||||
|
NON_NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
EOT;
|
||||||
|
$this->assertEquals($output, $introspectionSchema);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user