diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e82003f..95e4ab6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.1" + ".": "0.11.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db1057..08304d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 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) + +### Bug Fixes + +* **client:** resolve serialization issue with unions and enums ([c03ba75](https://github.com/moderation-api/sdk-php/commit/c03ba75a951df565d5b6734f3fd581d356cfccfd)) + ## 0.11.1 (2026-04-11) Full Changelog: [v0.11.0...v0.11.1](https://github.com/moderation-api/sdk-php/compare/v0.11.0...v0.11.1) diff --git a/README.md b/README.md index 20f232c..c8e6b8f 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.1" +composer require "moderation-api/sdk-php 0.11.2" ``` diff --git a/src/Core/Attributes/Required.php b/src/Core/Attributes/Required.php index a1cb601..3e7bd0c 100644 --- a/src/Core/Attributes/Required.php +++ b/src/Core/Attributes/Required.php @@ -25,9 +25,6 @@ class Required public readonly bool $nullable; - /** @var array */ - private static array $enumConverters = []; - /** * @param class-string|Converter|string|null $type * @param class-string<\BackedEnum>|Converter|null $enum @@ -52,7 +49,7 @@ public function __construct( $type ??= new MapOf($map); } if (null !== $enum) { - $type ??= $enum instanceof Converter ? $enum : self::enumConverter($enum); + $type ??= $enum instanceof Converter ? $enum : EnumOf::fromBackedEnum($enum); } $this->apiName = $apiName; @@ -60,16 +57,4 @@ public function __construct( $this->optional = false; $this->nullable = $nullable; } - - /** @property class-string<\BackedEnum> $enum */ - private static function enumConverter(string $enum): Converter - { - if (!isset(self::$enumConverters[$enum])) { - // @phpstan-ignore-next-line argument.type - $converter = new EnumOf(array_column($enum::cases(), column_key: 'value')); - self::$enumConverters[$enum] = $converter; - } - - return self::$enumConverters[$enum]; - } } diff --git a/src/Core/Conversion.php b/src/Core/Conversion.php index 1b395ef..97f8771 100644 --- a/src/Core/Conversion.php +++ b/src/Core/Conversion.php @@ -8,6 +8,7 @@ use ModerationAPI\Core\Conversion\Contracts\Converter; use ModerationAPI\Core\Conversion\Contracts\ConverterSource; use ModerationAPI\Core\Conversion\DumpState; +use ModerationAPI\Core\Conversion\EnumOf; /** * @internal @@ -65,6 +66,13 @@ public static function coerce(Converter|ConverterSource|string $target, mixed $v return $target->coerce($value, state: $state); } + // BackedEnum class-name targets: wrap in EnumOf so enum values are scored + // against the enum's cases. Without this, tryConvert's default case scores + // any class-name target as `no`, even when the value is a valid enum member. + if (is_a($target, class: \BackedEnum::class, allow_string: true)) { + return EnumOf::fromBackedEnum($target)->coerce($value, state: $state); + } + return self::tryConvert($target, value: $value, state: $state); } @@ -78,6 +86,13 @@ public static function dump(Converter|ConverterSource|string $target, mixed $val return $target::converter()->dump($value, state: $state); } + // BackedEnum class-name targets: wrap in EnumOf so enum values are scored + // against the enum's cases. Without this, tryConvert's default case scores + // any class-name target as `no`, even when the value is a valid enum member. + if (is_a($target, class: \BackedEnum::class, allow_string: true)) { + return EnumOf::fromBackedEnum($target)->dump($value, state: $state); + } + self::tryConvert($target, value: $value, state: $state); return self::dump_unknown($value, state: $state); diff --git a/src/Core/Conversion/EnumOf.php b/src/Core/Conversion/EnumOf.php index 7877eb9..a21c137 100644 --- a/src/Core/Conversion/EnumOf.php +++ b/src/Core/Conversion/EnumOf.php @@ -14,6 +14,9 @@ final class EnumOf implements Converter { private readonly string $type; + /** @var array, self> */ + private static array $cache = []; + /** * @param list $members */ @@ -26,6 +29,13 @@ public function __construct(private readonly array $members) $this->type = $type; } + /** @param class-string<\BackedEnum> $enum */ + 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')); + } + public function coerce(mixed $value, CoerceState $state): mixed { $this->tally($value, state: $state); @@ -42,9 +52,10 @@ public function dump(mixed $value, DumpState $state): mixed private function tally(mixed $value, CoerceState|DumpState $state): void { - if (in_array($value, haystack: $this->members, strict: true)) { + $needle = $value instanceof \BackedEnum ? $value->value : $value; + if (in_array($needle, haystack: $this->members, strict: true)) { ++$state->yes; - } elseif ($this->type === gettype($value)) { + } elseif ($this->type === gettype($needle)) { ++$state->maybe; } else { ++$state->no; diff --git a/src/Version.php b/src/Version.php index 8d6abc3..0662ea0 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace ModerationAPI; // x-release-please-start-version -const VERSION = '0.11.1'; +const VERSION = '0.11.2'; // x-release-please-end