diff --git a/src/JsonApi/JsonSchema/SchemaFactory.php b/src/JsonApi/JsonSchema/SchemaFactory.php index 2baa10512e8..eccfb67d3db 100644 --- a/src/JsonApi/JsonSchema/SchemaFactory.php +++ b/src/JsonApi/JsonSchema/SchemaFactory.php @@ -258,7 +258,6 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin unset($schema['type']); $properties = $this->buildDefinitionPropertiesSchema($key, $className, $format, $type, $operation, $schema, []); - $properties['data']['properties']['attributes']['$ref'] = $prefix.$key; $properties['data'] = [ 'type' => 'array', @@ -347,9 +346,8 @@ private function buildDefinitionPropertiesSchema(string $key, string $className, $attributes[$propertyName] = $property; } - $currentRef = $this->getSchemaUriPrefix($schema->getVersion()).$schema->getRootDefinitionKey(); $replacement = self::PROPERTY_PROPS; - $replacement['attributes'] = ['$ref' => $currentRef]; + $replacement['attributes']['properties'] = $attributes; $included = []; if (\count($relationships) > 0) { diff --git a/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php b/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php index d588075cd02..5c647242691 100644 --- a/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php +++ b/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php @@ -15,9 +15,11 @@ use ApiPlatform\JsonApi\JsonSchema\SchemaFactory; use ApiPlatform\JsonApi\Tests\Fixtures\Dummy; +use ApiPlatform\JsonApi\Tests\Fixtures\RelatedDummy; use ApiPlatform\JsonSchema\DefinitionNameFactory; use ApiPlatform\JsonSchema\Schema; use ApiPlatform\JsonSchema\SchemaFactory as BaseSchemaFactory; +use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; @@ -31,7 +33,9 @@ use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Metadata\ResourceClassResolverInterface; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\TypeInfo\Type; class SchemaFactoryTest extends TestCase { @@ -112,7 +116,8 @@ public function testHasRootDefinitionKeyBuildSchema(): void 'type' => 'string', ], 'attributes' => [ - '$ref' => '#/definitions/Dummy', + 'type' => 'object', + 'properties' => [], ], ], 'required' => [ @@ -155,7 +160,8 @@ public function testSchemaTypeBuildSchema(): void $this->assertArrayHasKey('data', $objectSchema['properties']); $this->assertArrayHasKey('items', $objectSchema['properties']['data']); - $this->assertArrayHasKey('$ref', $objectSchema['properties']['data']['items']['properties']['attributes']); + $this->assertArrayNotHasKey('$ref', $objectSchema['properties']['data']['items']['properties']['attributes']); + $this->assertSame('object', $objectSchema['properties']['data']['items']['properties']['attributes']['type']); $properties = $objectSchema['properties']; $this->assertArrayHasKey('data', $properties); @@ -199,4 +205,89 @@ public function testPostOutputSchemaRequiresId(): void $data = $definitions[$rootDefinitionKey]['properties']['data']; $this->assertSame(['type', 'id'], $data['required']); } + + public function testRelationIsExcludedFromAttributes(): void + { + $schemaFactory = $this->buildSchemaFactoryWithRelation(); + $resultSchema = $schemaFactory->buildSchema(Dummy::class, 'jsonapi'); + + $definitions = $resultSchema->getDefinitions(); + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + $dataProperties = $definitions[$rootDefinitionKey]['properties']['data']['properties']; + + $this->assertArrayHasKey('attributes', $dataProperties); + $this->assertArrayNotHasKey('$ref', $dataProperties['attributes']); + $this->assertSame('object', $dataProperties['attributes']['type']); + + $attributes = $dataProperties['attributes']['properties']; + $this->assertArrayHasKey('name', $attributes); + $this->assertArrayNotHasKey('relatedDummy', $attributes, 'relations must not be documented as attributes'); + + $this->assertArrayHasKey('_id', $attributes, 'id is exposed as _id in the JSON:API attributes'); + $this->assertArrayNotHasKey('id', $attributes); + + $this->assertArrayHasKey('relationships', $dataProperties); + $this->assertArrayHasKey('relatedDummy', $dataProperties['relationships']['properties']); + } + + private function buildSchemaFactoryWithRelation(): SchemaFactory + { + $dummyOperation = (new Get())->withName('get')->withShortName('Dummy'); + $relatedOperation = (new Get())->withName('get')->withShortName('RelatedDummy'); + + $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $resourceMetadataFactory->create(Dummy::class)->willReturn( + new ResourceMetadataCollection(Dummy::class, [ + (new ApiResource())->withOperations(new Operations(['get' => $dummyOperation])), + ]) + ); + $resourceMetadataFactory->create(RelatedDummy::class)->willReturn( + new ResourceMetadataCollection(RelatedDummy::class, [ + (new ApiResource())->withOperations(new Operations(['get' => $relatedOperation])), + ]) + ); + + $propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactory->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name', 'relatedDummy'])); + $propertyNameCollectionFactory->create(RelatedDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); + + $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactory->create(Dummy::class, 'id', Argument::type('array'))->willReturn( + (new ApiProperty())->withNativeType(Type::int())->withReadable(true)->withSchema(['type' => 'integer']) + ); + $propertyMetadataFactory->create(Dummy::class, 'name', Argument::type('array'))->willReturn( + (new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withSchema(['type' => 'string']) + ); + $propertyMetadataFactory->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn( + (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(true)->withSchema(['type' => Schema::UNKNOWN_TYPE]) + ); + $propertyMetadataFactory->create(RelatedDummy::class, 'id', Argument::type('array'))->willReturn( + (new ApiProperty())->withNativeType(Type::int())->withReadable(true)->withSchema(['type' => 'integer']) + ); + $propertyMetadataFactory->create(RelatedDummy::class, 'name', Argument::type('array'))->willReturn( + (new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withSchema(['type' => 'string']) + ); + + $resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolver->isResourceClass(Dummy::class)->willReturn(true); + $resourceClassResolver->isResourceClass(RelatedDummy::class)->willReturn(true); + + $definitionNameFactory = new DefinitionNameFactory(null); + + $baseSchemaFactory = new BaseSchemaFactory( + resourceMetadataFactory: $resourceMetadataFactory->reveal(), + propertyNameCollectionFactory: $propertyNameCollectionFactory->reveal(), + propertyMetadataFactory: $propertyMetadataFactory->reveal(), + resourceClassResolver: $resourceClassResolver->reveal(), + definitionNameFactory: $definitionNameFactory, + ); + + return new SchemaFactory( + schemaFactory: $baseSchemaFactory, + propertyMetadataFactory: $propertyMetadataFactory->reveal(), + resourceClassResolver: $resourceClassResolver->reveal(), + resourceMetadataFactory: $resourceMetadataFactory->reveal(), + definitionNameFactory: $definitionNameFactory, + ); + } } diff --git a/tests/Functional/JsonSchema/JsonApiJsonSchemaTest.php b/tests/Functional/JsonSchema/JsonApiJsonSchemaTest.php index a9c3f3533af..58601413036 100644 --- a/tests/Functional/JsonSchema/JsonApiJsonSchemaTest.php +++ b/tests/Functional/JsonSchema/JsonApiJsonSchemaTest.php @@ -59,28 +59,19 @@ public function testJsonApi(): void { $speciesSchema = $this->schemaFactory->buildSchema(Issue6317::class, 'jsonapi', Schema::TYPE_OUTPUT); $this->assertEquals('#/definitions/Issue6317.jsonapi', $speciesSchema['$ref']); - $this->assertEquals([ - 'properties' => [ - 'data' => [ - 'type' => 'object', - 'properties' => [ - 'id' => [ - 'type' => 'string', - ], - 'type' => [ - 'type' => 'string', - ], - 'attributes' => [ - '$ref' => '#/definitions/Issue6317', - ], - ], - 'required' => [ - 'type', - 'id', - ], - ], - ], - ], $speciesSchema['definitions']['Issue6317.jsonapi']); + $data = $speciesSchema['definitions']['Issue6317.jsonapi']['properties']['data']; + $this->assertSame('object', $data['type']); + $this->assertSame(['type' => 'string'], $data['properties']['id']); + $this->assertSame(['type' => 'string'], $data['properties']['type']); + $this->assertSame(['type', 'id'], $data['required']); + + $attributes = $data['properties']['attributes']; + $this->assertArrayNotHasKey('$ref', $attributes); + $this->assertSame('object', $attributes['type']); + $this->assertEqualsCanonicalizing( + ['name', 'value', '_id', 'ordinal', 'cardinal'], + array_keys($attributes['properties']) + ); } public function testJsonApiIncludesSchemaForQuestion(): void @@ -150,7 +141,7 @@ public function testJsonApiIncludesSchemaForSpecies(): void 'type' => 'string', ], 'attributes' => [ - '$ref' => '#/definitions/Species', + 'type' => 'object', ], ], 'required' => [