From 9187f8218b177950d895987b087d3ee6b742aaf4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:06:43 +0000 Subject: [PATCH 1/2] fix: populate enum-typed properties with enum instances --- src/Core/Conversion/EnumOf.php | 20 +++++++++-- tests/Core/ModelTest.php | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/Core/Conversion/EnumOf.php b/src/Core/Conversion/EnumOf.php index a21c137..4f65eb0 100644 --- a/src/Core/Conversion/EnumOf.php +++ b/src/Core/Conversion/EnumOf.php @@ -19,9 +19,12 @@ final class EnumOf implements Converter /** * @param list $members + * @param class-string<\BackedEnum>|null $class */ - public function __construct(private readonly array $members) - { + public function __construct( + private readonly array $members, + private readonly ?string $class = null, + ) { $type = 'NULL'; foreach ($this->members as $member) { $type = gettype($member); @@ -33,13 +36,24 @@ public function __construct(private readonly array $members) public static function fromBackedEnum(string $enum): self { // @phpstan-ignore-next-line argument.type - return self::$cache[$enum] ??= new self(array_column($enum::cases(), column_key: 'value')); + return self::$cache[$enum] ??= new self( + array_column($enum::cases(), column_key: 'value'), + class: $enum, + ); } public function coerce(mixed $value, CoerceState $state): mixed { $this->tally($value, state: $state); + if ($value instanceof \BackedEnum) { + return $value; + } + + if (null !== $this->class && (is_int($value) || is_string($value))) { + return ($this->class)::tryFrom($value) ?? $value; + } + return $value; } diff --git a/tests/Core/ModelTest.php b/tests/Core/ModelTest.php index 9a4573d..059ba01 100644 --- a/tests/Core/ModelTest.php +++ b/tests/Core/ModelTest.php @@ -47,6 +47,30 @@ public function __construct( } } +enum TicketPriority: string +{ + case Low = 'low'; + case High = 'high'; +} + +class Ticket implements BaseModel +{ + /** @use SdkModel> */ + use SdkModel; + + #[Required(enum: TicketPriority::class)] + public TicketPriority $priority; + + /** @var list */ + #[Required(list: TicketPriority::class)] + public array $labels; + + public function __construct() + { + $this->initialize(); + } +} + /** * @internal * @@ -141,4 +165,42 @@ public function testSerializeModelWithExplicitNull(): void json_encode($model) ); } + + #[Test] + public function testScalarEnumCoercesToInstance(): void + { + $model = Ticket::fromArray(['priority' => 'low', 'labels' => []]); + $this->assertSame(TicketPriority::Low, $model->priority); + } + + #[Test] + public function testListOfEnumCoercesElementsToInstances(): void + { + $model = Ticket::fromArray(['priority' => 'low', 'labels' => ['low', 'high']]); + $this->assertCount(2, $model->labels); + $this->assertSame(TicketPriority::Low, $model->labels[0]); + $this->assertSame(TicketPriority::High, $model->labels[1]); + } + + #[Test] + public function testEnumInstancePassesThrough(): void + { + $model = Ticket::fromArray(['priority' => TicketPriority::High, 'labels' => []]); + $this->assertSame(TicketPriority::High, $model->priority); + } + + #[Test] + public function testInvalidEnumScalarFallsBackToData(): void + { + $model = Ticket::fromArray(['priority' => 'urgent', 'labels' => []]); + $this->assertSame('urgent', $model['priority']); + } + + #[Test] + public function testEnumWireFormatStableAcrossConstruction(): void + { + $fromScalar = Ticket::fromArray(['priority' => 'low', 'labels' => ['high']]); + $fromInstance = Ticket::fromArray(['priority' => TicketPriority::Low, 'labels' => [TicketPriority::High]]); + $this->assertSame(json_encode($fromScalar), json_encode($fromInstance)); + } } From f4d310ea88c762de9d1b85eb80cfc9320f186d42 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:06:56 +0000 Subject: [PATCH 2/2] release: 0.11.3 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ README.md | 2 +- src/Version.php | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 95e4ab6..5332797 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.2" + ".": "0.11.3" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 08304d8..253ed42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.11.3 (2026-04-18) + +Full Changelog: [v0.11.2...v0.11.3](https://github.com/moderation-api/sdk-php/compare/v0.11.2...v0.11.3) + +### Bug Fixes + +* populate enum-typed properties with enum instances ([9187f82](https://github.com/moderation-api/sdk-php/commit/9187f8218b177950d895987b087d3ee6b742aaf4)) + ## 0.11.2 (2026-04-18) Full Changelog: [v0.11.1...v0.11.2](https://github.com/moderation-api/sdk-php/compare/v0.11.1...v0.11.2) diff --git a/README.md b/README.md index c8e6b8f..1bcb96e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The REST API documentation can be found on [docs.moderationapi.com](https://docs ``` -composer require "moderation-api/sdk-php 0.11.2" +composer require "moderation-api/sdk-php 0.11.3" ``` diff --git a/src/Version.php b/src/Version.php index 0662ea0..1bad0ad 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace ModerationAPI; // x-release-please-start-version -const VERSION = '0.11.2'; +const VERSION = '0.11.3'; // x-release-please-end