Skip to content

Commit d688026

Browse files
committed
feat: reference formats
1 parent 2a13100 commit d688026

File tree

29 files changed

+821
-393
lines changed

29 files changed

+821
-393
lines changed

features/jsonapi/related-resouces-inclusion.feature

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ Feature: JSON API Inclusion of Related Resources
5454
}
5555
"""
5656

57+
@createSchema
5758
Scenario: Request inclusion of a non existing related resource
59+
Given there are 3 dummy property objects
5860
When I send a "GET" request to "/dummy_properties/1?include=foo"
5961
Then the response status code should be 200
6062
And the response should be in JSON
@@ -87,7 +89,9 @@ Feature: JSON API Inclusion of Related Resources
8789
}
8890
"""
8991

92+
@createSchema
9093
Scenario: Request inclusion of a related resource keeping main object properties unfiltered
94+
Given there are 3 dummy property objects
9195
When I send a "GET" request to "/dummy_properties/1?include=group&fields[group]=id,foo&fields[DummyProperty]=bar,baz"
9296
Then the response status code should be 200
9397
And the response should be in JSON

features/openapi/docs.feature

Lines changed: 1 addition & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,9 @@ Feature: Documentation support
153153
And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters" should have 6 elements
154154

155155
# Subcollection - check schema
156-
And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.responses.200.content.application/ld+json.schema.properties.hydra:member.items.$ref" should be equal to "#/components/schemas/RelatedToDummyFriend.jsonld-fakemanytomany"
156+
And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.responses.200.content.application/ld+json.schema.allOf[1].properties.hydra:member.items.$ref" should be equal to "#/components/schemas/RelatedToDummyFriend.jsonld-fakemanytomany"
157157

158158
# Deprecations
159-
And the JSON node "paths./dummies.get.deprecated" should be false
160159
And the JSON node "paths./deprecated_resources.get.deprecated" should be true
161160
And the JSON node "paths./deprecated_resources.post.deprecated" should be true
162161
And the JSON node "paths./deprecated_resources/{id}.get.deprecated" should be true
@@ -166,111 +165,6 @@ Feature: Documentation support
166165

167166
# Formats
168167
And the OpenAPI class "Dummy.jsonld" exists
169-
And the "@id" property exists for the OpenAPI class "Dummy.jsonld"
170-
And the JSON node "paths./dummies.get.responses.200.content.application/ld+json" should be equal to:
171-
"""
172-
{
173-
"schema": {
174-
"type": "object",
175-
"properties": {
176-
"hydra:member": {
177-
"type": "array",
178-
"items": {
179-
"$ref": "#/components/schemas/Dummy.jsonld"
180-
}
181-
},
182-
"hydra:totalItems": {
183-
"type": "integer",
184-
"minimum": 0
185-
},
186-
"hydra:view": {
187-
"type": "object",
188-
"properties": {
189-
"@id": {
190-
"type": "string",
191-
"format": "iri-reference"
192-
},
193-
"@type": {
194-
"type": "string"
195-
},
196-
"hydra:first": {
197-
"type": "string",
198-
"format": "iri-reference"
199-
},
200-
"hydra:last": {
201-
"type": "string",
202-
"format": "iri-reference"
203-
},
204-
"hydra:previous": {
205-
"type": "string",
206-
"format": "iri-reference"
207-
},
208-
"hydra:next": {
209-
"type": "string",
210-
"format": "iri-reference"
211-
}
212-
},
213-
"example": {
214-
"@id": "string",
215-
"type": "string",
216-
"hydra:first": "string",
217-
"hydra:last": "string",
218-
"hydra:previous": "string",
219-
"hydra:next": "string"
220-
}
221-
},
222-
"hydra:search": {
223-
"type": "object",
224-
"properties": {
225-
"@type": {
226-
"type": "string"
227-
},
228-
"hydra:template": {
229-
"type": "string"
230-
},
231-
"hydra:variableRepresentation": {
232-
"type": "string"
233-
},
234-
"hydra:mapping": {
235-
"type": "array",
236-
"items": {
237-
"type": "object",
238-
"properties": {
239-
"@type": {
240-
"type": "string"
241-
},
242-
"variable": {
243-
"type": "string"
244-
},
245-
"property": {
246-
"type": ["string", "null"]
247-
},
248-
"required": {
249-
"type": "boolean"
250-
}
251-
}
252-
}
253-
}
254-
}
255-
}
256-
},
257-
"required": [
258-
"hydra:member"
259-
]
260-
}
261-
}
262-
"""
263-
And the JSON node "paths./dummies.get.responses.200.content.application/json" should be equal to:
264-
"""
265-
{
266-
"schema": {
267-
"type": "array",
268-
"items": {
269-
"$ref": "#/components/schemas/Dummy"
270-
}
271-
}
272-
}
273-
"""
274168
And the JSON node "paths./override_open_api_responses.post.responses" should be equal to:
275169
"""
276170
{
@@ -322,7 +216,6 @@ Feature: Documentation support
322216
And the "resourceRelated" property for the OpenAPI class "Resource" should be equal to:
323217
"""
324218
{
325-
"readOnly": true,
326219
"anyOf": [
327220
{
328221
"$ref": "#/components/schemas/ResourceRelated"

src/Hal/JsonSchema/SchemaFactory.php

Lines changed: 118 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313

1414
namespace ApiPlatform\Hal\JsonSchema;
1515

16+
use ApiPlatform\JsonSchema\DefinitionNameFactory;
17+
use ApiPlatform\JsonSchema\DefinitionNameFactoryInterface;
18+
use ApiPlatform\JsonSchema\ResourceMetadataTrait;
1619
use ApiPlatform\JsonSchema\Schema;
1720
use ApiPlatform\JsonSchema\SchemaFactoryAwareInterface;
1821
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
22+
use ApiPlatform\JsonSchema\SchemaUriPrefixTrait;
1923
use ApiPlatform\Metadata\Operation;
24+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2025

2126
/**
2227
* Decorator factory which adds HAL properties to the JSON Schema document.
@@ -26,6 +31,11 @@
2631
*/
2732
final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
2833
{
34+
use ResourceMetadataTrait;
35+
use SchemaUriPrefixTrait;
36+
37+
private const COLLECTION_BASE_SCHEMA_NAME = 'HalCollectionBaseSchema';
38+
2939
private const HREF_PROP = [
3040
'href' => [
3141
'type' => 'string',
@@ -44,8 +54,12 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
4454
],
4555
];
4656

47-
public function __construct(private readonly SchemaFactoryInterface $schemaFactory)
57+
public function __construct(private readonly SchemaFactoryInterface $schemaFactory, private ?DefinitionNameFactoryInterface $definitionNameFactory = null, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null)
4858
{
59+
if (!$definitionNameFactory) {
60+
$this->definitionNameFactory = new DefinitionNameFactory();
61+
}
62+
$this->resourceMetadataFactory = $resourceMetadataFactory;
4963
if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
5064
$this->schemaFactory->setSchemaFactory($this);
5165
}
@@ -56,79 +70,131 @@ public function __construct(private readonly SchemaFactoryInterface $schemaFacto
5670
*/
5771
public function buildSchema(string $className, string $format = 'jsonhal', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
5872
{
59-
$schema = $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
6073
if ('jsonhal' !== $format) {
61-
return $schema;
74+
return $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
75+
}
76+
77+
if (!$this->isResourceClass($className)) {
78+
$operation = null;
79+
$inputOrOutputClass = null;
80+
$serializerContext ??= [];
81+
} else {
82+
$operation = $this->findOperation($className, $type, $operation, $serializerContext, $format);
83+
$inputOrOutputClass = $this->findOutputClass($className, $type, $operation, $serializerContext);
84+
$serializerContext ??= $this->getSerializerContext($operation, $type);
6285
}
6386

87+
if (null === $inputOrOutputClass) {
88+
// input or output disabled
89+
return $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
90+
}
91+
92+
$schema = $this->schemaFactory->buildSchema($className, 'json', $type, $operation, $schema, $serializerContext, $forceCollection);
6493
$definitions = $schema->getDefinitions();
65-
if ($key = $schema->getRootDefinitionKey()) {
66-
$definitions[$key]['properties'] = self::BASE_PROPS + ($definitions[$key]['properties'] ?? []);
94+
$definitionName = $this->definitionNameFactory->create($className, $format, $className, $operation, $serializerContext);
95+
$prefix = $this->getSchemaUriPrefix($schema->getVersion());
96+
$collectionKey = $schema->getItemsDefinitionKey();
97+
98+
// Already computed
99+
if (!$collectionKey && isset($definitions[$definitionName])) {
100+
$schema['$ref'] = $prefix.$definitionName;
67101

68102
return $schema;
69103
}
70-
if ($key = $schema->getItemsDefinitionKey()) {
71-
$definitions[$key]['properties'] = self::BASE_PROPS + ($definitions[$key]['properties'] ?? []);
104+
105+
$key = $schema->getRootDefinitionKey() ?? $collectionKey;
106+
107+
$definitions[$definitionName] = [
108+
'allOf' => [
109+
['type' => 'object', 'properties' => self::BASE_PROPS],
110+
['$ref' => $prefix.$key],
111+
],
112+
];
113+
114+
if (isset($definitions[$key]['description'])) {
115+
$definitions[$definitionName]['description'] = $definitions[$key]['description'];
116+
}
117+
118+
if (!$collectionKey) {
119+
$schema['$ref'] = $prefix.$definitionName;
120+
121+
return $schema;
72122
}
73123

74124
if (($schema['type'] ?? '') === 'array') {
75-
$items = $schema['items'];
76-
unset($schema['items']);
77-
78-
$schema['type'] = 'object';
79-
$schema['properties'] = [
80-
'_embedded' => [
81-
'anyOf' => [
82-
[
125+
if (!isset($definitions[self::COLLECTION_BASE_SCHEMA_NAME])) {
126+
$definitions[self::COLLECTION_BASE_SCHEMA_NAME] = [
127+
'type' => 'object',
128+
'properties' => [
129+
'_embedded' => [
130+
'anyOf' => [
131+
[
132+
'type' => 'object',
133+
'properties' => [
134+
'item' => [
135+
'type' => 'array',
136+
],
137+
],
138+
],
139+
['type' => 'object'],
140+
],
141+
],
142+
'totalItems' => [
143+
'type' => 'integer',
144+
'minimum' => 0,
145+
],
146+
'itemsPerPage' => [
147+
'type' => 'integer',
148+
'minimum' => 0,
149+
],
150+
'_links' => [
83151
'type' => 'object',
84152
'properties' => [
85-
'item' => [
86-
'type' => 'array',
87-
'items' => $items,
153+
'self' => [
154+
'type' => 'object',
155+
'properties' => self::HREF_PROP,
156+
],
157+
'first' => [
158+
'type' => 'object',
159+
'properties' => self::HREF_PROP,
160+
],
161+
'last' => [
162+
'type' => 'object',
163+
'properties' => self::HREF_PROP,
164+
],
165+
'next' => [
166+
'type' => 'object',
167+
'properties' => self::HREF_PROP,
168+
],
169+
'previous' => [
170+
'type' => 'object',
171+
'properties' => self::HREF_PROP,
88172
],
89173
],
90174
],
91-
['type' => 'object'],
92175
],
93-
],
94-
'totalItems' => [
95-
'type' => 'integer',
96-
'minimum' => 0,
97-
],
98-
'itemsPerPage' => [
99-
'type' => 'integer',
100-
'minimum' => 0,
101-
],
102-
'_links' => [
176+
'required' => ['_links', '_embedded'],
177+
];
178+
}
179+
180+
unset($schema['items']);
181+
unset($schema['type']);
182+
183+
$schema['description'] = "$definitionName collection.";
184+
$schema['allOf'] = [
185+
['$ref' => $prefix.self::COLLECTION_BASE_SCHEMA_NAME],
186+
[
103187
'type' => 'object',
104188
'properties' => [
105-
'self' => [
106-
'type' => 'object',
107-
'properties' => self::HREF_PROP,
108-
],
109-
'first' => [
110-
'type' => 'object',
111-
'properties' => self::HREF_PROP,
112-
],
113-
'last' => [
114-
'type' => 'object',
115-
'properties' => self::HREF_PROP,
116-
],
117-
'next' => [
118-
'type' => 'object',
119-
'properties' => self::HREF_PROP,
120-
],
121-
'previous' => [
122-
'type' => 'object',
123-
'properties' => self::HREF_PROP,
189+
'_embedded' => [
190+
'additionalProperties' => [
191+
'type' => 'array',
192+
'items' => ['$ref' => $prefix.$definitionName],
193+
],
124194
],
125195
],
126196
],
127197
];
128-
$schema['required'] = [
129-
'_links',
130-
'_embedded',
131-
];
132198

133199
return $schema;
134200
}

0 commit comments

Comments
 (0)