From 660ea40bee5152d916e41df302606093964c6c6f Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 24 Mar 2024 10:44:24 +0100 Subject: [PATCH] make Shape accept mixed as input --- CHANGELOG.md | 1 + proofs/shape.php | 32 ++++++++++++++++++ src/Is.php | 6 ++-- src/Shape.php | 84 ++++++++++++++++++++++++++---------------------- 4 files changed, 81 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d121a..559b776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Changed - `Is::array()->and(Is::list())` has been shortened to `Is::list()` +- `Is::array()->and(Shape::of(...$args))` has been shortened to `Shape::of(...$args)` ## 1.3.0 - 2024-03-05 diff --git a/proofs/shape.php b/proofs/shape.php index eb82006..888deeb 100644 --- a/proofs/shape.php +++ b/proofs/shape.php @@ -5,6 +5,7 @@ Shape, Is, }; +use Innmind\BlackBox\Set; return static function() { yield test( @@ -175,4 +176,35 @@ static function($assert) { ); }, ); + + yield proof( + 'Shape validates non arrays', + given( + Set\Either::any( + Set\Strings::any(), + Set\Integers::any(), + Set\RealNumbers::any(), + Set\Elements::of( + true, + false, + new stdClass, + ), + ), + ), + static function($assert, $value) { + $assert->same( + [['$', 'Value is not of type array']], + Shape::of('bar', Is::int())($value) + ->match( + static fn() => null, + static fn($failures) => $failures + ->map(static fn($failure) => [ + $failure->path()->toString(), + $failure->message(), + ]) + ->toList(), + ), + ); + }, + ); }; diff --git a/src/Is.php b/src/Is.php index 6ef0b64..02e7c90 100644 --- a/src/Is.php +++ b/src/Is.php @@ -132,12 +132,10 @@ public static function list(Constraint $each = null): Constraint * @psalm-pure * * @param non-empty-string $key - * - * @return Constraint> */ - public static function shape(string $key, Constraint $constraint): Constraint + public static function shape(string $key, Constraint $constraint): Shape { - return self::array()->and(Shape::of($key, $constraint)); + return Shape::of($key, $constraint); } public function and(Constraint $constraint): Constraint diff --git a/src/Shape.php b/src/Shape.php index d2b255c..d3839c4 100644 --- a/src/Shape.php +++ b/src/Shape.php @@ -9,7 +9,7 @@ }; /** - * @implements Constraint> + * @implements Constraint> * @psalm-immutable */ final class Shape implements Constraint @@ -31,43 +31,7 @@ private function __construct(array $constraints, array $optional) public function __invoke(mixed $value): Validation { - $optional = new \stdClass; - /** @var Validation> */ - $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) => match ($value) { - $optional => Validation::success($optional), - default => $constraint($value)->mapFailures( - static fn($failure) => $failure->under($key), - ), - }, - ); - - $validation = $validation->and( - $keyValidation->and($ofType)($value), - static function($array, $value) use ($key, $optional) { - if ($value !== $optional) { - /** @psalm-suppress MixedAssignment */ - $array[$key] = $value; - } - - return $array; - }, - ); - } - - return $validation; + return Is::array()($value)->flatMap($this->validate(...)); } /** @@ -126,4 +90,48 @@ public function asPredicate(): PredicateInterface { return Predicate::of($this); } + + /** + * @return Validation> + */ + private function validate(array $value): Validation + { + $optional = new \stdClass; + /** @var Validation> */ + $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) => match ($value) { + $optional => Validation::success($optional), + default => $constraint($value)->mapFailures( + static fn($failure) => $failure->under($key), + ), + }, + ); + + $validation = $validation->and( + $keyValidation->and($ofType)($value), + static function($array, $value) use ($key, $optional) { + if ($value !== $optional) { + /** @psalm-suppress MixedAssignment */ + $array[$key] = $value; + } + + return $array; + }, + ); + } + + return $validation; + } }