From ca7a226f37c94535d3468d0b20c35f918b6ae240 Mon Sep 17 00:00:00 2001 From: Thai Nguyen Hung Date: Wed, 20 Nov 2024 13:00:35 +0700 Subject: [PATCH] feat(validation): enhance enum validation (#755) --- src/Tempest/Validation/src/Rules/Enum.php | 59 +++++++++++++++++-- .../Validation/tests/Rules/EnumTest.php | 29 +++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/Tempest/Validation/src/Rules/Enum.php b/src/Tempest/Validation/src/Rules/Enum.php index 4237ddaee..4ef325044 100644 --- a/src/Tempest/Validation/src/Rules/Enum.php +++ b/src/Tempest/Validation/src/Rules/Enum.php @@ -7,11 +7,12 @@ use Attribute; use Tempest\Validation\Rule; use UnexpectedValueException; +use UnitEnum; #[Attribute] final readonly class Enum implements Rule { - public function __construct(private string $enum) + public function __construct(private string $enum, private array $only = [], private array $except = []) { if (! enum_exists($this->enum)) { throw new UnexpectedValueException(sprintf( @@ -23,15 +24,65 @@ public function __construct(private string $enum) public function isValid(mixed $value): bool { - if (method_exists($this->enum, 'tryFrom')) { - return $this->enum::tryFrom($value) !== null; + if ($value instanceof $this->enum) { + return $this->isDesirable($value); } - return defined("$this->enum::{$value}"); + return ($enumValue = $this->retrieveEnumValue($value)) !== null && $this->isDesirable($enumValue); + } + + /** + * Specify the cases that should be considered valid. + * + * @param UnitEnum|array $values + */ + public function only(UnitEnum|array $values): self + { + return new self( + enum: $this->enum, + only: [ + ...$this->only, + ...(is_array($values) ? $values : func_get_args()), + ], + ); + } + + /** + * Specify the cases that should be considered invalid. + * + * @param UnitEnum|array $values + */ + public function except(UnitEnum|array $values): self + { + return new self( + enum: $this->enum, + except: [ + ...$this->except, + ...(is_array($values) ? $values : func_get_args()), + ], + ); } public function message(): string { return "The value must be a valid enumeration [$this->enum] case"; } + + private function isDesirable($value): bool + { + return match (true) { + ! empty($this->only) => in_array(needle: $value, haystack: $this->only, strict: true), + ! empty($this->except) => ! in_array(needle: $value, haystack: $this->except, strict: true), + default => true, + }; + } + + private function retrieveEnumValue(mixed $value) + { + if (method_exists($this->enum, 'tryFrom')) { + return $this->enum::tryFrom($value); + } + + return defined("$this->enum::{$value}") ? $this->enum::{$value} : null; + } } diff --git a/src/Tempest/Validation/tests/Rules/EnumTest.php b/src/Tempest/Validation/tests/Rules/EnumTest.php index 10da1000e..bbd8fa667 100644 --- a/src/Tempest/Validation/tests/Rules/EnumTest.php +++ b/src/Tempest/Validation/tests/Rules/EnumTest.php @@ -62,4 +62,33 @@ public function test_enum_has_to_exist(): void new Enum('Bob'); } + + public function test_validating_only_enums(): void + { + $rule = new Enum(SomeEnum::class); + $this->assertTrue($rule->only(SomeEnum::VALUE_1)->isValid('VALUE_1')); + $this->assertFalse($rule->only(SomeEnum::VALUE_2)->isValid('VALUE_1')); + } + + public function test_validating_except_enums(): void + { + $rule = new Enum(SomeEnum::class); + $this->assertTrue($rule->except(SomeEnum::VALUE_2)->isValid('VALUE_1')); + $this->assertFalse($rule->except(SomeEnum::VALUE_1)->isValid('VALUE_1')); + } + + public function test_validating_only_backed_enums(): void + { + $rule = new Enum(SomeBackedEnum::class); + $this->assertTrue($rule->only(SomeBackedEnum::Test, SomeBackedEnum::Test2)->isValid('one')); + $this->assertTrue($rule->only(SomeBackedEnum::Test)->only(SomeBackedEnum::Test2)->isValid('one')); + $this->assertFalse($rule->only(SomeBackedEnum::Test2)->isValid('one')); + } + + public function test_validating_except_backed_enums(): void + { + $rule = new Enum(SomeBackedEnum::class); + $this->assertTrue($rule->except(SomeBackedEnum::Test2)->isValid('one')); + $this->assertFalse($rule->except(SomeBackedEnum::Test)->isValid('one')); + } }