2017-02-19 22:26:56 +03:00
|
|
|
<?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;
|
2017-07-05 15:33:25 +03:00
|
|
|
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
|
|
|
use GraphQL\Language\AST\FieldDefinitionNode;
|
2017-02-19 22:26:56 +03:00
|
|
|
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;
|
2018-02-09 15:49:10 +03:00
|
|
|
use GraphQL\Language\AST\SchemaDefinitionNode;
|
2017-02-19 22:26:56 +03:00
|
|
|
use GraphQL\Language\AST\TypeNode;
|
|
|
|
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
|
|
|
use GraphQL\Language\Parser;
|
2017-03-04 23:10:52 +03:00
|
|
|
use GraphQL\Language\Source;
|
2017-02-19 22:26:56 +03:00
|
|
|
use GraphQL\Language\Token;
|
2017-08-12 17:32:07 +03:00
|
|
|
use GraphQL\Type\Schema;
|
2017-02-19 22:26:56 +03:00
|
|
|
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;
|
|
|
|
|
|
|
|
/**
|
2017-08-20 18:10:13 +03:00
|
|
|
* Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST)
|
|
|
|
* See [section in docs](type-system/type-language.md) for details.
|
2017-02-19 22:26:56 +03:00
|
|
|
*/
|
|
|
|
class BuildSchema
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @param Type $innerType
|
|
|
|
* @param TypeNode $inputTypeNode
|
2018-02-08 21:33:54 +03:00
|
|
|
* @return Type
|
2017-02-19 22:26:56 +03:00
|
|
|
*/
|
|
|
|
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.
|
|
|
|
*
|
2017-08-20 18:10:13 +03:00
|
|
|
* Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
|
2017-02-19 22:26:56 +03:00
|
|
|
* has no resolve methods, so execution will use default resolvers.
|
|
|
|
*
|
2018-02-08 21:33:54 +03:00
|
|
|
* Accepts options as a third argument:
|
|
|
|
*
|
|
|
|
* - commentDescriptions:
|
|
|
|
* Provide true to use preceding comments as the description.
|
|
|
|
*
|
|
|
|
*
|
2017-08-20 18:10:13 +03:00
|
|
|
* @api
|
2017-02-19 22:26:56 +03:00
|
|
|
* @param DocumentNode $ast
|
2017-07-28 13:55:25 +03:00
|
|
|
* @param callable $typeConfigDecorator
|
2017-02-19 22:26:56 +03:00
|
|
|
* @return Schema
|
|
|
|
* @throws Error
|
|
|
|
*/
|
2018-02-08 21:33:54 +03:00
|
|
|
public static function buildAST(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2018-02-08 21:33:54 +03:00
|
|
|
$builder = new self($ast, $typeConfigDecorator, $options);
|
2017-02-19 22:26:56 +03:00
|
|
|
return $builder->buildSchema();
|
|
|
|
}
|
|
|
|
|
|
|
|
private $ast;
|
|
|
|
private $innerTypeMap;
|
|
|
|
private $nodeMap;
|
2017-07-28 13:55:25 +03:00
|
|
|
private $typeConfigDecorator;
|
|
|
|
private $loadedTypeDefs;
|
2018-02-08 21:33:54 +03:00
|
|
|
private $options;
|
2017-02-19 22:26:56 +03:00
|
|
|
|
2018-02-08 21:33:54 +03:00
|
|
|
public function __construct(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
|
|
|
$this->ast = $ast;
|
2017-07-28 13:55:25 +03:00
|
|
|
$this->typeConfigDecorator = $typeConfigDecorator;
|
|
|
|
$this->loadedTypeDefs = [];
|
2018-02-08 21:33:54 +03:00
|
|
|
$this->options = $options;
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
2018-02-08 21:33:54 +03:00
|
|
|
|
2017-02-19 22:26:56 +03:00
|
|
|
public function buildSchema()
|
|
|
|
{
|
2018-02-09 15:49:10 +03:00
|
|
|
/** @var SchemaDefinitionNode $schemaDef */
|
2017-02-19 22:26:56 +03:00
|
|
|
$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:
|
2017-07-04 09:59:46 +03:00
|
|
|
$typeName = $d->name->value;
|
|
|
|
if (!empty($this->nodeMap[$typeName])) {
|
|
|
|
throw new Error("Type \"$typeName\" was defined more than once.");
|
|
|
|
}
|
2017-02-19 22:26:56 +03:00
|
|
|
$typeDefs[] = $d;
|
2017-07-04 09:59:46 +03:00
|
|
|
$this->nodeMap[$typeName] = $d;
|
2017-02-19 22:26:56 +03:00
|
|
|
break;
|
|
|
|
case NodeKind::DIRECTIVE_DEFINITION:
|
|
|
|
$directiveDefs[] = $d;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 15:49:10 +03:00
|
|
|
$operationTypes = $schemaDef
|
|
|
|
? $this->getOperationTypes($schemaDef)
|
|
|
|
: [
|
|
|
|
'query' => isset($this->nodeMap['Query']) ? 'Query' : null,
|
|
|
|
'mutation' => isset($this->nodeMap['Mutation']) ? 'Mutation' : null,
|
|
|
|
'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null,
|
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
|
|
|
|
$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(),
|
|
|
|
];
|
|
|
|
|
|
|
|
$directives = array_map([$this, 'getDirective'], $directiveDefs);
|
|
|
|
|
|
|
|
// If specified directives were not explicitly declared, add them.
|
2018-02-09 14:54:34 +03:00
|
|
|
$skip = array_reduce($directives, function ($hasSkip, $directive) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return $hasSkip || $directive->name == 'skip';
|
|
|
|
});
|
|
|
|
if (!$skip) {
|
|
|
|
$directives[] = Directive::skipDirective();
|
|
|
|
}
|
|
|
|
|
2018-02-09 14:54:34 +03:00
|
|
|
$include = array_reduce($directives, function ($hasInclude, $directive) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return $hasInclude || $directive->name == 'include';
|
|
|
|
});
|
|
|
|
if (!$include) {
|
|
|
|
$directives[] = Directive::includeDirective();
|
|
|
|
}
|
|
|
|
|
2018-02-09 14:54:34 +03:00
|
|
|
$deprecated = array_reduce($directives, function ($hasDeprecated, $directive) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return $hasDeprecated || $directive->name == 'deprecated';
|
|
|
|
});
|
|
|
|
if (!$deprecated) {
|
|
|
|
$directives[] = Directive::deprecatedDirective();
|
|
|
|
}
|
|
|
|
|
2018-02-09 15:49:10 +03:00
|
|
|
if (!isset($operationTypes['query'])) {
|
|
|
|
throw new Error(
|
|
|
|
'Must provide schema definition with query type or a type named Query.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-03-04 23:10:52 +03:00
|
|
|
$schema = new Schema([
|
2018-02-09 15:49:10 +03:00
|
|
|
'query' => $this->getObjectType($operationTypes['query']),
|
|
|
|
'mutation' => isset($operationTypes['mutation']) ?
|
|
|
|
$this->getObjectType($operationTypes['mutation']) :
|
2017-02-19 22:26:56 +03:00
|
|
|
null,
|
2018-02-09 15:49:10 +03:00
|
|
|
'subscription' => isset($operationTypes['subscription']) ?
|
|
|
|
$this->getObjectType($operationTypes['subscription']) :
|
2017-02-19 22:26:56 +03:00
|
|
|
null,
|
2018-02-09 14:54:34 +03:00
|
|
|
'typeLoader' => function ($name) {
|
2017-07-28 13:55:25 +03:00
|
|
|
return $this->typeDefNamed($name);
|
|
|
|
},
|
2017-02-19 22:26:56 +03:00
|
|
|
'directives' => $directives,
|
2017-09-20 13:43:06 +03:00
|
|
|
'astNode' => $schemaDef,
|
2018-02-09 14:54:34 +03:00
|
|
|
'types' => function () {
|
2017-07-28 13:55:25 +03:00
|
|
|
$types = [];
|
|
|
|
foreach ($this->nodeMap as $name => $def) {
|
|
|
|
if (!isset($this->loadedTypeDefs[$name])) {
|
|
|
|
$types[] = $this->typeDefNamed($def->name->value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $types;
|
|
|
|
}
|
2017-02-19 22:26:56 +03:00
|
|
|
]);
|
2017-03-04 23:10:52 +03:00
|
|
|
|
|
|
|
return $schema;
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
2018-02-09 15:49:10 +03:00
|
|
|
/**
|
|
|
|
* @param SchemaDefinitionNode $schemaDef
|
|
|
|
* @return array
|
|
|
|
* @throws Error
|
|
|
|
*/
|
|
|
|
private function getOperationTypes($schemaDef)
|
|
|
|
{
|
|
|
|
$opTypes = [];
|
|
|
|
|
|
|
|
foreach ($schemaDef->operationTypes as $operationType) {
|
|
|
|
$typeName = $operationType->type->name->value;
|
|
|
|
$operation = $operationType->operation;
|
|
|
|
|
|
|
|
if (isset($opTypes[$operation])) {
|
|
|
|
throw new Error("Must provide only one $operation type in schema.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($this->nodeMap[$typeName])) {
|
|
|
|
throw new Error("Specified $operation type \"$typeName\" not found in document.");
|
|
|
|
}
|
|
|
|
|
|
|
|
$opTypes[$operation] = $typeName;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $opTypes;
|
|
|
|
}
|
|
|
|
|
2017-02-19 22:26:56 +03:00
|
|
|
private function getDirective(DirectiveDefinitionNode $directiveNode)
|
|
|
|
{
|
|
|
|
return new Directive([
|
|
|
|
'name' => $directiveNode->name->value,
|
|
|
|
'description' => $this->getDescription($directiveNode),
|
2018-02-09 14:54:34 +03:00
|
|
|
'locations' => Utils::map($directiveNode->locations, function ($node) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return $node->value;
|
2017-07-28 13:55:25 +03:00
|
|
|
}),
|
2017-02-19 22:26:56 +03:00
|
|
|
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
|
2017-09-20 13:43:06 +03:00
|
|
|
'astNode' => $directiveNode
|
2017-02-19 22:26:56 +03:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2018-02-09 15:49:10 +03:00
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @return CustomScalarType|EnumType|InputObjectType|UnionType
|
|
|
|
* @throws Error
|
|
|
|
*/
|
|
|
|
private function getObjectType($name)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2018-02-09 15:49:10 +03:00
|
|
|
$type = $this->typeDefNamed($name);
|
2017-02-19 22:26:56 +03:00
|
|
|
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);
|
2017-07-03 19:09:50 +03:00
|
|
|
Utils::invariant($type instanceof InterfaceType, 'Expected Interface type.');
|
2017-02-19 22:26:56 +03:00
|
|
|
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.');
|
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
$this->loadedTypeDefs[$typeName] = true;
|
|
|
|
|
|
|
|
$config = $this->makeSchemaDefConfig($this->nodeMap[$typeName]);
|
|
|
|
|
|
|
|
if ($this->typeConfigDecorator) {
|
|
|
|
$fn = $this->typeConfigDecorator;
|
|
|
|
try {
|
2017-08-17 16:33:36 +03:00
|
|
|
$config = $fn($config, $this->nodeMap[$typeName], $this->nodeMap);
|
2017-07-28 13:55:25 +03:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
throw new Error(
|
2018-02-09 14:54:34 +03:00
|
|
|
"Type config decorator passed to " . (static::class) . " threw an error " .
|
2017-07-28 13:55:25 +03:00
|
|
|
"when building $typeName type: {$e->getMessage()}",
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$e
|
|
|
|
);
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
throw new Error(
|
2018-02-09 14:54:34 +03:00
|
|
|
"Type config decorator passed to " . (static::class) . " threw an error " .
|
2017-07-28 13:55:25 +03:00
|
|
|
"when building $typeName type: {$e->getMessage()}",
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!is_array($config) || isset($config[0])) {
|
|
|
|
throw new Error(
|
2018-02-09 14:54:34 +03:00
|
|
|
"Type config decorator passed to " . (static::class) . " is expected to return an array, but got " .
|
2017-07-28 13:55:25 +03:00
|
|
|
Utils::getVariableType($config)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$innerTypeDef = $this->makeSchemaDef($this->nodeMap[$typeName], $config);
|
|
|
|
|
2017-02-19 22:26:56 +03:00
|
|
|
if (!$innerTypeDef) {
|
|
|
|
throw new Error("Nothing constructed for $typeName.");
|
|
|
|
}
|
|
|
|
$this->innerTypeMap[$typeName] = $innerTypeDef;
|
|
|
|
return $innerTypeDef;
|
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeSchemaDefConfig($def)
|
|
|
|
{
|
|
|
|
if (!$def) {
|
|
|
|
throw new Error('def must be defined.');
|
|
|
|
}
|
|
|
|
switch ($def->kind) {
|
|
|
|
case NodeKind::OBJECT_TYPE_DEFINITION:
|
|
|
|
return $this->makeTypeDefConfig($def);
|
|
|
|
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
|
|
|
return $this->makeInterfaceDefConfig($def);
|
|
|
|
case NodeKind::ENUM_TYPE_DEFINITION:
|
|
|
|
return $this->makeEnumDefConfig($def);
|
|
|
|
case NodeKind::UNION_TYPE_DEFINITION:
|
|
|
|
return $this->makeUnionDefConfig($def);
|
|
|
|
case NodeKind::SCALAR_TYPE_DEFINITION:
|
|
|
|
return $this->makeScalarDefConfig($def);
|
|
|
|
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
|
|
|
return $this->makeInputObjectDefConfig($def);
|
|
|
|
default:
|
|
|
|
throw new Error("Type kind of {$def->kind} not supported.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function makeSchemaDef($def, array $config = null)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
|
|
|
if (!$def) {
|
|
|
|
throw new Error('def must be defined.');
|
|
|
|
}
|
2017-07-28 13:55:25 +03:00
|
|
|
|
|
|
|
$config = $config ?: $this->makeSchemaDefConfig($def);
|
|
|
|
|
2017-02-19 22:26:56 +03:00
|
|
|
switch ($def->kind) {
|
|
|
|
case NodeKind::OBJECT_TYPE_DEFINITION:
|
2017-07-28 13:55:25 +03:00
|
|
|
return new ObjectType($config);
|
2017-02-19 22:26:56 +03:00
|
|
|
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
2017-07-28 13:55:25 +03:00
|
|
|
return new InterfaceType($config);
|
2017-02-19 22:26:56 +03:00
|
|
|
case NodeKind::ENUM_TYPE_DEFINITION:
|
2017-07-28 13:55:25 +03:00
|
|
|
return new EnumType($config);
|
2017-02-19 22:26:56 +03:00
|
|
|
case NodeKind::UNION_TYPE_DEFINITION:
|
2017-07-28 13:55:25 +03:00
|
|
|
return new UnionType($config);
|
2017-02-19 22:26:56 +03:00
|
|
|
case NodeKind::SCALAR_TYPE_DEFINITION:
|
2017-07-28 13:55:25 +03:00
|
|
|
return new CustomScalarType($config);
|
2017-02-19 22:26:56 +03:00
|
|
|
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
2017-07-28 13:55:25 +03:00
|
|
|
return new InputObjectType($config);
|
2017-02-19 22:26:56 +03:00
|
|
|
default:
|
|
|
|
throw new Error("Type kind of {$def->kind} not supported.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeTypeDefConfig(ObjectTypeDefinitionNode $def)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
|
|
|
$typeName = $def->name->value;
|
2017-07-28 13:55:25 +03:00
|
|
|
return [
|
2017-02-19 22:26:56 +03:00
|
|
|
'name' => $typeName,
|
|
|
|
'description' => $this->getDescription($def),
|
2018-02-09 14:54:34 +03:00
|
|
|
'fields' => function () use ($def) {
|
2017-07-28 13:55:25 +03:00
|
|
|
return $this->makeFieldDefMap($def);
|
|
|
|
},
|
2018-02-09 14:54:34 +03:00
|
|
|
'interfaces' => function () use ($def) {
|
2017-07-28 13:55:25 +03:00
|
|
|
return $this->makeImplementedInterfaces($def);
|
2017-09-20 13:43:06 +03:00
|
|
|
},
|
|
|
|
'astNode' => $def
|
2017-07-28 13:55:25 +03:00
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private function makeFieldDefMap($def)
|
|
|
|
{
|
|
|
|
return Utils::keyValMap(
|
|
|
|
$def->fields,
|
|
|
|
function ($field) {
|
|
|
|
return $field->name->value;
|
|
|
|
},
|
2018-02-09 14:54:34 +03:00
|
|
|
function ($field) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return [
|
|
|
|
'type' => $this->produceOutputType($field->type),
|
|
|
|
'description' => $this->getDescription($field),
|
|
|
|
'args' => $this->makeInputValues($field->arguments),
|
2017-09-20 13:43:06 +03:00
|
|
|
'deprecationReason' => $this->getDeprecationReason($field),
|
|
|
|
'astNode' => $field
|
2017-02-19 22:26:56 +03:00
|
|
|
];
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def)
|
|
|
|
{
|
2017-07-28 13:55:25 +03:00
|
|
|
if (isset($def->interfaces)) {
|
|
|
|
return Utils::map($def->interfaces, function ($iface) {
|
|
|
|
return $this->produceInterfaceType($iface);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return null;
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private function makeInputValues($values)
|
|
|
|
{
|
|
|
|
return Utils::keyValMap(
|
|
|
|
$values,
|
|
|
|
function ($value) {
|
|
|
|
return $value->name->value;
|
|
|
|
},
|
2018-02-09 14:54:34 +03:00
|
|
|
function ($value) {
|
2017-02-19 22:26:56 +03:00
|
|
|
$type = $this->produceInputType($value->type);
|
|
|
|
$config = [
|
|
|
|
'name' => $value->name->value,
|
|
|
|
'type' => $type,
|
2017-09-20 13:43:06 +03:00
|
|
|
'description' => $this->getDescription($value),
|
|
|
|
'astNode' => $value
|
2017-02-19 22:26:56 +03:00
|
|
|
];
|
|
|
|
if (isset($value->defaultValue)) {
|
2017-07-10 15:50:26 +03:00
|
|
|
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
return $config;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeInterfaceDefConfig(InterfaceTypeDefinitionNode $def)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
|
|
|
$typeName = $def->name->value;
|
2017-07-28 13:55:25 +03:00
|
|
|
return [
|
2017-02-19 22:26:56 +03:00
|
|
|
'name' => $typeName,
|
|
|
|
'description' => $this->getDescription($def),
|
2018-02-09 14:54:34 +03:00
|
|
|
'fields' => function () use ($def) {
|
2017-07-28 13:55:25 +03:00
|
|
|
return $this->makeFieldDefMap($def);
|
|
|
|
},
|
2018-02-09 14:54:34 +03:00
|
|
|
'astNode' => $def
|
2017-07-28 13:55:25 +03:00
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeEnumDefConfig(EnumTypeDefinitionNode $def)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2017-07-28 13:55:25 +03:00
|
|
|
return [
|
2017-02-19 22:26:56 +03:00
|
|
|
'name' => $def->name->value,
|
|
|
|
'description' => $this->getDescription($def),
|
2017-09-20 13:43:06 +03:00
|
|
|
'astNode' => $def,
|
2017-02-19 22:26:56 +03:00
|
|
|
'values' => Utils::keyValMap(
|
|
|
|
$def->values,
|
2018-02-09 14:54:34 +03:00
|
|
|
function ($enumValue) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return $enumValue->name->value;
|
|
|
|
},
|
2018-02-09 14:54:34 +03:00
|
|
|
function ($enumValue) {
|
2017-02-19 22:26:56 +03:00
|
|
|
return [
|
|
|
|
'description' => $this->getDescription($enumValue),
|
2017-09-20 13:43:06 +03:00
|
|
|
'deprecationReason' => $this->getDeprecationReason($enumValue),
|
|
|
|
'astNode' => $enumValue
|
2017-02-19 22:26:56 +03:00
|
|
|
];
|
|
|
|
}
|
|
|
|
)
|
2017-07-28 13:55:25 +03:00
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeUnionDefConfig(UnionTypeDefinitionNode $def)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2017-07-28 13:55:25 +03:00
|
|
|
return [
|
2017-02-19 22:26:56 +03:00
|
|
|
'name' => $def->name->value,
|
|
|
|
'description' => $this->getDescription($def),
|
2018-02-09 14:54:34 +03:00
|
|
|
'types' => Utils::map($def->types, function ($typeNode) {
|
2017-07-28 13:55:25 +03:00
|
|
|
return $this->produceObjectType($typeNode);
|
|
|
|
}),
|
2018-02-09 14:54:34 +03:00
|
|
|
'astNode' => $def
|
2017-07-28 13:55:25 +03:00
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeScalarDefConfig(ScalarTypeDefinitionNode $def)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2017-07-28 13:55:25 +03:00
|
|
|
return [
|
2017-02-19 22:26:56 +03:00
|
|
|
'name' => $def->name->value,
|
|
|
|
'description' => $this->getDescription($def),
|
2017-09-20 13:43:06 +03:00
|
|
|
'astNode' => $def,
|
2018-02-09 14:54:34 +03:00
|
|
|
'serialize' => function () {
|
2017-07-28 13:55:25 +03:00
|
|
|
return false;
|
|
|
|
},
|
2017-02-19 22:26:56 +03:00
|
|
|
// 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.
|
2018-02-09 14:54:34 +03:00
|
|
|
'parseValue' => function () {
|
2017-07-28 13:55:25 +03:00
|
|
|
return false;
|
|
|
|
},
|
2018-02-09 14:54:34 +03:00
|
|
|
'parseLiteral' => function () {
|
2017-07-28 13:55:25 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
2017-07-28 13:55:25 +03:00
|
|
|
private function makeInputObjectDefConfig(InputObjectTypeDefinitionNode $def)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2017-07-28 13:55:25 +03:00
|
|
|
return [
|
2017-02-19 22:26:56 +03:00
|
|
|
'name' => $def->name->value,
|
|
|
|
'description' => $this->getDescription($def),
|
2018-02-09 14:54:34 +03:00
|
|
|
'fields' => function () use ($def) {
|
|
|
|
return $this->makeInputValues($def->fields);
|
|
|
|
},
|
2017-09-20 13:43:06 +03:00
|
|
|
'astNode' => $def,
|
2017-07-28 13:55:25 +03:00
|
|
|
];
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
2017-07-04 09:59:46 +03:00
|
|
|
/**
|
|
|
|
* Given a collection of directives, returns the string value for the
|
|
|
|
* deprecation reason.
|
|
|
|
*
|
2017-07-05 15:33:25 +03:00
|
|
|
* @param EnumValueDefinitionNode | FieldDefinitionNode $node
|
|
|
|
* @return string
|
2017-07-04 09:59:46 +03:00
|
|
|
*/
|
2017-07-05 15:33:25 +03:00
|
|
|
private function getDeprecationReason($node)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2017-07-05 15:33:25 +03:00
|
|
|
$deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node);
|
|
|
|
return isset($deprecated['reason']) ? $deprecated['reason'] : null;
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-08 21:33:54 +03:00
|
|
|
* Given an ast node, returns its string description.
|
2017-02-19 22:26:56 +03:00
|
|
|
*/
|
|
|
|
public function getDescription($node)
|
2018-02-08 21:33:54 +03:00
|
|
|
{
|
|
|
|
if ($node->description) {
|
|
|
|
return $node->description->value;
|
|
|
|
}
|
|
|
|
if (isset($this->options['commentDescriptions'])) {
|
|
|
|
$rawValue = $this->getLeadingCommentBlock($node);
|
|
|
|
if ($rawValue !== null) {
|
|
|
|
return BlockString::value("\n" . $rawValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLeadingCommentBlock($node)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
|
|
|
$loc = $node->loc;
|
2017-07-28 13:55:25 +03:00
|
|
|
if (!$loc || !$loc->startToken) {
|
2018-02-09 14:54:34 +03:00
|
|
|
return;
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
$comments = [];
|
|
|
|
$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;
|
|
|
|
$comments[] = $value;
|
|
|
|
$token = $token->prev;
|
|
|
|
}
|
2018-02-08 21:33:54 +03:00
|
|
|
|
|
|
|
return implode("\n", array_reverse($comments));
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper function to build a GraphQLSchema directly from a source
|
|
|
|
* document.
|
2018-02-08 21:33:54 +03:00
|
|
|
*
|
2017-08-20 18:10:13 +03:00
|
|
|
* @api
|
2017-08-17 16:33:36 +03:00
|
|
|
* @param DocumentNode|Source|string $source
|
2017-07-28 13:55:25 +03:00
|
|
|
* @param callable $typeConfigDecorator
|
2017-03-04 23:10:52 +03:00
|
|
|
* @return Schema
|
2017-02-19 22:26:56 +03:00
|
|
|
*/
|
2017-07-28 13:55:25 +03:00
|
|
|
public static function build($source, callable $typeConfigDecorator = null)
|
2017-02-19 22:26:56 +03:00
|
|
|
{
|
2017-08-17 16:33:36 +03:00
|
|
|
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
|
|
|
|
return self::buildAST($doc, $typeConfigDecorator);
|
2017-02-19 22:26:56 +03:00
|
|
|
}
|
2018-02-09 14:54:34 +03:00
|
|
|
}
|