Skip to content

Commit 5908915

Browse files
committed
fix(json-schema): share invariable sub-schemas
1 parent 51f64ed commit 5908915

File tree

12 files changed

+94
-137
lines changed

12 files changed

+94
-137
lines changed

src/Hydra/JsonSchema/SchemaFactory.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
6767
'additionalProperties' => true,
6868
],
6969
],
70-
] + self::BASE_PROPS,
71-
],
70+
],
71+
] + self::BASE_PROPS,
7272
];
7373

7474
private const ITEM_BASE_SCHEMA_OUTPUT = [

src/Hydra/Tests/JsonSchema/SchemaFactoryTest.php

+35-46
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use ApiPlatform\JsonSchema\DefinitionNameFactory;
2020
use ApiPlatform\JsonSchema\Schema;
2121
use ApiPlatform\JsonSchema\SchemaFactory as BaseSchemaFactory;
22+
use ApiPlatform\Metadata\ApiProperty;
2223
use ApiPlatform\Metadata\ApiResource;
2324
use ApiPlatform\Metadata\Get;
2425
use ApiPlatform\Metadata\GetCollection;
@@ -28,6 +29,7 @@
2829
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2930
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
3031
use PHPUnit\Framework\TestCase;
32+
use Prophecy\Argument;
3133
use Prophecy\PhpUnit\ProphecyTrait;
3234

3335
class SchemaFactoryTest extends TestCase
@@ -48,10 +50,12 @@ protected function setUp(): void
4850
);
4951

5052
$propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
51-
$propertyNameCollectionFactory->create(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_OUTPUT])->willReturn(new PropertyNameCollection());
53+
$propertyNameCollectionFactory->create(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_OUTPUT])->willReturn(new PropertyNameCollection(['id', 'name']));
5254
$propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
55+
$propertyMetadataFactory->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new ApiProperty(identifier: true));
56+
$propertyMetadataFactory->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new ApiProperty());
5357

54-
$definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true]);
58+
$definitionNameFactory = new DefinitionNameFactory();
5559

5660
$baseSchemaFactory = new BaseSchemaFactory(
5761
resourceMetadataFactory: $resourceMetadataFactoryCollection->reveal(),
@@ -60,7 +64,12 @@ protected function setUp(): void
6064
definitionNameFactory: $definitionNameFactory,
6165
);
6266

63-
$this->schemaFactory = new SchemaFactory($baseSchemaFactory);
67+
$this->schemaFactory = new SchemaFactory(
68+
$baseSchemaFactory,
69+
[],
70+
$definitionNameFactory,
71+
$resourceMetadataFactoryCollection->reveal(),
72+
);
6473
}
6574

6675
public function testBuildSchema(): void
@@ -86,12 +95,13 @@ public function testHasRootDefinitionKeyBuildSchema(): void
8695
$rootDefinitionKey = $resultSchema->getRootDefinitionKey();
8796

8897
$this->assertTrue(isset($definitions[$rootDefinitionKey]));
89-
$this->assertTrue(isset($definitions[$rootDefinitionKey]['properties']));
90-
$properties = $resultSchema['definitions'][$rootDefinitionKey]['properties'];
98+
$this->assertTrue(isset($definitions[$rootDefinitionKey]['allOf'][1]['properties']));
99+
$this->assertEquals($definitions[$rootDefinitionKey]['allOf'][0], ['$ref' => '#/definitions/HydraItemBaseSchema']);
100+
101+
$properties = $definitions['HydraItemBaseSchema']['properties'];
91102
$this->assertArrayHasKey('@context', $properties);
92103
$this->assertEquals(
93104
[
94-
'readOnly' => true,
95105
'oneOf' => [
96106
['type' => 'string'],
97107
[
@@ -119,55 +129,34 @@ public function testHasRootDefinitionKeyBuildSchema(): void
119129
public function testSchemaTypeBuildSchema(): void
120130
{
121131
$resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, new GetCollection());
122-
$definitionName = 'Dummy.jsonld';
123-
124132
$this->assertNull($resultSchema->getRootDefinitionKey());
125-
$this->assertTrue(isset($resultSchema['properties']));
126-
$this->assertTrue(isset($resultSchema['properties']['hydra:member']));
127-
$this->assertArrayHasKey('hydra:totalItems', $resultSchema['properties']);
128-
$this->assertArrayHasKey('hydra:view', $resultSchema['properties']);
129-
$this->assertArrayHasKey('hydra:search', $resultSchema['properties']);
130-
$properties = $resultSchema['definitions'][$definitionName]['properties'];
133+
$hydraCollectionSchema = $resultSchema['definitions']['HydraCollectionBaseSchema'];
134+
$properties = $hydraCollectionSchema['properties'];
135+
$this->assertTrue(isset($properties['hydra:member']));
136+
$this->assertArrayHasKey('hydra:totalItems', $properties);
137+
$this->assertArrayHasKey('hydra:view', $properties);
138+
$this->assertArrayHasKey('hydra:search', $properties);
131139
$this->assertArrayNotHasKey('@context', $properties);
132-
$this->assertArrayHasKey('@type', $properties);
133-
$this->assertArrayHasKey('@id', $properties);
134140

135-
$resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, null, null, null, true);
141+
$this->assertTrue(isset($properties['hydra:view']));
142+
$this->assertArrayHasKey('properties', $properties['hydra:view']);
143+
$this->assertArrayHasKey('hydra:first', $properties['hydra:view']['properties']);
144+
$this->assertArrayHasKey('hydra:last', $properties['hydra:view']['properties']);
145+
$this->assertArrayHasKey('hydra:previous', $properties['hydra:view']['properties']);
146+
$this->assertArrayHasKey('hydra:next', $properties['hydra:view']['properties']);
136147

137-
$this->assertNull($resultSchema->getRootDefinitionKey());
138-
$this->assertTrue(isset($resultSchema['properties']));
139-
$this->assertTrue(isset($resultSchema['properties']['hydra:member']));
140-
$this->assertArrayHasKey('hydra:totalItems', $resultSchema['properties']);
141-
$this->assertArrayHasKey('hydra:view', $resultSchema['properties']);
142-
$this->assertArrayHasKey('hydra:search', $resultSchema['properties']);
143-
$properties = $resultSchema['definitions'][$definitionName]['properties'];
144-
$this->assertArrayNotHasKey('@context', $properties);
145-
$this->assertArrayHasKey('@type', $properties);
146-
$this->assertArrayHasKey('@id', $properties);
147-
}
148-
149-
public function testHasHydraViewNavigationBuildSchema(): void
150-
{
151-
$resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, new GetCollection());
152-
153-
$this->assertNull($resultSchema->getRootDefinitionKey());
154-
$this->assertTrue(isset($resultSchema['properties']));
155-
$this->assertTrue(isset($resultSchema['properties']['hydra:view']));
156-
$this->assertArrayHasKey('properties', $resultSchema['properties']['hydra:view']);
157-
$this->assertArrayHasKey('hydra:first', $resultSchema['properties']['hydra:view']['properties']);
158-
$this->assertArrayHasKey('hydra:last', $resultSchema['properties']['hydra:view']['properties']);
159-
$this->assertArrayHasKey('hydra:previous', $resultSchema['properties']['hydra:view']['properties']);
160-
$this->assertArrayHasKey('hydra:next', $resultSchema['properties']['hydra:view']['properties']);
148+
$forcedCollection = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, null, null, null, true);
149+
$this->assertEquals($resultSchema['allOf'][0]['$ref'], $forcedCollection['allOf'][0]['$ref']);
161150
}
162151

163152
public function testSchemaTypeBuildSchemaWithoutPrefix(): void
164153
{
165154
$resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonld', Schema::TYPE_OUTPUT, new GetCollection(), null, [ContextBuilder::HYDRA_CONTEXT_HAS_PREFIX => false]);
166155
$this->assertNull($resultSchema->getRootDefinitionKey());
167-
$this->assertTrue(isset($resultSchema['properties']));
168-
$this->assertTrue(isset($resultSchema['properties']['member']));
169-
$this->assertArrayHasKey('totalItems', $resultSchema['properties']);
170-
$this->assertArrayHasKey('view', $resultSchema['properties']);
171-
$this->assertArrayHasKey('search', $resultSchema['properties']);
156+
$hydraCollectionSchema = $resultSchema['definitions']['HydraCollectionBaseSchema'];
157+
$properties = $hydraCollectionSchema['properties'];
158+
$this->assertArrayHasKey('totalItems', $properties);
159+
$this->assertArrayHasKey('view', $properties);
160+
$this->assertArrayHasKey('search', $properties);
172161
}
173162
}

src/JsonApi/JsonSchema/SchemaFactory.php

+9-13
Original file line numberDiff line numberDiff line change
@@ -234,21 +234,17 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin
234234
$properties = $this->buildDefinitionPropertiesSchema($key, $className, $format, $type, $operation, $schema, []);
235235
$properties['data']['properties']['attributes']['$ref'] = $prefix.$key;
236236

237-
$definitions[$definitionName] = [
238-
'description' => "$definitionName collection.",
239-
'allOf' => [
240-
['$ref' => $prefix.self::COLLECTION_BASE_SCHEMA_NAME],
241-
['type' => 'object', 'properties' => [
242-
'data' => [
243-
'type' => 'array',
244-
'items' => $properties['data'],
245-
],
246-
]],
247-
],
237+
$schema['description'] = "$definitionName collection.";
238+
$schema['allOf'] = [
239+
['$ref' => $prefix.self::COLLECTION_BASE_SCHEMA_NAME],
240+
['type' => 'object', 'properties' => [
241+
'data' => [
242+
'type' => 'array',
243+
'items' => $properties['data'],
244+
],
245+
]],
248246
];
249247

250-
$schema['$ref'] = $prefix.$definitionName;
251-
252248
return $schema;
253249
}
254250

src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php

+35-54
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ protected function setUp(): void
4545
(new ApiResource())->withOperations(new Operations([
4646
'get' => (new Get())->withName('get'),
4747
])),
48-
]));
48+
])
49+
);
4950
$propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
5051
$propertyNameCollectionFactory->create(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_OUTPUT])->willReturn(new PropertyNameCollection());
5152
$propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
5253

53-
$definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true]);
54+
$definitionNameFactory = new DefinitionNameFactory();
5455

5556
$baseSchemaFactory = new BaseSchemaFactory(
5657
resourceMetadataFactory: $resourceMetadataFactory->reveal(),
@@ -60,6 +61,7 @@ protected function setUp(): void
6061
);
6162

6263
$resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class);
64+
$resourceClassResolver->isResourceClass(Dummy::class)->willReturn(true);
6365

6466
$this->schemaFactory = new SchemaFactory(
6567
schemaFactory: $baseSchemaFactory,
@@ -107,9 +109,7 @@ public function testHasRootDefinitionKeyBuildSchema(): void
107109
'type' => 'string',
108110
],
109111
'attributes' => [
110-
'type' => 'object',
111-
'properties' => [
112-
],
112+
'$ref' => '#/definitions/Dummy',
113113
],
114114
],
115115
'required' => [
@@ -124,58 +124,39 @@ public function testHasRootDefinitionKeyBuildSchema(): void
124124
public function testSchemaTypeBuildSchema(): void
125125
{
126126
$resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonapi', Schema::TYPE_OUTPUT, new GetCollection());
127-
$definitionName = 'Dummy.jsonapi';
128127

129128
$this->assertNull($resultSchema->getRootDefinitionKey());
130-
$this->assertTrue(isset($resultSchema['properties']));
131-
$this->assertArrayHasKey('links', $resultSchema['properties']);
132-
$this->assertArrayHasKey('self', $resultSchema['properties']['links']['properties']);
133-
$this->assertArrayHasKey('first', $resultSchema['properties']['links']['properties']);
134-
$this->assertArrayHasKey('prev', $resultSchema['properties']['links']['properties']);
135-
$this->assertArrayHasKey('next', $resultSchema['properties']['links']['properties']);
136-
$this->assertArrayHasKey('last', $resultSchema['properties']['links']['properties']);
137-
138-
$this->assertArrayHasKey('meta', $resultSchema['properties']);
139-
$this->assertArrayHasKey('totalItems', $resultSchema['properties']['meta']['properties']);
140-
$this->assertArrayHasKey('itemsPerPage', $resultSchema['properties']['meta']['properties']);
141-
$this->assertArrayHasKey('currentPage', $resultSchema['properties']['meta']['properties']);
142-
143-
$this->assertArrayHasKey('data', $resultSchema['properties']);
144-
$this->assertArrayHasKey('items', $resultSchema['properties']['data']);
145-
$this->assertArrayHasKey('$ref', $resultSchema['properties']['data']['items']);
146-
147-
$properties = $resultSchema['definitions'][$definitionName]['properties'];
129+
$this->assertTrue(isset($resultSchema['allOf'][0]['$ref']));
130+
$this->assertEquals($resultSchema['allOf'][0]['$ref'], '#/definitions/JsonApiCollectionBaseSchema');
131+
132+
$jsonApiCollectionBaseSchema = $resultSchema['definitions']['JsonApiCollectionBaseSchema'];
133+
$this->assertTrue(isset($jsonApiCollectionBaseSchema['properties']));
134+
$this->assertArrayHasKey('links', $jsonApiCollectionBaseSchema['properties']);
135+
$this->assertArrayHasKey('self', $jsonApiCollectionBaseSchema['properties']['links']['properties']);
136+
$this->assertArrayHasKey('first', $jsonApiCollectionBaseSchema['properties']['links']['properties']);
137+
$this->assertArrayHasKey('prev', $jsonApiCollectionBaseSchema['properties']['links']['properties']);
138+
$this->assertArrayHasKey('next', $jsonApiCollectionBaseSchema['properties']['links']['properties']);
139+
$this->assertArrayHasKey('last', $jsonApiCollectionBaseSchema['properties']['links']['properties']);
140+
141+
$this->assertArrayHasKey('meta', $jsonApiCollectionBaseSchema['properties']);
142+
$this->assertArrayHasKey('totalItems', $jsonApiCollectionBaseSchema['properties']['meta']['properties']);
143+
$this->assertArrayHasKey('itemsPerPage', $jsonApiCollectionBaseSchema['properties']['meta']['properties']);
144+
$this->assertArrayHasKey('currentPage', $jsonApiCollectionBaseSchema['properties']['meta']['properties']);
145+
146+
$objectSchema = $resultSchema['allOf'][1];
147+
$this->assertArrayHasKey('data', $objectSchema['properties']);
148+
149+
$this->assertArrayHasKey('items', $objectSchema['properties']['data']);
150+
$this->assertArrayHasKey('$ref', $objectSchema['properties']['data']['items']['properties']['attributes']);
151+
152+
$properties = $objectSchema['properties'];
148153
$this->assertArrayHasKey('data', $properties);
149-
$this->assertArrayHasKey('properties', $properties['data']);
150-
$this->assertArrayHasKey('id', $properties['data']['properties']);
151-
$this->assertArrayHasKey('type', $properties['data']['properties']);
152-
$this->assertArrayHasKey('attributes', $properties['data']['properties']);
153-
154-
$resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonapi', Schema::TYPE_OUTPUT, forceCollection: true);
154+
$this->assertArrayHasKey('items', $properties['data']);
155+
$this->assertArrayHasKey('id', $properties['data']['items']['properties']);
156+
$this->assertArrayHasKey('type', $properties['data']['items']['properties']);
157+
$this->assertArrayHasKey('attributes', $properties['data']['items']['properties']);
155158

156-
$this->assertNull($resultSchema->getRootDefinitionKey());
157-
$this->assertTrue(isset($resultSchema['properties']));
158-
$this->assertArrayHasKey('links', $resultSchema['properties']);
159-
$this->assertArrayHasKey('self', $resultSchema['properties']['links']['properties']);
160-
$this->assertArrayHasKey('first', $resultSchema['properties']['links']['properties']);
161-
$this->assertArrayHasKey('prev', $resultSchema['properties']['links']['properties']);
162-
$this->assertArrayHasKey('next', $resultSchema['properties']['links']['properties']);
163-
$this->assertArrayHasKey('last', $resultSchema['properties']['links']['properties']);
164-
165-
$this->assertArrayHasKey('meta', $resultSchema['properties']);
166-
$this->assertArrayHasKey('totalItems', $resultSchema['properties']['meta']['properties']);
167-
$this->assertArrayHasKey('itemsPerPage', $resultSchema['properties']['meta']['properties']);
168-
$this->assertArrayHasKey('currentPage', $resultSchema['properties']['meta']['properties']);
169-
170-
$this->assertArrayHasKey('data', $resultSchema['properties']);
171-
$this->assertArrayHasKey('items', $resultSchema['properties']['data']);
172-
$this->assertArrayHasKey('$ref', $resultSchema['properties']['data']['items']);
173-
174-
$properties = $resultSchema['definitions'][$definitionName]['properties'];
175-
$this->assertArrayHasKey('data', $properties);
176-
$this->assertArrayHasKey('properties', $properties['data']);
177-
$this->assertArrayHasKey('id', $properties['data']['properties']);
178-
$this->assertArrayHasKey('type', $properties['data']['properties']);
179-
$this->assertArrayHasKey('attributes', $properties['data']['properties']);
159+
$forcedCollection = $this->schemaFactory->buildSchema(Dummy::class, 'jsonapi', Schema::TYPE_OUTPUT, forceCollection: true);
160+
$this->assertEquals($resultSchema['allOf'][0]['$ref'], $forcedCollection['allOf'][0]['$ref']);
180161
}
181162
}

src/JsonSchema/SchemaFactory.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public function buildSchema(string $className, string $format = 'json', string $
135135
foreach ($this->propertyNameCollectionFactory->create($inputOrOutputClass, $options) as $propertyName) {
136136
$propertyMetadata = $this->propertyMetadataFactory->create($inputOrOutputClass, $propertyName, $options);
137137

138-
if (!$propertyMetadata->isReadable() && !$propertyMetadata->isWritable()) {
138+
if (false === $propertyMetadata->isReadable() && false === $propertyMetadata->isWritable()) {
139139
continue;
140140
}
141141

0 commit comments

Comments
 (0)