Skip to content

Commit 33dc6fa

Browse files
committed
feat: reference formats
1 parent ad54075 commit 33dc6fa

40 files changed

+525
-265
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/Doctrine/Odm/Filter/DateFilter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ private function addMatch(Builder $aggregationBuilder, string $field, string $op
248248
*/
249249
public function getSchema(Parameter $parameter): array
250250
{
251-
return ['type' => 'date'];
251+
return ['type' => 'string', 'format' => 'date'];
252252
}
253253

254254
public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null

src/Doctrine/Odm/Filter/NumericFilter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,6 @@ protected function getType(?string $doctrineType = null): string
168168

169169
public function getSchema(Parameter $parameter): array
170170
{
171-
return ['type' => 'numeric'];
171+
return ['type' => 'number'];
172172
}
173173
}

src/Doctrine/Orm/Filter/DateFilter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
280280
*/
281281
public function getSchema(Parameter $parameter): array
282282
{
283-
return ['type' => 'date'];
283+
return ['type' => 'string', 'format' => 'date'];
284284
}
285285

286286
public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null

src/Doctrine/Orm/Filter/NumericFilter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,6 @@ protected function getType(?string $doctrineType = null): string
181181

182182
public function getSchema(Parameter $parameter): array
183183
{
184-
return ['type' => 'numeric'];
184+
return ['type' => 'number'];
185185
}
186186
}

src/Hal/JsonSchema/SchemaFactory.php

+118-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,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)