diff --git a/composer.json b/composer.json index cba3e87e751..300a0bbf3bc 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "friendsofsymfony/user-bundle": "^2.2@dev", "guzzlehttp/guzzle": "^6.0", "jangregor/phpstan-prophecy": "^0.4.2", - "justinrainbow/json-schema": "^5.0", + "justinrainbow/json-schema": "^5.2.1", "nelmio/api-doc-bundle": "^2.13.4", "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", "phpdocumentor/type-resolver": "^0.3 || ^0.4", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e1dfc6f5648..038f0e02fdc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -54,6 +54,9 @@ parameters: - message: '#Parameter \#1 \$docblock of method phpDocumentor\\Reflection\\DocBlockFactoryInterface::create\(\) expects string, ReflectionClass given\.#' path: %currentWorkingDirectory%/src/Metadata/Resource/Factory/PhpDocResourceMetadataFactory.php + - + message: '#Parameter \#1 \$objectValue of method GraphQL\\Type\\Definition\\InterfaceType::resolveType\(\) expects object, array()? given.#' + path: %currentWorkingDirectory%/tests/GraphQl/Type/TypeBuilderTest.php - message: '#Property ApiPlatform\\Core\\Test\\DoctrineMongoDbOdmFilterTestCase::\$repository \(Doctrine\\ODM\\MongoDB\\Repository\\DocumentRepository\) does not accept Doctrine\\ORM\\EntityRepository\.#' path: %currentWorkingDirectory%/src/Test/DoctrineMongoDbOdmFilterTestCase.php @@ -78,7 +81,10 @@ parameters: - message: '#Binary operation "\+" between (float\|int\|)?string and 0 results in an error\.#' path: %currentWorkingDirectory%/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php - - '#Parameter \#1 \$objectValue of method GraphQL\\Type\\Definition\\InterfaceType::resolveType\(\) expects object, array()? given.#' + # https://github.com/phpstan/phpstan-symfony/issues/27 + - + message: '#Service "api_platform\.json_schema\.schema_factory" is private\.#' + path: %currentWorkingDirectory%/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php # Expected, due to optional interfaces - '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryCollectionExtensionInterface::applyToCollection\(\) invoked with 5 parameters, 3-4 required\.#' diff --git a/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php b/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php index 97304347755..e52cade819c 100644 --- a/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php +++ b/src/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php @@ -13,9 +13,13 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Test; +use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubset; use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\MatchesJsonSchema; +use ApiPlatform\Core\JsonSchema\Schema; +use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; use PHPUnit\Framework\ExpectationFailedException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Contracts\HttpClient\ResponseInterface; /** @@ -28,9 +32,9 @@ trait ApiTestAssertionsTrait use BrowserKitAssertionsTrait; /** - * Asserts that the retrieved JSON contains has the specified subset. + * Asserts that the retrieved JSON contains the specified subset. * - * This method delegates to self::assertArraySubset(). + * This method delegates to static::assertArraySubset(). * * @param array|string $subset * @@ -91,18 +95,34 @@ public static function assertJsonEquals($json, string $message = ''): void public static function assertArraySubset($subset, $array, bool $checkForObjectIdentity = false, string $message = ''): void { $constraint = new ArraySubset($subset, $checkForObjectIdentity); + static::assertThat($array, $constraint, $message); } /** - * @param array|string $jsonSchema + * @param object|array|string $jsonSchema */ public static function assertMatchesJsonSchema($jsonSchema, ?int $checkMode = null, string $message = ''): void { $constraint = new MatchesJsonSchema($jsonSchema, $checkMode); + static::assertThat(self::getHttpResponse()->toArray(false), $constraint, $message); } + public static function assertMatchesResourceCollectionJsonSchema(string $resourceClass, ?string $operationName = null, string $format = 'jsonld'): void + { + $schema = self::getSchemaFactory()->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, OperationType::COLLECTION, $operationName); + + static::assertMatchesJsonSchema($schema); + } + + public static function assertMatchesResourceItemJsonSchema(string $resourceClass, ?string $operationName = null, string $format = 'jsonld'): void + { + $schema = self::getSchemaFactory()->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, OperationType::ITEM, $operationName); + + static::assertMatchesJsonSchema($schema); + } + private static function getHttpClient(Client $newClient = null): ?Client { static $client; @@ -126,4 +146,16 @@ private static function getHttpResponse(): ResponseInterface return $response; } + + private static function getSchemaFactory(): SchemaFactoryInterface + { + try { + /** @var SchemaFactoryInterface $schemaFactory */ + $schemaFactory = static::$container->get('api_platform.json_schema.schema_factory'); + } catch (ServiceNotFoundException $e) { + throw new \LogicException('You cannot use the resource JSON Schema assertions if the "api_platform.swagger.versions" config is null or empty.'); + } + + return $schemaFactory; + } } diff --git a/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php b/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php index eee5837667c..b7773b2a0c0 100644 --- a/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php +++ b/src/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php @@ -25,16 +25,19 @@ */ final class MatchesJsonSchema extends Constraint { + /** + * @var object|array + */ private $schema; private $checkMode; /** - * @param array|string $schema + * @param object|array|string $schema */ public function __construct($schema, ?int $checkMode = null) { + $this->schema = \is_string($schema) ? json_decode($schema) : $schema; $this->checkMode = $checkMode; - $this->schema = \is_array($schema) ? (object) $schema : json_decode($schema); } /** @@ -46,15 +49,15 @@ public function toString(): string } /** - * @param array $other + * {@inheritdoc} */ protected function matches($other): bool { if (!class_exists(Validator::class)) { - throw new \RuntimeException('The "justinrainbow/json-schema" library must be installed to use "assertMatchesJsonSchema()". Try running "composer require --dev justinrainbow/json-schema".'); + throw new \LogicException('The "justinrainbow/json-schema" library must be installed to use "assertMatchesJsonSchema()". Try running "composer require --dev justinrainbow/json-schema".'); } - $other = (object) $other; + $other = $this->normalizeJson($other); $validator = new Validator(); $validator->validate($other, $this->schema, $this->checkMode); @@ -63,14 +66,14 @@ protected function matches($other): bool } /** - * @param object $other + * {@inheritdoc} */ protected function additionalFailureDescription($other): string { - $other = (object) $other; + $other = $this->normalizeJson($other); $validator = new Validator(); - $validator->check($other, $this->schema); + $validator->validate($other, $this->schema, $this->checkMode); $errors = []; foreach ($validator->getErrors() as $error) { @@ -80,4 +83,32 @@ protected function additionalFailureDescription($other): string return implode("\n", $errors); } + + /** + * Normalizes a JSON document. + * + * Specifically, we should ensure that: + * 1. a JSON object is represented as a PHP object, not as an associative array. + */ + private function normalizeJson($document) + { + if (is_scalar($document) || \is_object($document)) { + return $document; + } + + if (!\is_array($document)) { + throw new \InvalidArgumentException('Document must be scalar, array or object.'); + } + + $document = json_encode($document); + if (!\is_string($document)) { + throw new \UnexpectedValueException('JSON encode failed.'); + } + $document = json_decode($document); + if (!\is_array($document) && !\is_object($document)) { + throw new \UnexpectedValueException('JSON decode failed.'); + } + + return $document; + } } diff --git a/src/Hydra/JsonSchema/SchemaFactory.php b/src/Hydra/JsonSchema/SchemaFactory.php index 2400daf3364..adfcad521f2 100644 --- a/src/Hydra/JsonSchema/SchemaFactory.php +++ b/src/Hydra/JsonSchema/SchemaFactory.php @@ -47,9 +47,9 @@ public function __construct(BaseSchemaFactory $schemaFactory) /** * {@inheritdoc} */ - public function buildSchema(string $resourceClass, string $format = 'jsonld', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema + public function buildSchema(string $resourceClass, string $format = 'jsonld', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema { - $schema = $this->schemaFactory->buildSchema($resourceClass, $format, $output, $operationType, $operationName, $schema, $serializerContext, $forceCollection); + $schema = $this->schemaFactory->buildSchema($resourceClass, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); if ('jsonld' !== $format) { return $schema; } @@ -74,24 +74,29 @@ public function buildSchema(string $resourceClass, string $format = 'jsonld', bo ], 'hydra:totalItems' => [ 'type' => 'integer', - 'minimum' => 1, + 'minimum' => 0, ], 'hydra:view' => [ 'type' => 'object', 'properties' => [ - '@id' => ['type' => 'string'], - '@type' => ['type' => 'string'], + '@id' => [ + 'type' => 'string', + 'format' => 'iri-reference', + ], + '@type' => [ + 'type' => 'string', + ], 'hydra:first' => [ - 'type' => 'integer', - 'minimum' => 1, + 'type' => 'string', + 'format' => 'iri-reference', ], 'hydra:last' => [ - 'type' => 'integer', - 'minimum' => 1, + 'type' => 'string', + 'format' => 'iri-reference', ], 'hydra:next' => [ - 'type' => 'integer', - 'minimum' => 1, + 'type' => 'string', + 'format' => 'iri-reference', ], ], ], @@ -116,6 +121,9 @@ public function buildSchema(string $resourceClass, string $format = 'jsonld', bo ], ], ]; + $schema['required'] = [ + 'hydra:member', + ]; return $schema; } diff --git a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php index e930854d186..c322da6ac9b 100644 --- a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php +++ b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\JsonSchema\Command; use ApiPlatform\Core\Api\OperationType; +use ApiPlatform\Core\JsonSchema\Schema; use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidOptionException; @@ -53,7 +54,7 @@ protected function configure() ->addOption('itemOperation', null, InputOption::VALUE_REQUIRED, 'The item operation') ->addOption('collectionOperation', null, InputOption::VALUE_REQUIRED, 'The collection operation') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The response format', (string) $this->formats[0]) - ->addOption('type', null, InputOption::VALUE_REQUIRED, 'The type of schema to generate (input or output)', 'input'); + ->addOption('type', null, InputOption::VALUE_REQUIRED, sprintf('The type of schema to generate (%s or %s)', Schema::TYPE_INPUT, Schema::TYPE_OUTPUT), Schema::TYPE_INPUT); } /** @@ -71,11 +72,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $collectionOperation = $input->getOption('collectionOperation'); /** @var string $format */ $format = $input->getOption('format'); - /** @var string $outputType */ - $outputType = $input->getOption('type'); + /** @var string $type */ + $type = $input->getOption('type'); - if (!\in_array($outputType, ['input', 'output'], true)) { - $io->error('You can only use "input" or "output" for the "type" option'); + if (!\in_array($type, [Schema::TYPE_INPUT, Schema::TYPE_OUTPUT], true)) { + $io->error(sprintf('You can only use "%s" or "%s" for the "type" option', Schema::TYPE_INPUT, Schema::TYPE_OUTPUT)); return 1; } @@ -100,10 +101,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $operationName = $itemOperation ?? $collectionOperation; } - $schema = $this->schemaFactory->buildSchema($resource, $format, 'output' === $outputType, $operationType, $operationName); + $schema = $this->schemaFactory->buildSchema($resource, $format, $type, $operationType, $operationName); if (null !== $operationType && null !== $operationName && !$schema->isDefined()) { - $io->error(sprintf('There is no %ss defined for the operation "%s" of the resource "%s".', $outputType, $operationName, $resource)); + $io->error(sprintf('There is no %s defined for the operation "%s" of the resource "%s".', $type, $operationName, $resource)); return 1; } diff --git a/src/JsonSchema/Schema.php b/src/JsonSchema/Schema.php index 0bc333094e6..29a9add5ba8 100644 --- a/src/JsonSchema/Schema.php +++ b/src/JsonSchema/Schema.php @@ -27,9 +27,11 @@ */ final class Schema extends \ArrayObject { + public const TYPE_INPUT = 'input'; + public const TYPE_OUTPUT = 'output'; public const VERSION_JSON_SCHEMA = 'json-schema'; - public const VERSION_SWAGGER = 'swagger'; public const VERSION_OPENAPI = 'openapi'; + public const VERSION_SWAGGER = 'swagger'; private $version; @@ -49,6 +51,8 @@ public function getVersion(): string } /** + * {@inheritdoc} + * * @param bool $includeDefinitions if set to false, definitions will not be included in the resulting array */ public function getArrayCopy(bool $includeDefinitions = true): array diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index 923f0bb145d..addfe1e5d87 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -62,20 +62,20 @@ public function addDistinctFormat(string $format): void /** * {@inheritdoc} */ - public function buildSchema(string $resourceClass, string $format = 'json', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema + public function buildSchema(string $resourceClass, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema { $schema = $schema ?? new Schema(); - if (null === $metadata = $this->getMetadata($resourceClass, $output, $operationType, $operationName, $serializerContext)) { + if (null === $metadata = $this->getMetadata($resourceClass, $type, $operationType, $operationName, $serializerContext)) { return $schema; } [$resourceMetadata, $serializerContext, $inputOrOutputClass] = $metadata; $version = $schema->getVersion(); - $definitionName = $this->buildDefinitionName($resourceClass, $format, $output, $operationType, $operationName, $serializerContext); + $definitionName = $this->buildDefinitionName($resourceClass, $format, $type, $operationType, $operationName, $serializerContext); $method = (null !== $operationType && null !== $operationName) ? $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method') : 'GET'; - if (!$output && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) { + if (Schema::TYPE_OUTPUT !== $type && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) { return $schema; } @@ -196,9 +196,9 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str $schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema; } - private function buildDefinitionName(string $resourceClass, string $format = 'json', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?array $serializerContext = null): string + private function buildDefinitionName(string $resourceClass, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?array $serializerContext = null): string { - [$resourceMetadata, $serializerContext, $inputOrOutputClass] = $this->getMetadata($resourceClass, $output, $operationType, $operationName, $serializerContext); + [$resourceMetadata, $serializerContext, $inputOrOutputClass] = $this->getMetadata($resourceClass, $type, $operationType, $operationName, $serializerContext); $prefix = $resourceMetadata->getShortName(); if (null !== $inputOrOutputClass && $resourceClass !== $inputOrOutputClass) { @@ -220,10 +220,10 @@ private function buildDefinitionName(string $resourceClass, string $format = 'js return $name; } - private function getMetadata(string $resourceClass, bool $output, ?string $operationType, ?string $operationName, ?array $serializerContext): ?array + private function getMetadata(string $resourceClass, string $type = Schema::TYPE_OUTPUT, ?string $operationType, ?string $operationName, ?array $serializerContext): ?array { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $output ? 'output' : 'input'; + $attribute = Schema::TYPE_OUTPUT === $type ? 'output' : 'input'; if (null === $operationType || null === $operationName) { $inputOrOutput = $resourceMetadata->getAttribute($attribute, ['class' => $resourceClass]); } else { @@ -237,14 +237,14 @@ private function getMetadata(string $resourceClass, bool $output, ?string $opera return [ $resourceMetadata, - $serializerContext ?? $this->getSerializerContext($resourceMetadata, $output, $operationType, $operationName), + $serializerContext ?? $this->getSerializerContext($resourceMetadata, $type, $operationType, $operationName), $inputOrOutput['class'], ]; } - private function getSerializerContext(ResourceMetadata $resourceMetadata, bool $output, ?string $operationType, ?string $operationName): array + private function getSerializerContext(ResourceMetadata $resourceMetadata, string $type = Schema::TYPE_OUTPUT, ?string $operationType, ?string $operationName): array { - $attribute = $output ? 'normalization_context' : 'denormalization_context'; + $attribute = Schema::TYPE_OUTPUT === $type ? 'normalization_context' : 'denormalization_context'; if (null === $operationType || null === $operationName) { return $resourceMetadata->getAttribute($attribute, []); diff --git a/src/JsonSchema/SchemaFactoryInterface.php b/src/JsonSchema/SchemaFactoryInterface.php index fcae80c4a7c..3f43c58ac86 100644 --- a/src/JsonSchema/SchemaFactoryInterface.php +++ b/src/JsonSchema/SchemaFactoryInterface.php @@ -27,5 +27,5 @@ interface SchemaFactoryInterface /** * @throws ResourceClassNotFoundException */ - public function buildSchema(string $resourceClass, string $format = 'json', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema; + public function buildSchema(string $resourceClass, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema; } diff --git a/src/JsonSchema/TypeFactory.php b/src/JsonSchema/TypeFactory.php index e69b94d5617..77dee1d0e63 100644 --- a/src/JsonSchema/TypeFactory.php +++ b/src/JsonSchema/TypeFactory.php @@ -82,12 +82,9 @@ private function getClassType(?string $className, string $format = 'json', ?bool $version = $schema->getVersion(); $subSchema = new Schema($version); - /* - * @var Schema $schema Prevents a false positive in PHPStan - */ $subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema - $this->schemaFactory->buildSchema($className, $format, true, null, null, $subSchema, $serializerContext); + $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, null, $subSchema, $serializerContext); return ['$ref' => $subSchema['$ref']]; } diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index 6708ea7642d..b7e2319e506 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -288,10 +288,10 @@ private function getPathOperation(bool $v3, string $operationName, array $operat /** * @return array the update message as first value, and if the schema is defined as second */ - private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, string $resourceClass, string $operationType, string $operationName, array $mimeTypes, bool $output = true, bool $forceCollection = false): array + private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, string $resourceClass, string $operationType, string $operationName, array $mimeTypes, string $type = Schema::TYPE_OUTPUT, bool $forceCollection = false): array { if (!$v3) { - $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $output, $operationType, $operationName, 'json', null, $forceCollection); + $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, 'json', null, $forceCollection); if (!$jsonSchema->isDefined()) { return [$message, false]; } @@ -302,7 +302,7 @@ private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, } foreach ($mimeTypes as $mimeType => $format) { - $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $output, $operationType, $operationName, $format, null, $forceCollection); + $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, $format, null, $forceCollection); if (!$jsonSchema->isDefined()) { return [$message, false]; } @@ -437,7 +437,7 @@ private function addSubresourceOperation(bool $v3, array $subresourceOperation, $successResponse = [ 'description' => sprintf('%s %s response', $subresourceOperation['shortNames'][0], $collection ? 'collection' : 'resource'), ]; - [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $subresourceOperation['resource_class'], OperationType::SUBRESOURCE, $operationName, $mimeTypes, true, $collection); + [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $subresourceOperation['resource_class'], OperationType::SUBRESOURCE, $operationName, $mimeTypes, Schema::TYPE_OUTPUT, $collection); $pathOperation['responses'] = ['200' => $successResponse, '404' => ['description' => 'Resource not found']]; @@ -525,7 +525,7 @@ private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObj return $pathOperation; } - [$message, $defined] = $this->addSchemas($v3, [], $definitions, $resourceClass, $operationType, $operationName, $requestMimeTypes, false); + [$message, $defined] = $this->addSchemas($v3, [], $definitions, $resourceClass, $operationType, $operationName, $requestMimeTypes, Schema::TYPE_INPUT); if (!$defined) { return $pathOperation; } @@ -570,12 +570,12 @@ private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperatio return $pathOperation; } - private function getJsonSchema(bool $v3, \ArrayObject $definitions, string $resourceClass, bool $output, ?string $operationType, ?string $operationName, string $format = 'json', ?array $serializerContext = null, bool $forceCollection = false): Schema + private function getJsonSchema(bool $v3, \ArrayObject $definitions, string $resourceClass, string $type, ?string $operationType, ?string $operationName, string $format = 'json', ?array $serializerContext = null, bool $forceCollection = false): Schema { $schema = new Schema($v3 ? Schema::VERSION_OPENAPI : Schema::VERSION_SWAGGER); $schema->setDefinitions($definitions); - $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $output, $operationType, $operationName, $schema, $serializerContext, $forceCollection); + $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); return $schema; } diff --git a/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php b/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php index 8fe4b94f0c6..30c565f1f05 100644 --- a/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php +++ b/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Symfony\Bundle\Test; use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Model\ResourceInterface; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Runner\Version; @@ -120,6 +121,18 @@ public function testAssertMatchesJsonSchema(): void $this->assertMatchesJsonSchema(json_decode($jsonSchema, true)); } + public function testAssertMatchesResourceCollectionJsonSchema(): void + { + self::createClient()->request('GET', '/resource_interfaces'); + $this->assertMatchesResourceCollectionJsonSchema(ResourceInterface::class); + } + + public function testAssertMatchesResourceItemJsonSchema(): void + { + self::createClient()->request('GET', '/resource_interfaces/some-id'); + $this->assertMatchesResourceItemJsonSchema(ResourceInterface::class); + } + // Next tests have been imported from dms/phpunit-arraysubset-asserts, because the original constraint has been deprecated. public function testAssertArraySubsetPassesStrictConfig(): void diff --git a/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php b/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php index c9e9522d31e..8e677953500 100644 --- a/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php +++ b/tests/Fixtures/TestBundle/DataProvider/SerializableItemDataProvider.php @@ -14,16 +14,16 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\DataProvider; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; +use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderInterface; use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderTrait; -use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\SerializableResource as SerializableResourceDocument; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SerializableResource; /** * @author Vincent Chalamon */ -class SerializableItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface +class SerializableItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface, SerializerAwareDataProviderInterface { use SerializerAwareDataProviderTrait; @@ -32,10 +32,6 @@ class SerializableItemDataProvider implements ItemDataProviderInterface, Seriali */ public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) { - if (!\in_array($resourceClass, [SerializableResource::class, SerializableResourceDocument::class], true)) { - throw new ResourceClassNotSupportedException(); - } - return $this->getSerializer()->deserialize(<<<'JSON' { "id": 1, @@ -45,4 +41,12 @@ public function getItem(string $resourceClass, $id, string $operationName = null JSON , $resourceClass, 'json'); } + + /** + * {@inheritdoc} + */ + public function supports(string $resourceClass, string $operationName = null, array $context = []): bool + { + return \in_array($resourceClass, [SerializableResource::class, SerializableResourceDocument::class], true); + } } diff --git a/tests/Hydra/JsonSchema/SchemaFactoryTest.php b/tests/Hydra/JsonSchema/SchemaFactoryTest.php index 545901f2076..e3ecfbbfd28 100644 --- a/tests/Hydra/JsonSchema/SchemaFactoryTest.php +++ b/tests/Hydra/JsonSchema/SchemaFactoryTest.php @@ -15,6 +15,7 @@ use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory; +use ApiPlatform\Core\JsonSchema\Schema; use ApiPlatform\Core\JsonSchema\SchemaFactory as BaseSchemaFactory; use ApiPlatform\Core\JsonSchema\TypeFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; @@ -77,7 +78,7 @@ public function testHasRootDefinitionKeyBuildSchema(): void public function testSchemaTypeBuildSchema(): void { - $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', true, OperationType::COLLECTION); + $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, OperationType::COLLECTION); $this->assertNull($resultSchema->getRootDefinitionKey()); $this->assertArrayHasKey('properties', $resultSchema); @@ -86,7 +87,7 @@ public function testSchemaTypeBuildSchema(): void $this->assertArrayHasKey('hydra:view', $resultSchema['properties']); $this->assertArrayHasKey('hydra:search', $resultSchema['properties']); - $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', true, null, null, null, null, true); + $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, null, null, null, null, true); $this->assertNull($resultSchema->getRootDefinitionKey()); $this->assertArrayHasKey('properties', $resultSchema); diff --git a/tests/JsonSchema/TypeFactoryTest.php b/tests/JsonSchema/TypeFactoryTest.php index 653bc56c226..3afb48a0419 100644 --- a/tests/JsonSchema/TypeFactoryTest.php +++ b/tests/JsonSchema/TypeFactoryTest.php @@ -47,7 +47,7 @@ public function testGetClassType(): void { $schemaFactoryProphecy = $this->prophesize(SchemaFactoryInterface::class); - $schemaFactoryProphecy->buildSchema(Dummy::class, 'jsonld', true, null, null, Argument::type(Schema::class), ['foo' => 'bar'])->will(function (array $args) { + $schemaFactoryProphecy->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, null, null, Argument::type(Schema::class), ['foo' => 'bar'])->will(function (array $args) { $args[5]['$ref'] = 'ref'; return $args[5];