Skip to content

Commit bbbae81

Browse files
committed
fixes
1 parent 00b7529 commit bbbae81

File tree

3 files changed

+53
-56
lines changed

3 files changed

+53
-56
lines changed

src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php

+29-29
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ private function applyNullability(array $schema, bool $isNullable): array
184184
$currentType = $schema['type'];
185185
$schema['type'] = \is_array($currentType) ? array_merge($currentType, ['null']) : [$currentType, 'null'];
186186

187+
if (isset($schema['enum'])) {
188+
$schema['enum'][] = null;
189+
190+
return $schema;
191+
}
192+
187193
return $schema;
188194
}
189195

@@ -199,18 +205,23 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
199205
{
200206
$isNullable = $type->isNullable();
201207

202-
while ($type instanceof WrappingTypeInterface) {
203-
$type = $type->getWrappedType();
204-
}
205-
206208
if ($type instanceof UnionType) {
207209
$subTypes = array_filter($type->getTypes(), fn ($t) => !($t instanceof BuiltinType && $t->isIdentifiedBy(TypeIdentifier::NULL)));
210+
211+
foreach ($subTypes as $t) {
212+
$s = $this->getJsonSchemaFromType($t, $readableLink);
213+
// We can not find what type this is, let it be computed at runtime by the SchemaFactory
214+
if (($s['type'] ?? null) === Schema::UNKNOWN_TYPE) {
215+
return $s;
216+
}
217+
}
218+
208219
$schemas = array_map(fn ($t) => $this->getJsonSchemaFromType($t, $readableLink), $subTypes);
209220

210221
if (0 === \count($schemas)) {
211222
$schema = [];
212223
} elseif (1 === \count($schemas)) {
213-
$schema = $schemas[0];
224+
$schema = current($schemas);
214225
} else {
215226
$schema = ['anyOf' => $schemas];
216227
}
@@ -235,20 +246,20 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
235246
}
236247

237248
if ($type instanceof CollectionType) {
238-
$keyType = $type->getCollectionKeyType();
239249
$valueType = $type->getCollectionValueType();
240-
$schema = [];
250+
$valueSchema = $this->getJsonSchemaFromType($valueType, $readableLink);
251+
$keyType = $type->getCollectionKeyType();
241252

242253
// Associative array (string keys)
243-
if ($keyType->isSatisfiedBy(fn (Type $t) => $t instanceof BuiltinType && $t->isIdentifiedBy(TypeIdentifier::STRING))) {
254+
if ($keyType->isSatisfiedBy(fn (Type $t) => $t instanceof BuiltinType && $t->isIdentifiedBy(TypeIdentifier::INT))) {
244255
$schema = [
245-
'type' => 'object',
246-
'additionalProperties' => $this->getJsonSchemaFromType($valueType, $readableLink),
256+
'type' => 'array',
257+
'items' => $valueSchema,
247258
];
248259
} else { // List (int keys)
249260
$schema = [
250-
'type' => 'array',
251-
'items' => $this->getJsonSchemaFromType($valueType, $readableLink),
261+
'type' => 'object',
262+
'additionalProperties' => $valueSchema,
252263
];
253264
}
254265

@@ -262,10 +273,6 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
262273
}
263274

264275
if ($type instanceof BuiltinType) {
265-
if ($type->isIdentifiedBy(TypeIdentifier::NULL)) {
266-
return ['type' => 'null'];
267-
}
268-
269276
$schema = match ($type->getTypeIdentifier()) {
270277
TypeIdentifier::INT => ['type' => 'integer'],
271278
TypeIdentifier::FLOAT => ['type' => 'number'],
@@ -284,7 +291,7 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
284291
return $this->applyNullability($schema, $isNullable);
285292
}
286293

287-
return $this->applyNullability(['type' => Schema::UNKNOWN_TYPE], $isNullable);
294+
return ['type' => Schema::UNKNOWN_TYPE];
288295
}
289296

290297
/**
@@ -316,34 +323,27 @@ private function getClassSchemaDefinition(?string $className, ?bool $readableLin
316323
return ['type' => 'string', 'format' => 'binary'];
317324
}
318325

319-
if (is_a($className, \BackedEnum::class, true)) {
326+
$isResourceClass = $this->isResourceClass($className);
327+
if (!$isResourceClass && is_a($className, \BackedEnum::class, true)) {
320328
$enumCases = array_map(static fn (\BackedEnum $enum): string|int => $enum->value, $className::cases());
321329
$type = \is_string($enumCases[0] ?? '') ? 'string' : 'integer';
322330

323331
return ['type' => $type, 'enum' => $enumCases];
324332
}
325333

326-
$isResource = $this->isResourceClass($className);
327-
328334
// If it's a resource and links are not readable, represent as IRI string.
329-
if ($isResource && true !== $readableLink) {
335+
if ($isResourceClass && true !== $readableLink) {
330336
return [
331337
'type' => 'string',
332338
'format' => 'iri-reference',
333339
'example' => 'https://example.com/', // Add a generic example
334340
];
335341
}
336342

337-
// If it's a known resource represent it as UNKNOWN_TYPE this gets resolved at runtime by the SchemaFactory
338-
if ($isResource) {
339-
return ['type' => Schema::UNKNOWN_TYPE];
340-
}
341-
342-
// For non-resource objects that aren't handled specifically, default to object.
343-
return ['type' => 'object'];
343+
return ['type' => Schema::UNKNOWN_TYPE];
344344
}
345345

346-
private function getLegacyTypeSchema(ApiProperty $propertyMetadata, array $propertySchema, string $resourceClass, string $property, bool $link): array
346+
private function getLegacyTypeSchema(ApiProperty $propertyMetadata, array $propertySchema, string $resourceClass, string $property, ?bool $link): array
347347
{
348348
$types = $propertyMetadata->getBuiltinTypes() ?? [];
349349

src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php

+15-12
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,24 @@ public function create(string $resourceClass, string $property, array $options =
4646
}
4747
}
4848

49-
if (!method_exists(PropertyInfoExtractor::class, 'getType') && !$propertyMetadata->getBuiltinTypes()) {
50-
$types = $this->propertyInfo->getTypes($resourceClass, $property, $options) ?? [];
49+
// TODO: remove in 5.x
50+
if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
51+
if (!$propertyMetadata->getBuiltinTypes()) {
52+
$types = $this->propertyInfo->getTypes($resourceClass, $property, $options) ?? [];
5153

52-
foreach ($types as $i => $type) {
53-
// Temp fix for https://github.com/symfony/symfony/pull/52699
54-
if (ArrayCollection::class === $type->getClassName()) {
55-
$types[$i] = new Type($type->getBuiltinType(), $type->isNullable(), $type->getClassName(), true, $type->getCollectionKeyTypes(), $type->getCollectionValueTypes());
54+
foreach ($types as $i => $type) {
55+
// Temp fix for https://github.com/symfony/symfony/pull/52699
56+
if (ArrayCollection::class === $type->getClassName()) {
57+
$types[$i] = new Type($type->getBuiltinType(), $type->isNullable(), $type->getClassName(), true, $type->getCollectionKeyTypes(), $type->getCollectionValueTypes());
58+
}
5659
}
57-
}
58-
59-
$propertyMetadata = $propertyMetadata->withBuiltinTypes($types);
60-
}
6160

62-
if (!$propertyMetadata->getNativeType()) {
63-
$propertyMetadata = $propertyMetadata->withNativeType($this->propertyInfo->getType($resourceClass, $property, $options));
61+
$propertyMetadata = $propertyMetadata->withBuiltinTypes($types);
62+
}
63+
} else {
64+
if (!$propertyMetadata->getNativeType()) {
65+
$propertyMetadata = $propertyMetadata->withNativeType($this->propertyInfo->getType($resourceClass, $property, $options));
66+
}
6467
}
6568

6669
if (null === $propertyMetadata->getDescription() && null !== $description = $this->propertyInfo->getShortDescription($resourceClass, $property, $options)) {

tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php

+9-15
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,9 @@ public function testArraySchemaWithMultipleUnionTypesJsonLd(): void
177177
$result = $this->tester->getDisplay();
178178
$json = json_decode($result, associative: true);
179179

180-
$this->assertEquals($json['definitions']['Nest.jsonld']['properties']['owner']['anyOf'], [
181-
['$ref' => '#/definitions/Robin.jsonld'],
182-
['$ref' => '#/definitions/Wren.jsonld'],
183-
['type' => 'null'],
184-
]);
180+
$this->assertContains(['$ref' => '#/definitions/Robin.jsonld'], $json['definitions']['Nest.jsonld']['properties']['owner']['anyOf']);
181+
$this->assertContains(['$ref' => '#/definitions/Wren.jsonld'], $json['definitions']['Nest.jsonld']['properties']['owner']['anyOf']);
182+
$this->assertContains(['type' => 'null'], $json['definitions']['Nest.jsonld']['properties']['owner']['anyOf']);
185183

186184
$this->assertArrayHasKey('Wren.jsonld', $json['definitions']);
187185
$this->assertArrayHasKey('Robin.jsonld', $json['definitions']);
@@ -193,11 +191,9 @@ public function testArraySchemaWithMultipleUnionTypesJsonApi(): void
193191
$result = $this->tester->getDisplay();
194192
$json = json_decode($result, associative: true);
195193

196-
$this->assertEquals($json['definitions']['Nest.jsonapi']['properties']['data']['properties']['attributes']['properties']['owner']['anyOf'], [
197-
['$ref' => '#/definitions/Robin.jsonapi'],
198-
['$ref' => '#/definitions/Wren.jsonapi'],
199-
['type' => 'null'],
200-
]);
194+
$this->assertContains(['$ref' => '#/definitions/Robin.jsonapi'], $json['definitions']['Nest.jsonapi']['properties']['data']['properties']['attributes']['properties']['owner']['anyOf']);
195+
$this->assertContains(['$ref' => '#/definitions/Wren.jsonapi'], $json['definitions']['Nest.jsonapi']['properties']['data']['properties']['attributes']['properties']['owner']['anyOf']);
196+
$this->assertContains(['type' => 'null'], $json['definitions']['Nest.jsonapi']['properties']['data']['properties']['attributes']['properties']['owner']['anyOf']);
201197

202198
$this->assertArrayHasKey('Wren.jsonapi', $json['definitions']);
203199
$this->assertArrayHasKey('Robin.jsonapi', $json['definitions']);
@@ -209,11 +205,9 @@ public function testArraySchemaWithMultipleUnionTypesJsonHal(): void
209205
$result = $this->tester->getDisplay();
210206
$json = json_decode($result, associative: true);
211207

212-
$this->assertEquals($json['definitions']['Nest.jsonhal']['properties']['owner']['anyOf'], [
213-
['$ref' => '#/definitions/Robin.jsonhal'],
214-
['$ref' => '#/definitions/Wren.jsonhal'],
215-
['type' => 'null'],
216-
]);
208+
$this->assertContains(['$ref' => '#/definitions/Robin.jsonhal'], $json['definitions']['Nest.jsonhal']['properties']['owner']['anyOf']);
209+
$this->assertContains(['$ref' => '#/definitions/Wren.jsonhal'], $json['definitions']['Nest.jsonhal']['properties']['owner']['anyOf']);
210+
$this->assertContains(['type' => 'null'], $json['definitions']['Nest.jsonhal']['properties']['owner']['anyOf']);
217211

218212
$this->assertArrayHasKey('Wren.jsonhal', $json['definitions']);
219213
$this->assertArrayHasKey('Robin.jsonhal', $json['definitions']);

0 commit comments

Comments
 (0)