Skip to content

Commit

Permalink
Merge 2a22166 into 5109bf1
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Jul 23, 2020
2 parents 5109bf1 + 2a22166 commit 40be41f
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 107 deletions.
78 changes: 39 additions & 39 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# API Platform Core

API Platform Core is an easy to use and powerful system to create [hypermedia-driven REST APIs](https://en.wikipedia.org/wiki/HATEOAS).
API Platform Core is an easy to use and powerful system to create [hypermedia-driven REST](https://en.wikipedia.org/wiki/HATEOAS) and [GraphQL](https://graphql.org/) APIs.
It is a component of the [API Platform framework](https://api-platform.com) and it can be integrated
with [the Symfony framework](https://symfony.com) using the bundle distributed with the library.

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"require": {
"php": ">=7.1",
"doctrine/inflector": "^1.0",
"doctrine/inflector": "^1.0 || ^2.0",
"fig/link-util": "^1.0",
"psr/cache": "^1.0",
"psr/container": "^1.0",
Expand Down Expand Up @@ -88,7 +88,7 @@
"symfony/yaml": "^3.4 || ^4.0 || ^5.0",
"teohhanhui/stubs-mongodb": "@dev",
"twig/twig": "^1.42.3 || ^2.12",
"webonyx/graphql-php": ">=0.13.1 <1.0"
"webonyx/graphql-php": "^14.0"
},
"conflict": {
"doctrine/common": "<2.7",
Expand Down
9 changes: 7 additions & 2 deletions src/GraphQl/Action/EntrypointAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\Core\GraphQl\ExecutorInterface;
use ApiPlatform\Core\GraphQl\Type\SchemaBuilderInterface;
use GraphQL\Error\Debug;
use GraphQL\Error\DebugFlag;
use GraphQL\Error\Error;
use GraphQL\Executor\ExecutionResult;
use Symfony\Component\HttpFoundation\JsonResponse;
Expand Down Expand Up @@ -53,7 +54,11 @@ public function __construct(SchemaBuilderInterface $schemaBuilder, ExecutorInter
$this->graphQlPlaygroundAction = $graphQlPlaygroundAction;
$this->normalizer = $normalizer;
$this->errorHandler = $errorHandler;
$this->debug = $debug ? Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE : false;
if (class_exists(Debug::class)) {
$this->debug = $debug ? Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE : false;
} else {
$this->debug = $debug ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : DebugFlag::NONE;
}
$this->graphiqlEnabled = $graphiqlEnabled;
$this->graphQlPlaygroundEnabled = $graphQlPlaygroundEnabled;
$this->defaultIde = $defaultIde;
Expand Down Expand Up @@ -82,7 +87,7 @@ public function __invoke(Request $request): Response
->setErrorsHandler($this->errorHandler)
->setErrorFormatter([$this->normalizer, 'normalize']);
} catch (\Exception $exception) {
$executionResult = (new ExecutionResult(null, [new Error($exception->getMessage(), null, null, null, null, $exception)]))
$executionResult = (new ExecutionResult(null, [new Error($exception->getMessage(), null, null, [], null, $exception)]))
->setErrorsHandler($this->errorHandler)
->setErrorFormatter([$this->normalizer, 'normalize']);
}
Expand Down
55 changes: 42 additions & 13 deletions src/GraphQl/Type/Definition/IterableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,52 @@
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;

if (\PHP_VERSION_ID >= 70200) {
trait IterableTypeParseLiteralTrait
{
/**
* {@inheritdoc}
*
* @param ObjectValueNode|ListValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|NullValueNode $valueNode
*/
public function parseLiteral(/*Node */$valueNode, ?array $variables = null)
{
if ($valueNode instanceof ObjectValueNode || $valueNode instanceof ListValueNode) {
return $this->parseIterableLiteral($valueNode);
}

// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
}
}
} else {
trait IterableTypeParseLiteralTrait
{
/**
* {@inheritdoc}
*
* @param ObjectValueNode|ListValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|NullValueNode $valueNode
*/
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if ($valueNode instanceof ObjectValueNode || $valueNode instanceof ListValueNode) {
return $this->parseIterableLiteral($valueNode);
}

// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
}
}
}

/**
* Represents an iterable type.
*
Expand All @@ -33,6 +73,8 @@
*/
final class IterableType extends ScalarType implements TypeInterface
{
use IterableTypeParseLiteralTrait;

public function __construct()
{
$this->name = 'Iterable';
Expand Down Expand Up @@ -70,19 +112,6 @@ public function parseValue($value)
return $value;
}

/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, array $variables = null)
{
if ($valueNode instanceof ObjectValueNode || $valueNode instanceof ListValueNode) {
return $this->parseIterableLiteral($valueNode);
}

// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
}

/**
* @param StringValueNode|BooleanValueNode|IntValueNode|FloatValueNode|ObjectValueNode|ListValueNode|ValueNode $valueNode
*/
Expand Down
4 changes: 1 addition & 3 deletions src/GraphQl/Type/Definition/TypeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@

namespace ApiPlatform\Core\GraphQl\Type\Definition;

use GraphQL\Type\Definition\LeafType;

/**
* @experimental
*
* @author Alan Poulain <contact@alanpoulain.eu>
*/
interface TypeInterface extends LeafType
interface TypeInterface
{
public function getName(): string;
}
35 changes: 27 additions & 8 deletions src/GraphQl/Type/Definition/UploadType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,44 @@
namespace ApiPlatform\Core\GraphQl\Type\Definition;

use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
use Symfony\Component\HttpFoundation\File\UploadedFile;

if (\PHP_VERSION_ID >= 70200) {
trait UploadTypeParseLiteralTrait
{
/**
* {@inheritdoc}
*/
public function parseLiteral(/*Node */$valueNode, array $variables = null)
{
throw new Error('`Upload` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification.', $valueNode);
}
}
} else {
trait UploadTypeParseLiteralTrait
{
/**
* {@inheritdoc}
*/
public function parseLiteral(Node $valueNode, array $variables = null)
{
throw new Error('`Upload` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification.', $valueNode);
}
}
}

/**
* Represents an upload type.
*
* @author Mahmood Bazdar <mahmood@bazdar.me>
*/
final class UploadType extends ScalarType implements TypeInterface
{
use UploadTypeParseLiteralTrait;

public function __construct()
{
$this->name = 'Upload';
Expand Down Expand Up @@ -57,12 +84,4 @@ public function parseValue($value): UploadedFile

return $value;
}

/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, array $variables = null)
{
throw new Error('`Upload` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification.', $valueNode);
}
}
12 changes: 6 additions & 6 deletions src/GraphQl/Type/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function getItemQueryFields(string $resourceClass, ResourceMetadata $reso
$shortName = $resourceMetadata->getShortName();
$fieldName = lcfirst('item_query' === $queryName ? $shortName : $queryName.$shortName);
$description = $resourceMetadata->getGraphqlAttribute($queryName, 'description');
$deprecationReason = (string) $resourceMetadata->getGraphqlAttribute($queryName, 'deprecation_reason', '', true);
$deprecationReason = (string) $resourceMetadata->getGraphqlAttribute($queryName, 'deprecation_reason', null, true);

if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass, false, $queryName, null, null)) {
$args = $this->resolveResourceArgs($configuration['args'] ?? [], $queryName, $shortName);
Expand All @@ -116,7 +116,7 @@ public function getCollectionQueryFields(string $resourceClass, ResourceMetadata
$shortName = $resourceMetadata->getShortName();
$fieldName = lcfirst('collection_query' === $queryName ? $shortName : $queryName.$shortName);
$description = $resourceMetadata->getGraphqlAttribute($queryName, 'description');
$deprecationReason = (string) $resourceMetadata->getGraphqlAttribute($queryName, 'deprecation_reason', '', true);
$deprecationReason = (string) $resourceMetadata->getGraphqlAttribute($queryName, 'deprecation_reason', null, true);

if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass, false, $queryName, null, null)) {
$args = $this->resolveResourceArgs($configuration['args'] ?? [], $queryName, $shortName);
Expand All @@ -137,7 +137,7 @@ public function getMutationFields(string $resourceClass, ResourceMetadata $resou
$shortName = $resourceMetadata->getShortName();
$resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass);
$description = $resourceMetadata->getGraphqlAttribute($mutationName, 'description', ucfirst("{$mutationName}s a $shortName."), false);
$deprecationReason = $resourceMetadata->getGraphqlAttribute($mutationName, 'deprecation_reason', '', true);
$deprecationReason = $resourceMetadata->getGraphqlAttribute($mutationName, 'deprecation_reason', null, true);

if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, $resourceType, $resourceClass, false, null, $mutationName, null)) {
$fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, null, $deprecationReason, $resourceType, $resourceClass, true, null, $mutationName, null)];
Expand All @@ -157,7 +157,7 @@ public function getSubscriptionFields(string $resourceClass, ResourceMetadata $r
$shortName = $resourceMetadata->getShortName();
$resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass);
$description = $resourceMetadata->getGraphqlAttribute($subscriptionName, 'description', "Subscribes to the $subscriptionName event of a $shortName.", false);
$deprecationReason = $resourceMetadata->getGraphqlAttribute($subscriptionName, 'deprecation_reason', '', true);
$deprecationReason = $resourceMetadata->getGraphqlAttribute($subscriptionName, 'deprecation_reason', null, true);

if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, $resourceType, $resourceClass, false, null, null, $subscriptionName)) {
$fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, null, $deprecationReason, $resourceType, $resourceClass, true, null, null, $subscriptionName)];
Expand Down Expand Up @@ -226,7 +226,7 @@ public function getResourceObjectTypeFields(?string $resourceClass, ResourceMeta
continue;
}

if ($fieldConfiguration = $this->getResourceFieldConfiguration($property, $propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $resourceClass, $input, $queryName, $mutationName, $subscriptionName, $depth)) {
if ($fieldConfiguration = $this->getResourceFieldConfiguration($property, $propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', null), $propertyType, $resourceClass, $input, $queryName, $mutationName, $subscriptionName, $depth)) {
$fields['id' === $property ? '_id' : $this->normalizePropertyName($property, $resourceClass)] = $fieldConfiguration;
}
}
Expand Down Expand Up @@ -260,7 +260,7 @@ public function resolveResourceArgs(array $args, string $operationName, string $
*
* @see http://webonyx.github.io/graphql-php/type-system/object-types/
*/
private function getResourceFieldConfiguration(?string $property, ?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, int $depth = 0): ?array
private function getResourceFieldConfiguration(?string $property, ?string $fieldDescription, ?string $deprecationReason, Type $type, string $rootResource, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, int $depth = 0): ?array
{
try {
$resourceClass = $this->typeBuilder->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();
Expand Down
11 changes: 7 additions & 4 deletions src/HttpCache/EventListener/AddHeadersListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,15 @@ public function onKernelResponse(ResponseEvent $event): void
$response->setVary(array_diff($this->vary, $response->getVary()), false);
}

if (null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $this->sharedMaxAge) && !$response->headers->hasCacheControlDirective('s-maxage')) {
$response->setSharedMaxAge($sharedMaxAge);
// if the public-property is defined and not yet set; apply it to the response
$public = ($resourceCacheHeaders['public'] ?? $this->public);
if (null !== $public && !$response->headers->hasCacheControlDirective('public')) {
$public ? $response->setPublic() : $response->setPrivate();
}

if (null !== $this->public && !$response->headers->hasCacheControlDirective('public')) {
$this->public ? $response->setPublic() : $response->setPrivate();
// Cache-Control "s-maxage" is only relevant is resource is not marked as "private"
if (false !== $public && null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $this->sharedMaxAge) && !$response->headers->hasCacheControlDirective('s-maxage')) {
$response->setSharedMaxAge($sharedMaxAge);
}

if (null !== ($staleWhileRevalidate = $resourceCacheHeaders['stale_while_revalidate'] ?? $this->staleWhileRevalidate) && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) {
Expand Down
2 changes: 1 addition & 1 deletion src/Metadata/Extractor/AbstractExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ protected function resolve($value)
return (string) $resolved;
}

throw new\ RuntimeException(sprintf('The container parameter "%s", used in the resource configuration value "%s", must be a string or numeric, but it is of type %s.', $parameter, $value, \gettype($resolved)));
throw new \RuntimeException(sprintf('The container parameter "%s", used in the resource configuration value "%s", must be a string or numeric, but it is of type %s.', $parameter, $value, \gettype($resolved)));
}, $value);

return str_replace('%%', '%', $escapedValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,11 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
$containerBuilderProphecy->getDefinition('api_platform.graphql.subscription.mercure_iri_generator')->willReturn($definitionDummy);
$this->childDefinitionProphecy->setPublic(true)->will(function () {});

$containerBuilderProphecy->getDefinition(Argument::type('string'))
->willReturn($this->prophesize(Definition::class)->reveal());
$containerBuilderProphecy->getAlias(Argument::type('string'))
->willReturn($this->prophesize(Alias::class)->reveal());

$containerBuilderProphecy->getDefinition(Argument::type('string'))
->willReturn($this->prophesize(Definition::class)->reveal());
$containerBuilderProphecy->getAlias(Argument::type('string'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface;
use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
Expand Down Expand Up @@ -77,7 +78,7 @@ public function parseValue($value)
/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if ($valueNode instanceof StringValueNode && false !== \DateTime::createFromFormat(\DateTime::ATOM, $valueNode->value)) {
return $valueNode->value;
Expand Down
3 changes: 2 additions & 1 deletion tests/GraphQl/Action/EntrypointActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ApiPlatform\Core\GraphQl\Serializer\Exception\ErrorNormalizer;
use ApiPlatform\Core\GraphQl\Serializer\Exception\HttpExceptionNormalizer;
use ApiPlatform\Core\GraphQl\Type\SchemaBuilderInterface;
use GraphQL\Error\DebugFlag;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -239,7 +240,7 @@ private function getEntrypointAction(array $variables = ['graphqlVariable']): En
$errorHandler = new ErrorHandler();

$executionResultProphecy = $this->prophesize(ExecutionResult::class);
$executionResultProphecy->toArray(false)->willReturn(['GraphQL']);
$executionResultProphecy->toArray(DebugFlag::NONE)->willReturn(['GraphQL']);
$executionResultProphecy->setErrorFormatter([$normalizer, 'normalize'])->willReturn($executionResultProphecy);
$executionResultProphecy->setErrorsHandler($errorHandler)->willReturn($executionResultProphecy);
$executorProphecy = $this->prophesize(ExecutorInterface::class);
Expand Down
Loading

0 comments on commit 40be41f

Please sign in to comment.