Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.11.1"
".": "0.11.2"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The REST API documentation can be found on [docs.moderationapi.com](https://docs
<!-- x-release-please-start-version -->

```
composer require "moderation-api/sdk-php 0.11.1"
composer require "moderation-api/sdk-php 0.11.2"
```

<!-- x-release-please-end -->
Expand Down
17 changes: 1 addition & 16 deletions src/Core/Attributes/Required.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class Required

public readonly bool $nullable;

/** @var array<string,Converter> */
private static array $enumConverters = [];

/**
* @param class-string<ConverterSource>|Converter|string|null $type
* @param class-string<\BackedEnum>|Converter|null $enum
Expand All @@ -52,24 +49,12 @@ 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;
$this->type = $type;
$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];
}
}
15 changes: 15 additions & 0 deletions src/Core/Conversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
Expand Down
15 changes: 13 additions & 2 deletions src/Core/Conversion/EnumOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ final class EnumOf implements Converter
{
private readonly string $type;

/** @var array<class-string<\BackedEnum>, self> */
private static array $cache = [];

/**
* @param list<bool|float|int|string|null> $members
*/
Expand All @@ -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);
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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