Skip to content

Commit cf55f59

Browse files
fix(client): resolve serialization issue with unions and enums
1 parent f0c45e5 commit cf55f59

3 files changed

Lines changed: 29 additions & 18 deletions

File tree

src/Core/Attributes/Required.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ class Required
2525

2626
public readonly bool $nullable;
2727

28-
/** @var array<string,Converter> */
29-
private static array $enumConverters = [];
30-
3128
/**
3229
* @param class-string<ConverterSource>|Converter|string|null $type
3330
* @param class-string<\BackedEnum>|Converter|null $enum
@@ -52,24 +49,12 @@ public function __construct(
5249
$type ??= new MapOf($map);
5350
}
5451
if (null !== $enum) {
55-
$type ??= $enum instanceof Converter ? $enum : self::enumConverter($enum);
52+
$type ??= $enum instanceof Converter ? $enum : EnumOf::fromBackedEnum($enum);
5653
}
5754

5855
$this->apiName = $apiName;
5956
$this->type = $type;
6057
$this->optional = false;
6158
$this->nullable = $nullable;
6259
}
63-
64-
/** @property class-string<\BackedEnum> $enum */
65-
private static function enumConverter(string $enum): Converter
66-
{
67-
if (!isset(self::$enumConverters[$enum])) {
68-
// @phpstan-ignore-next-line argument.type
69-
$converter = new EnumOf(array_column($enum::cases(), column_key: 'value'));
70-
self::$enumConverters[$enum] = $converter;
71-
}
72-
73-
return self::$enumConverters[$enum];
74-
}
7560
}

src/Core/Conversion.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use HubSpotSDK\Core\Conversion\Contracts\Converter;
99
use HubSpotSDK\Core\Conversion\Contracts\ConverterSource;
1010
use HubSpotSDK\Core\Conversion\DumpState;
11+
use HubSpotSDK\Core\Conversion\EnumOf;
1112

1213
/**
1314
* @internal
@@ -65,6 +66,13 @@ public static function coerce(Converter|ConverterSource|string $target, mixed $v
6566
return $target->coerce($value, state: $state);
6667
}
6768

69+
// BackedEnum class-name targets: wrap in EnumOf so enum values are scored
70+
// against the enum's cases. Without this, tryConvert's default case scores
71+
// any class-name target as `no`, even when the value is a valid enum member.
72+
if (is_a($target, class: \BackedEnum::class, allow_string: true)) {
73+
return EnumOf::fromBackedEnum($target)->coerce($value, state: $state);
74+
}
75+
6876
return self::tryConvert($target, value: $value, state: $state);
6977
}
7078

@@ -78,6 +86,13 @@ public static function dump(Converter|ConverterSource|string $target, mixed $val
7886
return $target::converter()->dump($value, state: $state);
7987
}
8088

89+
// BackedEnum class-name targets: wrap in EnumOf so enum values are scored
90+
// against the enum's cases. Without this, tryConvert's default case scores
91+
// any class-name target as `no`, even when the value is a valid enum member.
92+
if (is_a($target, class: \BackedEnum::class, allow_string: true)) {
93+
return EnumOf::fromBackedEnum($target)->dump($value, state: $state);
94+
}
95+
8196
self::tryConvert($target, value: $value, state: $state);
8297

8398
return self::dump_unknown($value, state: $state);

src/Core/Conversion/EnumOf.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ final class EnumOf implements Converter
1414
{
1515
private readonly string $type;
1616

17+
/** @var array<class-string<\BackedEnum>, self> */
18+
private static array $cache = [];
19+
1720
/**
1821
* @param list<bool|float|int|string|null> $members
1922
*/
@@ -26,6 +29,13 @@ public function __construct(private readonly array $members)
2629
$this->type = $type;
2730
}
2831

32+
/** @param class-string<\BackedEnum> $enum */
33+
public static function fromBackedEnum(string $enum): self
34+
{
35+
// @phpstan-ignore-next-line argument.type
36+
return self::$cache[$enum] ??= new self(array_column($enum::cases(), column_key: 'value'));
37+
}
38+
2939
public function coerce(mixed $value, CoerceState $state): mixed
3040
{
3141
$this->tally($value, state: $state);
@@ -42,9 +52,10 @@ public function dump(mixed $value, DumpState $state): mixed
4252

4353
private function tally(mixed $value, CoerceState|DumpState $state): void
4454
{
45-
if (in_array($value, haystack: $this->members, strict: true)) {
55+
$needle = $value instanceof \BackedEnum ? $value->value : $value;
56+
if (in_array($needle, haystack: $this->members, strict: true)) {
4657
++$state->yes;
47-
} elseif ($this->type === gettype($value)) {
58+
} elseif ($this->type === gettype($needle)) {
4859
++$state->maybe;
4960
} else {
5061
++$state->no;

0 commit comments

Comments
 (0)