Skip to content

Commit 75c275c

Browse files
authored
fix(jsonapi): exclude relations from openapi attributes schema (#8313)
Fixes #8308
1 parent 8586a80 commit 75c275c

3 files changed

Lines changed: 108 additions & 28 deletions

File tree

src/JsonApi/JsonSchema/SchemaFactory.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin
258258
unset($schema['type']);
259259

260260
$properties = $this->buildDefinitionPropertiesSchema($key, $className, $format, $type, $operation, $schema, []);
261-
$properties['data']['properties']['attributes']['$ref'] = $prefix.$key;
262261

263262
$properties['data'] = [
264263
'type' => 'array',
@@ -347,9 +346,8 @@ private function buildDefinitionPropertiesSchema(string $key, string $className,
347346
$attributes[$propertyName] = $property;
348347
}
349348

350-
$currentRef = $this->getSchemaUriPrefix($schema->getVersion()).$schema->getRootDefinitionKey();
351349
$replacement = self::PROPERTY_PROPS;
352-
$replacement['attributes'] = ['$ref' => $currentRef];
350+
$replacement['attributes']['properties'] = $attributes;
353351

354352
$included = [];
355353
if (\count($relationships) > 0) {

src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515

1616
use ApiPlatform\JsonApi\JsonSchema\SchemaFactory;
1717
use ApiPlatform\JsonApi\Tests\Fixtures\Dummy;
18+
use ApiPlatform\JsonApi\Tests\Fixtures\RelatedDummy;
1819
use ApiPlatform\JsonSchema\DefinitionNameFactory;
1920
use ApiPlatform\JsonSchema\Schema;
2021
use ApiPlatform\JsonSchema\SchemaFactory as BaseSchemaFactory;
22+
use ApiPlatform\Metadata\ApiProperty;
2123
use ApiPlatform\Metadata\ApiResource;
2224
use ApiPlatform\Metadata\Get;
2325
use ApiPlatform\Metadata\GetCollection;
@@ -31,7 +33,9 @@
3133
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
3234
use ApiPlatform\Metadata\ResourceClassResolverInterface;
3335
use PHPUnit\Framework\TestCase;
36+
use Prophecy\Argument;
3437
use Prophecy\PhpUnit\ProphecyTrait;
38+
use Symfony\Component\TypeInfo\Type;
3539

3640
class SchemaFactoryTest extends TestCase
3741
{
@@ -112,7 +116,8 @@ public function testHasRootDefinitionKeyBuildSchema(): void
112116
'type' => 'string',
113117
],
114118
'attributes' => [
115-
'$ref' => '#/definitions/Dummy',
119+
'type' => 'object',
120+
'properties' => [],
116121
],
117122
],
118123
'required' => [
@@ -155,7 +160,8 @@ public function testSchemaTypeBuildSchema(): void
155160
$this->assertArrayHasKey('data', $objectSchema['properties']);
156161

157162
$this->assertArrayHasKey('items', $objectSchema['properties']['data']);
158-
$this->assertArrayHasKey('$ref', $objectSchema['properties']['data']['items']['properties']['attributes']);
163+
$this->assertArrayNotHasKey('$ref', $objectSchema['properties']['data']['items']['properties']['attributes']);
164+
$this->assertSame('object', $objectSchema['properties']['data']['items']['properties']['attributes']['type']);
159165

160166
$properties = $objectSchema['properties'];
161167
$this->assertArrayHasKey('data', $properties);
@@ -199,4 +205,89 @@ public function testPostOutputSchemaRequiresId(): void
199205
$data = $definitions[$rootDefinitionKey]['properties']['data'];
200206
$this->assertSame(['type', 'id'], $data['required']);
201207
}
208+
209+
public function testRelationIsExcludedFromAttributes(): void
210+
{
211+
$schemaFactory = $this->buildSchemaFactoryWithRelation();
212+
$resultSchema = $schemaFactory->buildSchema(Dummy::class, 'jsonapi');
213+
214+
$definitions = $resultSchema->getDefinitions();
215+
$rootDefinitionKey = $resultSchema->getRootDefinitionKey();
216+
$dataProperties = $definitions[$rootDefinitionKey]['properties']['data']['properties'];
217+
218+
$this->assertArrayHasKey('attributes', $dataProperties);
219+
$this->assertArrayNotHasKey('$ref', $dataProperties['attributes']);
220+
$this->assertSame('object', $dataProperties['attributes']['type']);
221+
222+
$attributes = $dataProperties['attributes']['properties'];
223+
$this->assertArrayHasKey('name', $attributes);
224+
$this->assertArrayNotHasKey('relatedDummy', $attributes, 'relations must not be documented as attributes');
225+
226+
$this->assertArrayHasKey('_id', $attributes, 'id is exposed as _id in the JSON:API attributes');
227+
$this->assertArrayNotHasKey('id', $attributes);
228+
229+
$this->assertArrayHasKey('relationships', $dataProperties);
230+
$this->assertArrayHasKey('relatedDummy', $dataProperties['relationships']['properties']);
231+
}
232+
233+
private function buildSchemaFactoryWithRelation(): SchemaFactory
234+
{
235+
$dummyOperation = (new Get())->withName('get')->withShortName('Dummy');
236+
$relatedOperation = (new Get())->withName('get')->withShortName('RelatedDummy');
237+
238+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
239+
$resourceMetadataFactory->create(Dummy::class)->willReturn(
240+
new ResourceMetadataCollection(Dummy::class, [
241+
(new ApiResource())->withOperations(new Operations(['get' => $dummyOperation])),
242+
])
243+
);
244+
$resourceMetadataFactory->create(RelatedDummy::class)->willReturn(
245+
new ResourceMetadataCollection(RelatedDummy::class, [
246+
(new ApiResource())->withOperations(new Operations(['get' => $relatedOperation])),
247+
])
248+
);
249+
250+
$propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
251+
$propertyNameCollectionFactory->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name', 'relatedDummy']));
252+
$propertyNameCollectionFactory->create(RelatedDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name']));
253+
254+
$propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
255+
$propertyMetadataFactory->create(Dummy::class, 'id', Argument::type('array'))->willReturn(
256+
(new ApiProperty())->withNativeType(Type::int())->withReadable(true)->withSchema(['type' => 'integer'])
257+
);
258+
$propertyMetadataFactory->create(Dummy::class, 'name', Argument::type('array'))->willReturn(
259+
(new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withSchema(['type' => 'string'])
260+
);
261+
$propertyMetadataFactory->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
262+
(new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(true)->withSchema(['type' => Schema::UNKNOWN_TYPE])
263+
);
264+
$propertyMetadataFactory->create(RelatedDummy::class, 'id', Argument::type('array'))->willReturn(
265+
(new ApiProperty())->withNativeType(Type::int())->withReadable(true)->withSchema(['type' => 'integer'])
266+
);
267+
$propertyMetadataFactory->create(RelatedDummy::class, 'name', Argument::type('array'))->willReturn(
268+
(new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withSchema(['type' => 'string'])
269+
);
270+
271+
$resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class);
272+
$resourceClassResolver->isResourceClass(Dummy::class)->willReturn(true);
273+
$resourceClassResolver->isResourceClass(RelatedDummy::class)->willReturn(true);
274+
275+
$definitionNameFactory = new DefinitionNameFactory(null);
276+
277+
$baseSchemaFactory = new BaseSchemaFactory(
278+
resourceMetadataFactory: $resourceMetadataFactory->reveal(),
279+
propertyNameCollectionFactory: $propertyNameCollectionFactory->reveal(),
280+
propertyMetadataFactory: $propertyMetadataFactory->reveal(),
281+
resourceClassResolver: $resourceClassResolver->reveal(),
282+
definitionNameFactory: $definitionNameFactory,
283+
);
284+
285+
return new SchemaFactory(
286+
schemaFactory: $baseSchemaFactory,
287+
propertyMetadataFactory: $propertyMetadataFactory->reveal(),
288+
resourceClassResolver: $resourceClassResolver->reveal(),
289+
resourceMetadataFactory: $resourceMetadataFactory->reveal(),
290+
definitionNameFactory: $definitionNameFactory,
291+
);
292+
}
202293
}

tests/Functional/JsonSchema/JsonApiJsonSchemaTest.php

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,28 +59,19 @@ public function testJsonApi(): void
5959
{
6060
$speciesSchema = $this->schemaFactory->buildSchema(Issue6317::class, 'jsonapi', Schema::TYPE_OUTPUT);
6161
$this->assertEquals('#/definitions/Issue6317.jsonapi', $speciesSchema['$ref']);
62-
$this->assertEquals([
63-
'properties' => [
64-
'data' => [
65-
'type' => 'object',
66-
'properties' => [
67-
'id' => [
68-
'type' => 'string',
69-
],
70-
'type' => [
71-
'type' => 'string',
72-
],
73-
'attributes' => [
74-
'$ref' => '#/definitions/Issue6317',
75-
],
76-
],
77-
'required' => [
78-
'type',
79-
'id',
80-
],
81-
],
82-
],
83-
], $speciesSchema['definitions']['Issue6317.jsonapi']);
62+
$data = $speciesSchema['definitions']['Issue6317.jsonapi']['properties']['data'];
63+
$this->assertSame('object', $data['type']);
64+
$this->assertSame(['type' => 'string'], $data['properties']['id']);
65+
$this->assertSame(['type' => 'string'], $data['properties']['type']);
66+
$this->assertSame(['type', 'id'], $data['required']);
67+
68+
$attributes = $data['properties']['attributes'];
69+
$this->assertArrayNotHasKey('$ref', $attributes);
70+
$this->assertSame('object', $attributes['type']);
71+
$this->assertEqualsCanonicalizing(
72+
['name', 'value', '_id', 'ordinal', 'cardinal'],
73+
array_keys($attributes['properties'])
74+
);
8475
}
8576

8677
public function testJsonApiIncludesSchemaForQuestion(): void
@@ -150,7 +141,7 @@ public function testJsonApiIncludesSchemaForSpecies(): void
150141
'type' => 'string',
151142
],
152143
'attributes' => [
153-
'$ref' => '#/definitions/Species',
144+
'type' => 'object',
154145
],
155146
],
156147
'required' => [

0 commit comments

Comments
 (0)