Skip to content

Commit

Permalink
feat(validation): enhance enum validation (#755)
Browse files Browse the repository at this point in the history
  • Loading branch information
hungthai1401 authored Nov 20, 2024
1 parent 3cca6bc commit ca7a226
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 4 deletions.
59 changes: 55 additions & 4 deletions src/Tempest/Validation/src/Rules/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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<UnitEnum> $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<UnitEnum> $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;
}
}
29 changes: 29 additions & 0 deletions src/Tempest/Validation/tests/Rules/EnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
}
}

0 comments on commit ca7a226

Please # to comment.