Skip to content

Commit ca7a226

Browse files
authored
feat(validation): enhance enum validation (#755)
1 parent 3cca6bc commit ca7a226

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

src/Tempest/Validation/src/Rules/Enum.php

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
use Attribute;
88
use Tempest\Validation\Rule;
99
use UnexpectedValueException;
10+
use UnitEnum;
1011

1112
#[Attribute]
1213
final readonly class Enum implements Rule
1314
{
14-
public function __construct(private string $enum)
15+
public function __construct(private string $enum, private array $only = [], private array $except = [])
1516
{
1617
if (! enum_exists($this->enum)) {
1718
throw new UnexpectedValueException(sprintf(
@@ -23,15 +24,65 @@ public function __construct(private string $enum)
2324

2425
public function isValid(mixed $value): bool
2526
{
26-
if (method_exists($this->enum, 'tryFrom')) {
27-
return $this->enum::tryFrom($value) !== null;
27+
if ($value instanceof $this->enum) {
28+
return $this->isDesirable($value);
2829
}
2930

30-
return defined("$this->enum::{$value}");
31+
return ($enumValue = $this->retrieveEnumValue($value)) !== null && $this->isDesirable($enumValue);
32+
}
33+
34+
/**
35+
* Specify the cases that should be considered valid.
36+
*
37+
* @param UnitEnum|array<UnitEnum> $values
38+
*/
39+
public function only(UnitEnum|array $values): self
40+
{
41+
return new self(
42+
enum: $this->enum,
43+
only: [
44+
...$this->only,
45+
...(is_array($values) ? $values : func_get_args()),
46+
],
47+
);
48+
}
49+
50+
/**
51+
* Specify the cases that should be considered invalid.
52+
*
53+
* @param UnitEnum|array<UnitEnum> $values
54+
*/
55+
public function except(UnitEnum|array $values): self
56+
{
57+
return new self(
58+
enum: $this->enum,
59+
except: [
60+
...$this->except,
61+
...(is_array($values) ? $values : func_get_args()),
62+
],
63+
);
3164
}
3265

3366
public function message(): string
3467
{
3568
return "The value must be a valid enumeration [$this->enum] case";
3669
}
70+
71+
private function isDesirable($value): bool
72+
{
73+
return match (true) {
74+
! empty($this->only) => in_array(needle: $value, haystack: $this->only, strict: true),
75+
! empty($this->except) => ! in_array(needle: $value, haystack: $this->except, strict: true),
76+
default => true,
77+
};
78+
}
79+
80+
private function retrieveEnumValue(mixed $value)
81+
{
82+
if (method_exists($this->enum, 'tryFrom')) {
83+
return $this->enum::tryFrom($value);
84+
}
85+
86+
return defined("$this->enum::{$value}") ? $this->enum::{$value} : null;
87+
}
3788
}

src/Tempest/Validation/tests/Rules/EnumTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,33 @@ public function test_enum_has_to_exist(): void
6262

6363
new Enum('Bob');
6464
}
65+
66+
public function test_validating_only_enums(): void
67+
{
68+
$rule = new Enum(SomeEnum::class);
69+
$this->assertTrue($rule->only(SomeEnum::VALUE_1)->isValid('VALUE_1'));
70+
$this->assertFalse($rule->only(SomeEnum::VALUE_2)->isValid('VALUE_1'));
71+
}
72+
73+
public function test_validating_except_enums(): void
74+
{
75+
$rule = new Enum(SomeEnum::class);
76+
$this->assertTrue($rule->except(SomeEnum::VALUE_2)->isValid('VALUE_1'));
77+
$this->assertFalse($rule->except(SomeEnum::VALUE_1)->isValid('VALUE_1'));
78+
}
79+
80+
public function test_validating_only_backed_enums(): void
81+
{
82+
$rule = new Enum(SomeBackedEnum::class);
83+
$this->assertTrue($rule->only(SomeBackedEnum::Test, SomeBackedEnum::Test2)->isValid('one'));
84+
$this->assertTrue($rule->only(SomeBackedEnum::Test)->only(SomeBackedEnum::Test2)->isValid('one'));
85+
$this->assertFalse($rule->only(SomeBackedEnum::Test2)->isValid('one'));
86+
}
87+
88+
public function test_validating_except_backed_enums(): void
89+
{
90+
$rule = new Enum(SomeBackedEnum::class);
91+
$this->assertTrue($rule->except(SomeBackedEnum::Test2)->isValid('one'));
92+
$this->assertFalse($rule->except(SomeBackedEnum::Test)->isValid('one'));
93+
}
6594
}

0 commit comments

Comments
 (0)