Skip to content

Commit

Permalink
add Shape::optional()
Browse files Browse the repository at this point in the history
  • Loading branch information
Baptouuuu committed Nov 22, 2023
1 parent 35ce7af commit 7fdd954
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 10 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,7 @@
# Changelog

## [Unreleased]

### Added

- `Innmind\Validation\Shape::optional()`
29 changes: 29 additions & 0 deletions proofs/shape.php
Expand Up @@ -103,4 +103,33 @@ static function($assert) {
);
},
);

yield test(
'Shape with optional key',
static function($assert) {
$assert->true(
Shape::of('foo', Is::int())
->with('bar', Is::bool())
->optional('bar')
->asPredicate()([
'foo' => 42,
]),
);
$assert->same(
[
'foo' => 42,
],
Shape::of('foo', Is::int())
->with('bar', Is::bool())
->optional('bar')([
'foo' => 42,
'baz' => 'invalid',
])
->match(
static fn($value) => $value,
static fn() => null,
),
);
},
);
};
50 changes: 40 additions & 10 deletions src/Shape.php
Expand Up @@ -16,32 +16,51 @@ final class Shape implements Constraint
{
/** @var non-empty-array<non-empty-string, Constraint<mixed, mixed>> */
private array $constraints;
/** @var list<non-empty-string> */
private array $optional;

/**
* @param non-empty-array<non-empty-string, Constraint<mixed, mixed>> $constraints
* @param list<non-empty-string> $optional
*/
private function __construct(array $constraints)
private function __construct(array $constraints, array $optional)
{
$this->constraints = $constraints;
$this->optional = $optional;
}

public function __invoke(mixed $value): Validation
{
$optional = new \stdClass;
/** @var Validation<Failure, non-empty-array<non-empty-string, mixed>> */
$validation = Validation::success([]);

foreach ($this->constraints as $key => $constraint) {
$keyValidation = Has::key($key);

if (\in_array($key, $this->optional, true)) {
/** @psalm-suppress MixedArgumentTypeCoercion */
$keyValidation = $keyValidation->or(Of::callable(
static fn() => Validation::success($optional),
));
}

$ofType = Of::callable(
static fn($value) => $constraint($value)->mapFailures(
static fn($failure) => $failure->under($key),
),
static fn($value) => match ($value) {
$optional => Validation::success($optional),
default => $constraint($value)->mapFailures(
static fn($failure) => $failure->under($key),
),
},
);

$validation = $validation->and(
Has::key($key)->and($ofType)($value),
static function($array, $value) use ($key) {
/** @psalm-suppress MixedAssignment */
$array[$key] = $value;
$keyValidation->and($ofType)($value),
static function($array, $value) use ($key, $optional) {
if ($value !== $optional) {
/** @psalm-suppress MixedAssignment */
$array[$key] = $value;
}

return $array;
},
Expand All @@ -58,7 +77,7 @@ static function($array, $value) use ($key) {
*/
public static function of(string $key, Constraint $constraint): self
{
return new self([$key => $constraint]);
return new self([$key => $constraint], []);
}

/**
Expand All @@ -69,7 +88,18 @@ public function with(string $key, Constraint $constraint): self
$constraints = $this->constraints;
$constraints[$key] = $constraint;

return new self($constraints);
return new self($constraints, $this->optional);
}

/**
* @param non-empty-string $key
*/
public function optional(string $key): self
{
$optional = $this->optional;
$optional[] = $key;

return new self($this->constraints, $optional);
}

public function and(Constraint $constraint): Constraint
Expand Down

0 comments on commit 7fdd954

Please sign in to comment.