Introspection for april 2016 spec + fixed tests

This commit is contained in:
vladar 2016-05-01 03:04:16 +06:00
parent e7c7924dc0
commit 183a9d72cf
2 changed files with 448 additions and 207 deletions

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type;
use GraphQL\Language\Printer;
use GraphQL\Schema;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
@ -16,6 +17,7 @@ use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Utils\AST;
class TypeKind {
const SCALAR = 0;
@ -42,18 +44,17 @@ class Introspection
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
onOperation
onFragment
onField
}
}
}
@ -62,7 +63,7 @@ class Introspection
kind
name
description
fields {
fields(includeDeprecated: true) {
name
description
args {
@ -80,7 +81,7 @@ class Introspection
interfaces {
...TypeRef
}
enumValues {
enumValues(includeDeprecated: true) {
name
description
isDeprecated
@ -120,17 +121,17 @@ EOD;
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
locations
args {
...InputValue
}
onOperation
onFragment
onField
}
}
}
@ -138,8 +139,10 @@ EOD;
fragment FullType on __Type {
kind
name
fields {
fields(includeDeprecated: true) {
name
args {
...InputValue
}
@ -155,8 +158,9 @@ EOD;
interfaces {
...TypeRef
}
enumValues {
enumValues(includeDeprecated: true) {
name
isDeprecated
deprecationReason
}
@ -167,6 +171,7 @@ EOD;
fragment InputValue on __InputValue {
name
type { ...TypeRef }
defaultValue
}
@ -250,28 +255,120 @@ EOD;
if (!isset(self::$_map['__Directive'])) {
self::$_map['__Directive'] = new ObjectType([
'name' => '__Directive',
'description' => 'A Directive provides a way to describe alternate runtime execution and ' .
'type validation behavior in a GraphQL document.' .
'\n\nIn some cases, you need to provide options to alter GraphQLs ' .
'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.',
'fields' => [
'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()],
'locations' => [
'type' => Type::nonNull(Type::listOf(Type::nonNull(
self::_directiveLocation()
)))
],
'args' => [
'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
'resolve' => function(Directive $directive) {return $directive->args ?: [];}
'resolve' => function (Directive $directive) {
return $directive->args ?: [];
}
],
'onOperation' => ['type' => Type::nonNull(Type::boolean())],
'onFragment' => ['type' => Type::nonNull(Type::boolean())],
'onField' => ['type' => Type::nonNull(Type::boolean())]
// NOTE: the following three fields are deprecated and are no longer part
// of the GraphQL specification.
'onOperation' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function($d) {
return in_array(Directive::$directiveLocations['QUERY'], $d['locations']) ||
in_array(Directive::$directiveLocations['MUTATION'], $d['locations']) ||
in_array(Directive::$directiveLocations['SUBSCRIPTION'], $d['locations']);
}
],
'onFragment' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function($d) {
return in_array(Directive::$directiveLocations['FRAGMENT_SPREAD'], $d['locations']) ||
in_array(Directive::$directiveLocations['INLINE_FRAGMENT'], $d['locations']) ||
in_array(Directive::$directiveLocations['FRAGMENT_DEFINITION'], $d['locations']);
}
],
'onField' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function($d) {
return in_array(Directive::$directiveLocations['FIELD'], $d['locations']);
}
]
]
]);
}
return self::$_map['__Directive'];
}
public static function _directiveLocation()
{
if (!isset(self::$_map['__DirectiveLocation'])) {
self::$_map['__DirectiveLocation'] = new EnumType([
'name' => '__DirectiveLocation',
'description' =>
'A Directive can be adjacent to many parts of the GraphQL language, a ' .
'__DirectiveLocation describes one such possible adjacencies.',
'values' => [
'QUERY' => [
'value' => Directive::$directiveLocations['QUERY'],
'description' => 'Location adjacent to a query operation.'
],
'MUTATION' => [
'value' => Directive::$directiveLocations['MUTATION'],
'description' => 'Location adjacent to a mutation operation.'
],
'SUBSCRIPTION' => [
'value' => Directive::$directiveLocations['SUBSCRIPTION'],
'description' => 'Location adjacent to a subscription operation.'
],
'FIELD' => [
'value' => Directive::$directiveLocations['FIELD'],
'description' => 'Location adjacent to a field.'
],
'FRAGMENT_DEFINITION' => [
'value' => Directive::$directiveLocations['FRAGMENT_DEFINITION'],
'description' => 'Location adjacent to a fragment definition.'
],
'FRAGMENT_SPREAD' => [
'value' => Directive::$directiveLocations['FRAGMENT_SPREAD'],
'description' => 'Location adjacent to a fragment spread.'
],
'INLINE_FRAGMENT' => [
'value' => Directive::$directiveLocations['INLINE_FRAGMENT'],
'description' => 'Location adjacent to an inline fragment.'
],
]
]);
}
return self::$_map['__DirectiveLocation'];
}
public static function _type()
{
if (!isset(self::$_map['__Type'])) {
self::$_map['__Type'] = new ObjectType([
'name' => '__Type',
'fields' => [
'description' =>
'The fundamental unit of any GraphQL Schema is the type. There are ' .
'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' .
"\n\n".
'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.',
'fields' => function() {
return [
'kind' => [
'type' => Type::nonNull(self::_typeKind()),
'resolve' => function (Type $type) {
@ -319,7 +416,7 @@ EOD;
}
],
'interfaces' => [
'type' => Type::listOf(Type::nonNull([__CLASS__, '_type'])),
'type' => Type::listOf(Type::nonNull(self::_type())),
'resolve' => function ($type) {
if ($type instanceof ObjectType) {
return $type->getInterfaces();
@ -329,9 +426,9 @@ EOD;
],
'possibleTypes' => [
'type' => Type::listOf(Type::nonNull([__CLASS__, '_type'])),
'resolve' => function ($type) {
'resolve' => function ($type, $args, ResolveInfo $info) {
if ($type instanceof InterfaceType || $type instanceof UnionType) {
return $type->getPossibleTypes();
return $info->schema->getPossibleTypes($type);
}
return null;
}
@ -366,14 +463,15 @@ EOD;
}
],
'ofType' => [
'type' => [__CLASS__, '_type'],
'resolve' => function($type) {
'type' => self::_type(),
'resolve' => function ($type) {
if ($type instanceof WrappingType) {
return $type->getWrappedType();
}
return null;
}]
]
];
}
]);
}
return self::$_map['__Type'];
@ -385,7 +483,11 @@ EOD;
self::$_map['__Field'] = new ObjectType([
'name' => '__Field',
'fields' => [
'description' =>
'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.',
'fields' => function() {
return [
'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()],
'args' => [
@ -395,7 +497,7 @@ EOD;
}
],
'type' => [
'type' => Type::nonNull([__CLASS__, '_type']),
'type' => Type::nonNull(self::_type()),
'resolve' => function ($field) {
return $field->getType();
}
@ -409,7 +511,8 @@ EOD;
'deprecationReason' => [
'type' => Type::string()
]
]
];
}
]);
}
return self::$_map['__Field'];
@ -420,22 +523,30 @@ EOD;
if (!isset(self::$_map['__InputValue'])) {
self::$_map['__InputValue'] = new ObjectType([
'name' => '__InputValue',
'fields' => [
'description' =>
'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.',
'fields' => function() {
return [
'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()],
'type' => [
'type' => Type::nonNull([__CLASS__, '_type']),
'resolve' => function($value) {
'type' => Type::nonNull(self::_type()),
'resolve' => function ($value) {
return method_exists($value, 'getType') ? $value->getType() : $value->type;
}
],
'defaultValue' => [
'type' => Type::string(),
'resolve' => function ($inputValue) {
return $inputValue->defaultValue === null ? null : json_encode($inputValue->defaultValue);
return $inputValue->defaultValue === null
? null
: Printer::doPrint(AST::astFromValue($inputValue->defaultValue, $inputValue->getType()));
}
]
]
];
}
]);
}
return self::$_map['__InputValue'];
@ -446,6 +557,10 @@ EOD;
if (!isset(self::$_map['__EnumValue'])) {
self::$_map['__EnumValue'] = new ObjectType([
'name' => '__EnumValue',
'description' =>
'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.',
'fields' => [
'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()],
@ -469,7 +584,7 @@ EOD;
if (!isset(self::$_map['__TypeKind'])) {
self::$_map['__TypeKind'] = new EnumType([
'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' => [
'SCALAR' => [
'value' => TypeKind::SCALAR,

View File

@ -14,12 +14,19 @@ use GraphQL\Validator\Rules\ProvidedNonNullArguments;
class IntrospectionTest extends \PHPUnit_Framework_TestCase
{
// Describe: Introspection
/**
* @it executes an introspection query
*/
function testExecutesAnIntrospectionQuery()
{
$emptySchema = new Schema(new ObjectType([
$emptySchema = new Schema([
'query' => new ObjectType([
'name' => 'QueryRoot',
'fields' => []
]));
])
]);
$request = Introspection::getIntrospectionQuery(false);
$expected = array (
@ -28,6 +35,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'__schema' =>
array (
'mutationType' => NULL,
'subscriptionType' => NULL,
'queryType' =>
array (
'name' => 'QueryRoot',
@ -44,7 +52,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
),
'enumValues' => NULL,
'possibleTypes' => NULL,
'fields' => []
'fields' => Array ()
),
1 =>
array (
@ -117,13 +125,13 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'deprecationReason' => NULL,
),
3 =>
array(
array (
'name' => 'subscriptionType',
'args' =>
array(
array (
),
'type' =>
array(
array (
'kind' => 'OBJECT',
'name' => '__Type',
'ofType' => NULL,
@ -817,6 +825,35 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'deprecationReason' => NULL,
),
2 =>
array (
'name' => 'locations',
'args' =>
array (
),
'type' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'LIST',
'name' => NULL,
'ofType' =>
array (
'kind' => 'NON_NULL',
'name' => NULL,
'ofType' =>
array (
'kind' => 'ENUM',
'name' => '__DirectiveLocation',
),
),
),
),
'isDeprecated' => false,
'deprecationReason' => NULL,
),
3 =>
array (
'name' => 'args',
'args' =>
@ -845,7 +882,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'isDeprecated' => false,
'deprecationReason' => NULL,
),
3 =>
4 =>
array (
'name' => 'onOperation',
'args' =>
@ -862,10 +899,10 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'ofType' => NULL,
),
),
'isDeprecated' => false,
'deprecationReason' => NULL,
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
),
4 =>
5 =>
array (
'name' => 'onFragment',
'args' =>
@ -882,10 +919,10 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'ofType' => NULL,
),
),
'isDeprecated' => false,
'deprecationReason' => NULL,
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
),
5 =>
6 =>
array (
'name' => 'onField',
'args' =>
@ -902,8 +939,8 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'ofType' => NULL,
),
),
'isDeprecated' => false,
'deprecationReason' => NULL,
'isDeprecated' => true,
'deprecationReason' => 'Use `locations`.',
),
),
'inputFields' => NULL,
@ -913,39 +950,99 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'enumValues' => NULL,
'possibleTypes' => NULL,
),
10 => [
10 =>
array (
'kind' => 'ENUM',
'name' => '__DirectiveLocation',
'fields' => NULL,
'inputFields' => NULL,
'interfaces' => NULL,
'enumValues' =>
array (
0 =>
array (
'name' => 'QUERY',
'isDeprecated' => false,
'deprecationReason' => null
),
1 =>
array (
'name' => 'MUTATION',
'isDeprecated' => false,
'deprecationReason' => null
),
2 =>
array (
'name' => 'SUBSCRIPTION',
'isDeprecated' => false,
'deprecationReason' => null
),
3 =>
array (
'name' => 'FIELD',
'isDeprecated' => false,
'deprecationReason' => null
),
4 =>
array (
'name' => 'FRAGMENT_DEFINITION',
'isDeprecated' => false,
'deprecationReason' => null
),
5 =>
array (
'name' => 'FRAGMENT_SPREAD',
'isDeprecated' => false,
'deprecationReason' => null
),
6 =>
array (
'name' => 'INLINE_FRAGMENT',
'isDeprecated' => false,
'deprecationReason' => null
),
),
'possibleTypes' => NULL,
),
11 => array (
'kind' => 'SCALAR',
'name' => 'ID',
'fields' => null,
'inputFields' => null,
'interfaces' => null,
'enumValues' => null,
'possibleTypes' => null
],
11 => [
'fields' => NULL,
'inputFields' => NULL,
'interfaces' => NULL,
'enumValues' => NULL,
'possibleTypes' => NULL,
),
12 => array (
'kind' => 'SCALAR',
'name' => 'Float',
'fields' => null,
'inputFields' => null,
'interfaces' => null,
'enumValues' => null,
'possibleTypes' => null
],
12 => [
'fields' => NULL,
'inputFields' => NULL,
'interfaces' => NULL,
'enumValues' => NULL,
'possibleTypes' => NULL,
),
13 => array (
'kind' => 'SCALAR',
'name' => 'Int',
'fields' => null,
'inputFields' => null,
'interfaces' => null,
'enumValues' => null,
'possibleTypes' => null
],
'fields' => NULL,
'inputFields' => NULL,
'interfaces' => NULL,
'enumValues' => NULL,
'possibleTypes' => NULL,
)
),
'directives' =>
array (
0 =>
array (
'name' => 'include',
'locations' =>
array (
0 => 'FIELD',
1 => 'FRAGMENT_SPREAD',
2 => 'INLINE_FRAGMENT',
),
'args' =>
array (
0 =>
@ -965,13 +1062,16 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
),
),
),
'onOperation' => false,
'onFragment' => true,
'onField' => true,
),
1 =>
array (
'name' => 'skip',
'locations' =>
array (
0 => 'FIELD',
1 => 'FRAGMENT_SPREAD',
2 => 'INLINE_FRAGMENT',
),
'args' =>
array (
0 =>
@ -991,20 +1091,23 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
),
),
),
'onOperation' => false,
'onFragment' => true,
'onField' => true,
),
),
),
),
)
);
$actual = GraphQL::execute($emptySchema, $request);
// print_r($actual);
// exit;
$this->assertEquals($expected, $actual);
}
/**
* @it introspects on input object
*/
function testIntrospectsOnInputObject()
{
$TestInputObject = new InputObjectType([
@ -1028,7 +1131,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__schema {
@ -1088,6 +1191,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertContains($expectedFragment, $result);
}
/**
* @it supports the __type root field
*/
public function testSupportsThe__typeRootField()
{
@ -1100,7 +1206,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__type(name: "TestType") {
@ -1118,6 +1224,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it identifies deprecated fields
*/
public function testIdentifiesDeprecatedFields()
{
@ -1134,7 +1243,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__type(name: "TestType") {
@ -1170,6 +1279,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it respects the includeDeprecated parameter for fields
*/
public function testRespectsTheIncludeDeprecatedParameterForFields()
{
$TestType = new ObjectType([
@ -1185,7 +1297,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__type(name: "TestType") {
@ -1232,6 +1344,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it identifies deprecated enum values
*/
public function testIdentifiesDeprecatedEnumValues()
{
$TestEnum = new EnumType([
@ -1252,7 +1367,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__type(name: "TestEnum") {
@ -1293,6 +1408,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it respects the includeDeprecated parameter for enum values
*/
public function testRespectsTheIncludeDeprecatedParameterForEnumValues()
{
$TestEnum = new EnumType([
@ -1313,7 +1431,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__type(name: "TestEnum") {
@ -1353,9 +1471,11 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it fails as expected on the __type root field without an arg
*/
public function testFailsAsExpectedOnThe__typeRootFieldWithoutAnArg()
{
$TestType = new ObjectType([
'name' => 'TestType',
'fields' => [
@ -1365,7 +1485,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
]
]);
$schema = new Schema($TestType);
$schema = new Schema(['query' => $TestType]);
$request = '
{
__type {
@ -1384,6 +1504,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it exposes descriptions on types and fields
*/
public function testExposesDescriptionsOnTypesAndFields()
{
$QueryRoot = new ObjectType([
@ -1391,7 +1514,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'fields' => []
]);
$schema = new Schema($QueryRoot);
$schema = new Schema(['query' => $QueryRoot]);
$request = '
{
schemaType: __type(name: "__Schema") {
@ -1441,6 +1564,9 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, GraphQL::execute($schema, $request));
}
/**
* @it exposes descriptions on enums
*/
public function testExposesDescriptionsOnEnums()
{
$QueryRoot = new ObjectType([
@ -1448,7 +1574,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'fields' => []
]);
$schema = new Schema($QueryRoot);
$schema = new Schema(['query' => $QueryRoot]);
$request = '
{
typeKindType: __type(name: "__TypeKind") {
@ -1465,7 +1591,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
'data' => [
'typeKindType' => [
'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' => [
[
'description' => 'Indicates this type is a scalar.',