diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b89af2652..440b5515c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2.6.5 +* JsonSchema: Update Hydra `@context` property possible types * Filter validation: Fix issue in Required filter validator with dot notation (#4221) * OpenAPI: Fix notice/warning for `response` without `content` in the `openapi_context` (#4210) * OpenAPI: Do not use output for request body (#4213) diff --git a/src/Hydra/JsonSchema/SchemaFactory.php b/src/Hydra/JsonSchema/SchemaFactory.php index c3f82538df2..a3c95897509 100644 --- a/src/Hydra/JsonSchema/SchemaFactory.php +++ b/src/Hydra/JsonSchema/SchemaFactory.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Hydra\JsonSchema; +use ApiPlatform\Core\JsonLd\ContextBuilder; use ApiPlatform\Core\JsonSchema\Schema; use ApiPlatform\Core\JsonSchema\SchemaFactory as BaseSchemaFactory; use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; @@ -35,7 +36,26 @@ final class SchemaFactory implements SchemaFactoryInterface '@type' => self::BASE_PROP, ]; private const BASE_ROOT_PROPS = [ - '@context' => self::BASE_PROP, + '@context' => [ + 'readOnly' => true, + 'oneOf' => [ + ['type' => 'string'], + [ + 'type' => 'object', + 'properties' => [ + '@vocab' => [ + 'type' => 'string', + ], + 'hydra' => [ + 'type' => 'string', + 'enum' => [ContextBuilder::HYDRA_NS], + ], + ], + 'required' => ['@vocab', 'hydra'], + 'additionalProperties' => true, + ], + ], + ], ] + self::BASE_PROPS; private $schemaFactory; diff --git a/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php b/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php index 7d8e3c37ecb..12fa9c00296 100644 --- a/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php +++ b/tests/Bridge/Symfony/Bundle/Test/ApiTestCaseTest.php @@ -16,6 +16,7 @@ use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Dummy as DummyDocument; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyDtoInputOutput; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Model\ResourceInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\SchemaTool; @@ -137,6 +138,21 @@ public function testAssertMatchesResourceItemJsonSchema(): void $this->assertMatchesResourceItemJsonSchema(ResourceInterface::class); } + public function testAssertMatchesResourceItemJsonSchemaOutput(): void + { + $this->recreateSchema(); + + /** @var EntityManagerInterface $manager */ + $manager = self::$container->get('doctrine')->getManager(); + $dummyDtoInputOutput = new DummyDtoInputOutput(); + $dummyDtoInputOutput->str = 'lorem'; + $dummyDtoInputOutput->num = 54; + $manager->persist($dummyDtoInputOutput); + $manager->flush(); + self::createClient()->request('GET', '/dummy_dto_input_outputs/1'); + $this->assertMatchesResourceItemJsonSchema(DummyDtoInputOutput::class); + } + // Next tests have been imported from dms/phpunit-arraysubset-asserts, because the original constraint has been deprecated. public function testAssertArraySubsetPassesStrictConfig(): void @@ -160,17 +176,7 @@ public function testAssertArraySubsetDoesNothingForValidScenario(): void public function testFindIriBy(): void { - self::bootKernel(); - /** - * @var EntityManagerInterface - */ - $manager = self::$container->get('doctrine')->getManager(); - /** @var \Doctrine\ORM\Mapping\ClassMetadata[] $classes */ - $classes = $manager->getMetadataFactory()->getAllMetadata(); - $schemaTool = new SchemaTool($manager); - - $schemaTool->dropSchema($classes); - $schemaTool->createSchema($classes); + $this->recreateSchema(); self::createClient()->request('POST', '/dummies', [ 'headers' => [ @@ -185,4 +191,18 @@ public function testFindIriBy(): void $this->assertMatchesRegularExpression('~^/dummies/\d+~', self::findIriBy($resource, ['name' => 'Kevin'])); $this->assertNull(self::findIriBy($resource, ['name' => 'not-exist'])); } + + private function recreateSchema(): void + { + self::bootKernel(); + + /** @var EntityManagerInterface $manager */ + $manager = self::$container->get('doctrine')->getManager(); + /** @var \Doctrine\ORM\Mapping\ClassMetadata[] $classes */ + $classes = $manager->getMetadataFactory()->getAllMetadata(); + $schemaTool = new SchemaTool($manager); + + $schemaTool->dropSchema($classes); + $schemaTool->createSchema($classes); + } } diff --git a/tests/Hydra/JsonSchema/SchemaFactoryTest.php b/tests/Hydra/JsonSchema/SchemaFactoryTest.php index 5c29c60ae6b..598776381d5 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\JsonLd\ContextBuilder; use ApiPlatform\Core\JsonSchema\Schema; use ApiPlatform\Core\JsonSchema\SchemaFactory as BaseSchemaFactory; use ApiPlatform\Core\JsonSchema\TypeFactoryInterface; @@ -79,6 +80,29 @@ public function testHasRootDefinitionKeyBuildSchema(): void $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]); $properties = $resultSchema['definitions'][$rootDefinitionKey]['properties']; $this->assertArrayHasKey('@context', $properties); + $this->assertSame( + [ + 'readOnly' => true, + 'oneOf' => [ + ['type' => 'string'], + [ + 'type' => 'object', + 'properties' => [ + '@vocab' => [ + 'type' => 'string', + ], + 'hydra' => [ + 'type' => 'string', + 'enum' => [ContextBuilder::HYDRA_NS], + ], + ], + 'required' => ['@vocab', 'hydra'], + 'additionalProperties' => true, + ], + ], + ], + $properties['@context'] + ); $this->assertArrayHasKey('@type', $properties); $this->assertArrayHasKey('@id', $properties); }