From 6975c64cf192b8ddc8a52974aea766335df3bfc8 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 6 Jan 2025 18:49:10 +0100 Subject: [PATCH 1/5] Allow null as type in schema --- src/Chain/StructuredOutput/SchemaFactory.php | 20 +++++++++++++++++++ .../ResponseFormatFactoryTest.php | 3 ++- .../StructuredOutput/SchemaFactoryTest.php | 3 ++- tests/Fixture/StructuredOutput/User.php | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Chain/StructuredOutput/SchemaFactory.php b/src/Chain/StructuredOutput/SchemaFactory.php index 57e2c156..c8d8f281 100644 --- a/src/Chain/StructuredOutput/SchemaFactory.php +++ b/src/Chain/StructuredOutput/SchemaFactory.php @@ -83,12 +83,24 @@ private function getTypeSchema(Type $type): array { switch ($type->getBuiltinType()) { case Type::BUILTIN_TYPE_INT: + if ($type->isNullable()) { + return ['type' => ['integer', 'null']]; + } + return ['type' => 'integer']; case Type::BUILTIN_TYPE_FLOAT: + if ($type->isNullable()) { + return ['type' => ['number', 'null']]; + } + return ['type' => 'number']; case Type::BUILTIN_TYPE_BOOL: + if ($type->isNullable()) { + return ['type' => ['boolean', 'null']]; + } + return ['type' => 'boolean']; case Type::BUILTIN_TYPE_ARRAY: @@ -111,6 +123,10 @@ private function getTypeSchema(Type $type): array case Type::BUILTIN_TYPE_OBJECT: if (\DateTimeInterface::class === $type->getClassName()) { + if ($type->isNullable()) { + return ['type' => ['string', 'null'], 'format' => 'date-time']; + } + return ['type' => 'string', 'format' => 'date-time']; } else { // Recursively build the schema for an object type @@ -121,6 +137,10 @@ private function getTypeSchema(Type $type): array case Type::BUILTIN_TYPE_STRING: default: // Fallback to string for any unhandled types + if ($type->isNullable()) { + return ['type' => ['string', 'null']]; + } + return ['type' => 'string']; } } diff --git a/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php b/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php index 3f3bcb8a..d8a2b1c1 100644 --- a/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php +++ b/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php @@ -37,8 +37,9 @@ public function create(): void 'format' => 'date-time', ], 'isActive' => ['type' => 'boolean'], + 'age' => ['type' => ['integer', 'null']], ], - 'required' => ['id', 'name', 'createdAt', 'isActive'], + 'required' => ['id', 'name', 'createdAt', 'isActive', 'age'], 'additionalProperties' => false, ], 'strict' => true, diff --git a/tests/Chain/StructuredOutput/SchemaFactoryTest.php b/tests/Chain/StructuredOutput/SchemaFactoryTest.php index 0db30a34..3fd0f657 100644 --- a/tests/Chain/StructuredOutput/SchemaFactoryTest.php +++ b/tests/Chain/StructuredOutput/SchemaFactoryTest.php @@ -39,8 +39,9 @@ public function buildSchemaForUserClass(): void 'format' => 'date-time', ], 'isActive' => ['type' => 'boolean'], + 'age' => ['type' => ['integer', 'null']], ], - 'required' => ['id', 'name', 'createdAt', 'isActive'], + 'required' => ['id', 'name', 'createdAt', 'isActive', 'age'], 'additionalProperties' => false, ]; diff --git a/tests/Fixture/StructuredOutput/User.php b/tests/Fixture/StructuredOutput/User.php index 50220df1..aee22008 100644 --- a/tests/Fixture/StructuredOutput/User.php +++ b/tests/Fixture/StructuredOutput/User.php @@ -13,4 +13,5 @@ final class User public string $name; public \DateTimeInterface $createdAt; public bool $isActive; + public ?int $age = null; } From 9bf6da856d1c8a4c4c776cd4788d3684ba683c57 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 6 Jan 2025 18:53:21 +0100 Subject: [PATCH 2/5] - --- tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php | 2 +- tests/Chain/StructuredOutput/SchemaFactoryTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php b/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php index d8a2b1c1..51825b52 100644 --- a/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php +++ b/tests/Chain/StructuredOutput/ResponseFormatFactoryTest.php @@ -39,7 +39,7 @@ public function create(): void 'isActive' => ['type' => 'boolean'], 'age' => ['type' => ['integer', 'null']], ], - 'required' => ['id', 'name', 'createdAt', 'isActive', 'age'], + 'required' => ['id', 'name', 'createdAt', 'isActive'], 'additionalProperties' => false, ], 'strict' => true, diff --git a/tests/Chain/StructuredOutput/SchemaFactoryTest.php b/tests/Chain/StructuredOutput/SchemaFactoryTest.php index 3fd0f657..2b4ace56 100644 --- a/tests/Chain/StructuredOutput/SchemaFactoryTest.php +++ b/tests/Chain/StructuredOutput/SchemaFactoryTest.php @@ -41,7 +41,7 @@ public function buildSchemaForUserClass(): void 'isActive' => ['type' => 'boolean'], 'age' => ['type' => ['integer', 'null']], ], - 'required' => ['id', 'name', 'createdAt', 'isActive', 'age'], + 'required' => ['id', 'name', 'createdAt', 'isActive'], 'additionalProperties' => false, ]; From 353826d963e92b123a371d843773b78f450331ea Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 8 Jan 2025 20:55:11 +0100 Subject: [PATCH 3/5] - --- src/Chain/StructuredOutput/SchemaFactory.php | 23 ++++---------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/Chain/StructuredOutput/SchemaFactory.php b/src/Chain/StructuredOutput/SchemaFactory.php index c8d8f281..432389c5 100644 --- a/src/Chain/StructuredOutput/SchemaFactory.php +++ b/src/Chain/StructuredOutput/SchemaFactory.php @@ -59,6 +59,10 @@ public function buildSchema(string $className): array $type = $types[0]; $propertySchema = $this->getTypeSchema($type); + if (!is_array($propertySchema['type']) && $type->isNullable()) { + $propertySchema['type'] = [$propertySchema['type'], 'null']; + } + // Add description if available if ($description) { $propertySchema['description'] = $description; @@ -83,24 +87,13 @@ private function getTypeSchema(Type $type): array { switch ($type->getBuiltinType()) { case Type::BUILTIN_TYPE_INT: - if ($type->isNullable()) { - return ['type' => ['integer', 'null']]; - } - return ['type' => 'integer']; case Type::BUILTIN_TYPE_FLOAT: - if ($type->isNullable()) { - return ['type' => ['number', 'null']]; - } return ['type' => 'number']; case Type::BUILTIN_TYPE_BOOL: - if ($type->isNullable()) { - return ['type' => ['boolean', 'null']]; - } - return ['type' => 'boolean']; case Type::BUILTIN_TYPE_ARRAY: @@ -123,10 +116,6 @@ private function getTypeSchema(Type $type): array case Type::BUILTIN_TYPE_OBJECT: if (\DateTimeInterface::class === $type->getClassName()) { - if ($type->isNullable()) { - return ['type' => ['string', 'null'], 'format' => 'date-time']; - } - return ['type' => 'string', 'format' => 'date-time']; } else { // Recursively build the schema for an object type @@ -137,10 +126,6 @@ private function getTypeSchema(Type $type): array case Type::BUILTIN_TYPE_STRING: default: // Fallback to string for any unhandled types - if ($type->isNullable()) { - return ['type' => ['string', 'null']]; - } - return ['type' => 'string']; } } From ad279aa83f99fbdbdece4e169c1675c3f9a8d692 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 8 Jan 2025 20:55:22 +0100 Subject: [PATCH 4/5] - --- src/Chain/StructuredOutput/SchemaFactory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Chain/StructuredOutput/SchemaFactory.php b/src/Chain/StructuredOutput/SchemaFactory.php index 432389c5..b060c775 100644 --- a/src/Chain/StructuredOutput/SchemaFactory.php +++ b/src/Chain/StructuredOutput/SchemaFactory.php @@ -59,6 +59,7 @@ public function buildSchema(string $className): array $type = $types[0]; $propertySchema = $this->getTypeSchema($type); + // Handle nullable types if (!is_array($propertySchema['type']) && $type->isNullable()) { $propertySchema['type'] = [$propertySchema['type'], 'null']; } From 06506d858de30a7f9492fbf39e1ce9cecb05bb18 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 8 Jan 2025 20:55:41 +0100 Subject: [PATCH 5/5] - --- src/Chain/StructuredOutput/SchemaFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Chain/StructuredOutput/SchemaFactory.php b/src/Chain/StructuredOutput/SchemaFactory.php index b060c775..b8781f70 100644 --- a/src/Chain/StructuredOutput/SchemaFactory.php +++ b/src/Chain/StructuredOutput/SchemaFactory.php @@ -91,7 +91,6 @@ private function getTypeSchema(Type $type): array return ['type' => 'integer']; case Type::BUILTIN_TYPE_FLOAT: - return ['type' => 'number']; case Type::BUILTIN_TYPE_BOOL: