Skip to content

Commit

Permalink
Use {"nullable": true, "anyOf": [{"$ref": ...}]} to comply with Ope…
Browse files Browse the repository at this point in the history
…nAPI 3.0

OpenAPI 3.1 is not yet released, but fixes nullability in
the way we had fixed it before (via `{"oneOf": [{"type": "null"}, ...]}`) in
OAI/OpenAPI-Specification#1977.

Until OpenAPI 3.1 is released, things like ``{"type": ["integer", "null"]}` are
not valid definitions (because `"null"` is not yet a recognized type).

We'll stick to OpenAPI 3.0 for now, using:

 * `{"nullable": true, ...}` for simple types
 * `{"nullable": true, "anyOf": [{"$ref": ...}]}` for type references
  • Loading branch information
Ocramius committed Feb 20, 2020
1 parent 03edb55 commit cc8dc66
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 49 deletions.
14 changes: 8 additions & 6 deletions src/JsonSchema/TypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,13 @@ private function addNullabilityToTypeDefinition(array $jsonSchema, Type $type, a
return $jsonSchema;
}

return [
'oneOf' => [
['type' => 'null'],
$jsonSchema,
],
];
if (\array_key_exists('$ref', $jsonSchema)) {
return [
'nullable' => true,
'anyOf' => [$jsonSchema],
];
}

return array_merge($jsonSchema, ['nullable' => true]);
}
}
57 changes: 23 additions & 34 deletions tests/JsonSchema/TypeFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,40 +29,38 @@ class TypeFactoryTest extends TestCase
public function testGetType(array $schema, Type $type): void
{
$typeFactory = new TypeFactory();
$this->assertSame($schema, $typeFactory->getType($type));
$this->assertEquals($schema, $typeFactory->getType($type));
}

public function typeProvider(): iterable
{
yield [['type' => 'integer'], new Type(Type::BUILTIN_TYPE_INT)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'integer']]], new Type(Type::BUILTIN_TYPE_INT, true)];
yield [['nullable' => true, 'type' => 'integer'], new Type(Type::BUILTIN_TYPE_INT, true)];
yield [['type' => 'number'], new Type(Type::BUILTIN_TYPE_FLOAT)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'number']]], new Type(Type::BUILTIN_TYPE_FLOAT, true)];
yield [['nullable' => true, 'type' => 'number'], new Type(Type::BUILTIN_TYPE_FLOAT, true)];
yield [['type' => 'boolean'], new Type(Type::BUILTIN_TYPE_BOOL)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'boolean']]], new Type(Type::BUILTIN_TYPE_BOOL, true)];
yield [['nullable' => true, 'type' => 'boolean'], new Type(Type::BUILTIN_TYPE_BOOL, true)];
yield [['type' => 'string'], new Type(Type::BUILTIN_TYPE_STRING)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'string']]], new Type(Type::BUILTIN_TYPE_STRING, true)];
yield [['nullable' => true, 'type' => 'string'], new Type(Type::BUILTIN_TYPE_STRING, true)];
yield [['type' => 'object'], new Type(Type::BUILTIN_TYPE_OBJECT)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'object']]], new Type(Type::BUILTIN_TYPE_OBJECT, true)];
yield [['nullable' => true, 'type' => 'object'], new Type(Type::BUILTIN_TYPE_OBJECT, true)];
yield [['type' => 'string', 'format' => 'date-time'], new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeImmutable::class)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'string', 'format' => 'date-time']]], new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTimeImmutable::class)];
yield [['nullable' => true, 'type' => 'string', 'format' => 'date-time'], new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTimeImmutable::class)];
yield [['type' => 'string', 'format' => 'duration'], new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateInterval::class)];
yield [['type' => 'object'], new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)];
yield [['oneOf' => [['type' => 'null'], ['type' => 'object']]], new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class)];
yield [['nullable' => true, 'type' => 'object'], new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class)];
yield [['type' => 'array', 'items' => ['type' => 'string']], new Type(Type::BUILTIN_TYPE_STRING, false, null, true)];
yield 'array can be itself nullable' => [
['oneOf' => [['type' => 'null'], ['type' => 'array', 'items' => ['type' => 'string']]]],
['nullable' => true, 'type' => 'array', 'items' => ['type' => 'string']],
new Type(Type::BUILTIN_TYPE_STRING, true, null, true),
];

yield 'array can contain nullable values' => [
[
'type' => 'array',
'items' => [
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
'nullable' => true,
'type' => 'string',
],
],
new Type(Type::BUILTIN_TYPE_STRING, false, null, true, null, new Type(Type::BUILTIN_TYPE_STRING, true, null, false)),
Expand All @@ -81,10 +79,9 @@ public function typeProvider(): iterable

yield 'nullable map with string keys becomes a nullable object' => [
[
'oneOf' => [
['type' => 'null'],
['type' => 'object', 'additionalProperties' => ['type' => 'string']],
],
'nullable' => true,
'type' => 'object',
'additionalProperties' => ['type' => 'string'],
],
new Type(
Type::BUILTIN_TYPE_STRING,
Expand Down Expand Up @@ -112,10 +109,8 @@ public function typeProvider(): iterable
[
'type' => 'object',
'additionalProperties' => [
'oneOf' => [
['type' => 'null'],
['type' => 'integer'],
],
'nullable' => true,
'type' => 'integer',
],
],
new Type(
Expand All @@ -130,17 +125,11 @@ public function typeProvider(): iterable

yield 'nullable map can contain nullable values' => [
[
'oneOf' => [
['type' => 'null'],
[
'type' => 'object',
'additionalProperties' => [
'oneOf' => [
['type' => 'null'],
['type' => 'integer'],
],
],
],
'nullable' => true,
'type' => 'object',
'additionalProperties' => [
'nullable' => true,
'type' => 'integer',
],
],
new Type(
Expand Down Expand Up @@ -297,8 +286,8 @@ public function testGetClassTypeWithNullability(): void

self::assertSame(
[
'oneOf' => [
['type' => 'null'],
'nullable' => true,
'anyOf' => [
['$ref' => 'the-ref-name'],
],
],
Expand Down
14 changes: 5 additions & 9 deletions tests/Swagger/Serializer/DocumentationNormalizerV3Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -385,13 +385,9 @@ private function doTestNormalize(OperationMethodResolverInterface $operationMeth
'description' => 'This is an initializable but not writable property.',
]),
'dummyDate' => new \ArrayObject([
'oneOf' => [
['type' => 'null'],
[
'type' => 'string',
'format' => 'date-time',
],
],
'nullable' => true,
'type' => 'string',
'format' => 'date-time',
'description' => 'This is a \DateTimeInterface object.',
]),
],
Expand Down Expand Up @@ -2005,8 +2001,8 @@ public function testNormalizeWithNestedNormalizationGroups(): void
]),
'relatedDummy' => new \ArrayObject([
'description' => 'This is a related dummy \o/.',
'oneOf' => [
['type' => 'null'],
'nullable' => true,
'anyOf' => [
['$ref' => '#/components/schemas/'.$relatedDummyRef],
],
]),
Expand Down

0 comments on commit cc8dc66

Please sign in to comment.