Skip to content

Commit bcaa78b

Browse files
committed
added Expect::enum()
1 parent 8584fa2 commit bcaa78b

3 files changed

Lines changed: 92 additions & 1 deletion

File tree

src/Schema/Expect.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,19 @@ public static function listable(string|Schema $type): Type
175175
->items($type)
176176
->before(fn($value) => is_array($value) || $value === null ? $value : [$value]);
177177
}
178+
179+
180+
/**
181+
* Creates a schema for a backed enum case, given as the case itself or its backing value.
182+
* @param class-string<\BackedEnum> $class
183+
*/
184+
public static function enum(string $class): Type
185+
{
186+
if (!is_subclass_of($class, \BackedEnum::class)) {
187+
throw new Nette\InvalidArgumentException("Class '$class' is not a backed enum.");
188+
}
189+
190+
$backing = (string) (new \ReflectionEnum($class))->getBackingType();
191+
return (new Type("$backing|$class"))->castTo($class);
192+
}
178193
}

src/Schema/Helpers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public static function getCastStrategy(string $type): \Closure
125125
} elseif (is_subclass_of($type, \BackedEnum::class)) {
126126
return static function ($value, Context $context) use ($type) {
127127
try {
128-
return $type::from($value);
128+
return $value === null || $value instanceof $type ? $value : $type::from($value);
129129
} catch (\TypeError | \ValueError) {
130130
$context->addError(
131131
'The %label% %path% expects to be %expected%, %value% given.',

tests/Schema/Expect.enum.phpt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,79 @@ test('unit enum as standalone type', function () {
2525
(new Processor)->process($schema, 'Clubs');
2626
}, ['The item expects to be Suit, \'Clubs\' given.']);
2727
});
28+
29+
30+
enum Color: string
31+
{
32+
case Red = 'red';
33+
case Blue = 'blue';
34+
}
35+
36+
enum Level: int
37+
{
38+
case Low = 1;
39+
case High = 2;
40+
}
41+
42+
43+
test('backing value is cast to a case', function () {
44+
Assert::same(Color::Red, (new Processor)->process(Expect::enum(Color::class), 'red'));
45+
Assert::same(Level::High, (new Processor)->process(Expect::enum(Level::class), 2));
46+
});
47+
48+
49+
test('case instance passes through', function () {
50+
Assert::same(Color::Blue, (new Processor)->process(Expect::enum(Color::class), Color::Blue));
51+
});
52+
53+
54+
test('invalid value lists the allowed ones', function () {
55+
checkValidationErrors(function () {
56+
(new Processor)->process(Expect::enum(Color::class), 'green');
57+
}, ["The item expects to be 'red'|'blue', 'green' given."]);
58+
59+
checkValidationErrors(function () {
60+
(new Processor)->process(Expect::enum(Level::class), 3);
61+
}, ['The item expects to be 1|2, 3 given.']);
62+
});
63+
64+
65+
test('wrong type is rejected before casting', function () {
66+
checkValidationErrors(function () {
67+
(new Processor)->process(Expect::enum(Color::class), []);
68+
}, ['The item expects to be string or Color, array given.']);
69+
});
70+
71+
72+
test('default and nullable', function () {
73+
$schema = Expect::structure([
74+
'color' => Expect::enum(Color::class)->default(Color::Red),
75+
'level' => Expect::enum(Level::class)->nullable(),
76+
]);
77+
78+
Assert::equal(
79+
(object) ['color' => Color::Red, 'level' => null],
80+
(new Processor)->process($schema, []),
81+
);
82+
83+
Assert::equal(
84+
(object) ['color' => Color::Blue, 'level' => null],
85+
(new Processor)->process($schema, ['color' => 'blue', 'level' => null]),
86+
);
87+
});
88+
89+
90+
testException(
91+
'pure enum is rejected',
92+
fn() => Expect::enum(Suit::class),
93+
Nette\InvalidArgumentException::class,
94+
"Class 'Suit' is not a backed enum.",
95+
);
96+
97+
98+
testException(
99+
'ordinary class is rejected',
100+
fn() => Expect::enum(stdClass::class),
101+
Nette\InvalidArgumentException::class,
102+
"Class 'stdClass' is not a backed enum.",
103+
);

0 commit comments

Comments
 (0)