From 6156476cb84eaf43b040d59db43226bfa45064ef Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 19 Aug 2026 12:42:20 +0530 Subject: [PATCH] fix: stop interpreting x-nullable --- src/Parser/Schema/Reader.php | 4 ++-- tests/CrossVersionFixtureTest.php | 2 +- tests/Fixtures/openapi-2.0.json | 3 +-- tests/Fixtures/openapi-3.0.json | 3 +-- tests/Fixtures/openapi-3.1.json | 2 +- tests/Schema/ReaderTest.php | 15 +++++++++++---- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Parser/Schema/Reader.php b/src/Parser/Schema/Reader.php index 1634b59..08663c5 100644 --- a/src/Parser/Schema/Reader.php +++ b/src/Parser/Schema/Reader.php @@ -32,7 +32,7 @@ private const array PARAMETER_FIELDS = [ 'type', 'format', 'items', 'default', 'enum', 'maximum', 'exclusiveMaximum', 'minimum', 'exclusiveMinimum', 'maxLength', 'minLength', 'pattern', - 'maxItems', 'minItems', 'uniqueItems', 'multipleOf', 'description', 'x-nullable', + 'maxItems', 'minItems', 'uniqueItems', 'multipleOf', 'description', ]; public function __construct(private Dialect $dialect) {} @@ -161,7 +161,7 @@ private function common(array $data): array return [ 'title' => Value::optionalString($data, 'title'), 'description' => Value::optionalString($data, 'description') ?? '', - 'nullable' => (bool) ($data['nullable'] ?? $data['x-nullable'] ?? false), + 'nullable' => (bool) ($data['nullable'] ?? false), 'default' => $data['default'] ?? null, 'enum' => $enum, 'format' => Value::optionalString($data, 'format'), diff --git a/tests/CrossVersionFixtureTest.php b/tests/CrossVersionFixtureTest.php index 6a50bb7..b7509ce 100644 --- a/tests/CrossVersionFixtureTest.php +++ b/tests/CrossVersionFixtureTest.php @@ -105,7 +105,7 @@ public function test_equivalent_documents_produce_equivalent_canonical_behavior( self::assertSame('int64', $pet->properties['id']->format); self::assertInstanceOf(StringSchema::class, $pet->properties['name']); self::assertSame(1, $pet->properties['name']->minLength); - self::assertTrue($pet->properties['nickname']->nullable); + self::assertFalse($pet->properties['nickname']->nullable); self::assertInstanceOf(ReferenceSchema::class, $pet->properties['parent']); self::assertSame($this->petReference($version), $pet->properties['parent']->reference); self::assertSame(['available', 'adopted'], $pet->properties['status']->enum); diff --git a/tests/Fixtures/openapi-2.0.json b/tests/Fixtures/openapi-2.0.json index af3e543..a5fde5d 100644 --- a/tests/Fixtures/openapi-2.0.json +++ b/tests/Fixtures/openapi-2.0.json @@ -132,8 +132,7 @@ "minLength": 1 }, "nickname": { - "type": "string", - "x-nullable": true + "type": "string" }, "parent": { "$ref": "#/definitions/Pet" diff --git a/tests/Fixtures/openapi-3.0.json b/tests/Fixtures/openapi-3.0.json index 504594c..b956f75 100644 --- a/tests/Fixtures/openapi-3.0.json +++ b/tests/Fixtures/openapi-3.0.json @@ -148,8 +148,7 @@ "minLength": 1 }, "nickname": { - "type": "string", - "nullable": true + "type": "string" }, "parent": { "$ref": "#/components/schemas/Pet" diff --git a/tests/Fixtures/openapi-3.1.json b/tests/Fixtures/openapi-3.1.json index 115ea9b..365455c 100644 --- a/tests/Fixtures/openapi-3.1.json +++ b/tests/Fixtures/openapi-3.1.json @@ -148,7 +148,7 @@ "minLength": 1 }, "nickname": { - "type": ["string", "null"] + "type": "string" }, "parent": { "$ref": "#/components/schemas/Pet" diff --git a/tests/Schema/ReaderTest.php b/tests/Schema/ReaderTest.php index 54ae311..128e9e7 100644 --- a/tests/Schema/ReaderTest.php +++ b/tests/Schema/ReaderTest.php @@ -66,13 +66,20 @@ public function test_an_explicit_enum_wins_over_const(): void self::assertSame(['a', 'b'], $this->reader(Version::V3_1)->read(['type' => 'string', 'const' => 'c', 'enum' => ['a', 'b']], '#/x')->enum); } - public function test_nullability_is_read_from_either_keyword(): void + public function test_nullability_is_read_from_the_nullable_keyword(): void { self::assertTrue($this->reader(Version::V3_0)->read(['type' => 'string', 'nullable' => true], '#/x')->nullable); - self::assertTrue($this->reader(Version::V2)->read(['type' => 'string', 'x-nullable' => true], '#/x')->nullable); self::assertFalse($this->reader(Version::V3_0)->read(['type' => 'string'], '#/x')->nullable); } + public function test_x_nullable_remains_an_uninterpreted_extension(): void + { + $schema = $this->reader(Version::V2)->read(['type' => 'string', 'x-nullable' => true], '#/x'); + + self::assertFalse($schema->nullable); + self::assertSame(['x-nullable' => true], $schema->extensions); + } + public function test_references_are_left_unexpanded_so_recursive_graphs_terminate(): void { $schema = $this->reader(Version::V3_1)->read(['$ref' => '#/components/schemas/Pet'], '#/x'); @@ -245,8 +252,8 @@ public function test_parameter_fields_are_lifted_into_a_schema_and_non_schema_ke self::assertInstanceOf(IntegerSchema::class, $schema); self::assertSame(1, $schema->minimum); - self::assertTrue($schema->nullable); - self::assertSame(['x-nullable' => true], $schema->extensions, "'x-nullable' is read as nullability and still kept as an extension"); + self::assertFalse($schema->nullable); + self::assertSame([], $schema->extensions); } public function test_extensions_are_carried_onto_the_schema(): void