Skip to content

Commit 8a62553

Browse files
authored
fix: manage JSON Schema nullability (#3817)
1 parent 6dded70 commit 8a62553

File tree

2 files changed

+129
-3
lines changed

2 files changed

+129
-3
lines changed

src/JsonSchema/TypeFactory.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ private function addNullabilityToTypeDefinition(array $jsonSchema, Type $type, ?
161161
];
162162
}
163163

164+
if ($schema && Schema::VERSION_JSON_SCHEMA === $schema->getVersion()) {
165+
return array_merge(
166+
$jsonSchema,
167+
[
168+
'type' => \is_array($jsonSchema['type'])
169+
? array_merge($jsonSchema['type'], ['null'])
170+
: [$jsonSchema['type'], 'null'],
171+
]
172+
);
173+
}
174+
164175
return array_merge($jsonSchema, ['nullable' => true]);
165176
}
166177
}

tests/JsonSchema/TypeFactoryTest.php

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class TypeFactoryTest extends TestCase
3232
public function testGetType(array $schema, Type $type): void
3333
{
3434
$typeFactory = new TypeFactory();
35-
$this->assertEquals($schema, $typeFactory->getType($type, 'json', null, null, new Schema()));
35+
$this->assertEquals($schema, $typeFactory->getType($type, 'json', null, null, new Schema(Schema::VERSION_OPENAPI)));
3636
}
3737

3838
public function typeProvider(): iterable
@@ -146,14 +146,129 @@ public function typeProvider(): iterable
146146
];
147147
}
148148

149-
/** @dataProvider openAPIV2typeProvider */
149+
/**
150+
* @dataProvider jsonSchemaTypeProvider
151+
*/
152+
public function testGetTypeWithJsonSchemaSyntax(array $schema, Type $type): void
153+
{
154+
$typeFactory = new TypeFactory();
155+
$this->assertEquals($schema, $typeFactory->getType($type, 'json', null, null, new Schema(Schema::VERSION_JSON_SCHEMA)));
156+
}
157+
158+
public function jsonSchemaTypeProvider(): iterable
159+
{
160+
yield [['type' => 'integer'], new Type(Type::BUILTIN_TYPE_INT)];
161+
yield [['type' => ['integer', 'null']], new Type(Type::BUILTIN_TYPE_INT, true)];
162+
yield [['type' => 'number'], new Type(Type::BUILTIN_TYPE_FLOAT)];
163+
yield [['type' => ['number', 'null']], new Type(Type::BUILTIN_TYPE_FLOAT, true)];
164+
yield [['type' => 'boolean'], new Type(Type::BUILTIN_TYPE_BOOL)];
165+
yield [['type' => ['boolean', 'null']], new Type(Type::BUILTIN_TYPE_BOOL, true)];
166+
yield [['type' => 'string'], new Type(Type::BUILTIN_TYPE_STRING)];
167+
yield [['type' => ['string', 'null']], new Type(Type::BUILTIN_TYPE_STRING, true)];
168+
yield [['type' => 'string'], new Type(Type::BUILTIN_TYPE_OBJECT)];
169+
yield [['type' => ['string', 'null']], new Type(Type::BUILTIN_TYPE_OBJECT, true)];
170+
yield [['type' => 'string', 'format' => 'date-time'], new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class)];
171+
yield [['type' => ['string', 'null'], 'format' => 'date-time'], new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTimeImmutable::class)];
172+
yield [['type' => 'string', 'format' => 'duration'], new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateInterval::class)];
173+
yield [['type' => 'string', 'format' => 'iri-reference'], new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)];
174+
yield [['type' => ['string', 'null'], 'format' => 'iri-reference'], new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class)];
175+
yield [['type' => 'array', 'items' => ['type' => 'string']], new Type(Type::BUILTIN_TYPE_STRING, false, null, true)];
176+
yield 'array can be itself nullable' => [
177+
['type' => ['array', 'null'], 'items' => ['type' => 'string']],
178+
new Type(Type::BUILTIN_TYPE_STRING, true, null, true),
179+
];
180+
181+
yield 'array can contain nullable values' => [
182+
[
183+
'type' => 'array',
184+
'items' => [
185+
'type' => ['string', 'null'],
186+
],
187+
],
188+
new Type(Type::BUILTIN_TYPE_STRING, false, null, true, null, new Type(Type::BUILTIN_TYPE_STRING, true, null, false)),
189+
];
190+
191+
yield 'map with string keys becomes an object' => [
192+
['type' => 'object', 'additionalProperties' => ['type' => 'string']],
193+
new Type(
194+
Type::BUILTIN_TYPE_STRING,
195+
false,
196+
null,
197+
true,
198+
new Type(Type::BUILTIN_TYPE_STRING, false, null, false)
199+
),
200+
];
201+
202+
yield 'nullable map with string keys becomes a nullable object' => [
203+
[
204+
'type' => ['object', 'null'],
205+
'additionalProperties' => ['type' => 'string'],
206+
],
207+
new Type(
208+
Type::BUILTIN_TYPE_STRING,
209+
true,
210+
null,
211+
true,
212+
new Type(Type::BUILTIN_TYPE_STRING, false, null, false),
213+
new Type(Type::BUILTIN_TYPE_STRING, false, null, false)
214+
),
215+
];
216+
217+
yield 'map value type will be considered' => [
218+
['type' => 'object', 'additionalProperties' => ['type' => 'integer']],
219+
new Type(
220+
Type::BUILTIN_TYPE_ARRAY,
221+
false,
222+
null,
223+
true,
224+
new Type(Type::BUILTIN_TYPE_STRING, false, null, false),
225+
new Type(Type::BUILTIN_TYPE_INT, false, null, false)
226+
),
227+
];
228+
229+
yield 'map value type nullability will be considered' => [
230+
[
231+
'type' => 'object',
232+
'additionalProperties' => [
233+
'type' => ['integer', 'null'],
234+
],
235+
],
236+
new Type(
237+
Type::BUILTIN_TYPE_ARRAY,
238+
false,
239+
null,
240+
true,
241+
new Type(Type::BUILTIN_TYPE_STRING, false, null, false),
242+
new Type(Type::BUILTIN_TYPE_INT, true, null, false)
243+
),
244+
];
245+
246+
yield 'nullable map can contain nullable values' => [
247+
[
248+
'type' => ['object', 'null'],
249+
'additionalProperties' => [
250+
'type' => ['integer', 'null'],
251+
],
252+
],
253+
new Type(
254+
Type::BUILTIN_TYPE_ARRAY,
255+
true,
256+
null,
257+
true,
258+
new Type(Type::BUILTIN_TYPE_STRING, false, null, false),
259+
new Type(Type::BUILTIN_TYPE_INT, true, null, false)
260+
),
261+
];
262+
}
263+
264+
/** @dataProvider openAPIV2TypeProvider */
150265
public function testGetTypeWithOpenAPIV2Syntax(array $schema, Type $type): void
151266
{
152267
$typeFactory = new TypeFactory();
153268
$this->assertSame($schema, $typeFactory->getType($type, 'json', null, null, new Schema(Schema::VERSION_SWAGGER)));
154269
}
155270

156-
public function openAPIV2typeProvider(): iterable
271+
public function openAPIV2TypeProvider(): iterable
157272
{
158273
yield [['type' => 'integer'], new Type(Type::BUILTIN_TYPE_INT)];
159274
yield [['type' => 'integer'], new Type(Type::BUILTIN_TYPE_INT, true)];

0 commit comments

Comments
 (0)