Skip to content

Commit 981e6e1

Browse files
committed
feat: reference formats
1 parent ad54075 commit 981e6e1

File tree

20 files changed

+475
-217
lines changed

20 files changed

+475
-217
lines changed

.github/workflows/ci.yml

+4-6
Original file line numberDiff line numberDiff line change
@@ -425,16 +425,15 @@ jobs:
425425
- name: Export OpenAPI documents
426426
run: |
427427
mkdir -p build/out/openapi
428-
tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json
428+
# tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json
429429
tests/Fixtures/app/console api:openapi:export --yaml -o build/out/openapi/openapi_v3.yaml
430430
- name: Setup node
431431
uses: actions/setup-node@v4
432432
with:
433433
node-version: '14'
434434
- name: Validate OpenAPI documents
435435
run: |
436-
npx swagger-cli validate build/out/openapi/openapi_v3.json
437-
npx swagger-cli validate build/out/openapi/openapi_v3.yaml
436+
npx @quobix/vacuum lint validate build/out/openapi/openapi_v3.yaml
438437
- name: Upload OpenAPI artifacts
439438
if: always()
440439
uses: actions/upload-artifact@v4
@@ -1227,16 +1226,15 @@ jobs:
12271226
- name: Export OpenAPI documents
12281227
run: |
12291228
mkdir -p build/out/openapi
1230-
tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json
1229+
# tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json
12311230
tests/Fixtures/app/console api:openapi:export --yaml -o build/out/openapi/openapi_v3.yaml
12321231
- name: Setup node
12331232
uses: actions/setup-node@v4
12341233
with:
12351234
node-version: '14'
12361235
- name: Validate OpenAPI documents
12371236
run: |
1238-
npx swagger-cli validate build/out/openapi/openapi_v3.json
1239-
npx swagger-cli validate build/out/openapi/openapi_v3.yaml
1237+
npx @quobix/vacuum lint validate build/out/openapi/openapi_v3.yaml
12401238
- name: Upload OpenAPI artifacts
12411239
if: always()
12421240
uses: actions/upload-artifact@v4

src/Hal/JsonSchema/SchemaFactory.php

+117-52
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,130 @@ 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+
$serializerContext ??= [];
80+
} else {
81+
$operation = $this->findOperation($className, $type, $operation, $serializerContext, $format);
82+
$inputOrOutputClass = $this->findOutputClass($className, $type, $operation, $serializerContext);
83+
$serializerContext ??= $this->getSerializerContext($operation, $type);
6284
}
6385

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

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

74123
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-
[
124+
if (!isset($definitions[self::COLLECTION_BASE_SCHEMA_NAME])) {
125+
$definitions[self::COLLECTION_BASE_SCHEMA_NAME] = [
126+
'type' => 'object',
127+
'properties' => [
128+
'_embedded' => [
129+
'anyOf' => [
130+
[
131+
'type' => 'object',
132+
'properties' => [
133+
'item' => [
134+
'type' => 'array',
135+
],
136+
],
137+
],
138+
['type' => 'object'],
139+
],
140+
],
141+
'totalItems' => [
142+
'type' => 'integer',
143+
'minimum' => 0,
144+
],
145+
'itemsPerPage' => [
146+
'type' => 'integer',
147+
'minimum' => 0,
148+
],
149+
'_links' => [
83150
'type' => 'object',
84151
'properties' => [
85-
'item' => [
86-
'type' => 'array',
87-
'items' => $items,
152+
'self' => [
153+
'type' => 'object',
154+
'properties' => self::HREF_PROP,
155+
],
156+
'first' => [
157+
'type' => 'object',
158+
'properties' => self::HREF_PROP,
159+
],
160+
'last' => [
161+
'type' => 'object',
162+
'properties' => self::HREF_PROP,
163+
],
164+
'next' => [
165+
'type' => 'object',
166+
'properties' => self::HREF_PROP,
167+
],
168+
'previous' => [
169+
'type' => 'object',
170+
'properties' => self::HREF_PROP,
88171
],
89172
],
90173
],
91-
['type' => 'object'],
92174
],
93-
],
94-
'totalItems' => [
95-
'type' => 'integer',
96-
'minimum' => 0,
97-
],
98-
'itemsPerPage' => [
99-
'type' => 'integer',
100-
'minimum' => 0,
101-
],
102-
'_links' => [
175+
'required' => ['_links', '_embedded'],
176+
];
177+
}
178+
179+
unset($schema['items']);
180+
unset($schema['type']);
181+
182+
$schema['description'] = "$definitionName collection.";
183+
$schema['allOf'] = [
184+
['$ref' => $prefix.self::COLLECTION_BASE_SCHEMA_NAME],
185+
[
103186
'type' => 'object',
104187
'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,
188+
'_embedded' => [
189+
'additionalProperties' => [
190+
'type' => 'array',
191+
'items' => ['$ref' => $prefix.$definitionName],
192+
],
124193
],
125194
],
126195
],
127196
];
128-
$schema['required'] = [
129-
'_links',
130-
'_embedded',
131-
];
132197

133198
return $schema;
134199
}

0 commit comments

Comments
 (0)