From 891509619c6dc57b18044661cdf664d53edf0d17 Mon Sep 17 00:00:00 2001 From: Ivan Kurnosov Date: Thu, 7 Nov 2024 09:54:38 +1300 Subject: [PATCH] Fix nested missing values --- src/Psl/Type/Exception/CoercionException.php | 21 +++++++++++++++++--- src/Psl/Type/Internal/ShapeType.php | 2 +- tests/unit/Type/ShapeTypeTest.php | 11 ++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Psl/Type/Exception/CoercionException.php b/src/Psl/Type/Exception/CoercionException.php index cf2286cb..128b10fc 100644 --- a/src/Psl/Type/Exception/CoercionException.php +++ b/src/Psl/Type/Exception/CoercionException.php @@ -14,10 +14,12 @@ final class CoercionException extends Exception { private string $target; + private bool $withValue; + /** * @param list $paths */ - private function __construct(?string $actual, string $target, string $message, array $paths = [], ?Throwable $previous = null) + private function __construct(?string $actual, string $target, string $message, bool $withValue, array $paths = [], ?Throwable $previous = null) { parent::__construct( $message, @@ -27,6 +29,7 @@ private function __construct(?string $actual, string $target, string $message, a ); $this->target = $target; + $this->withValue = $withValue; } public function getTargetType(): string @@ -52,7 +55,7 @@ public static function withValue( $previous && !$previous instanceof self ? ': ' . $previous->getMessage() : '', ); - return new self($actual, $target, $message, $paths, $previous); + return new self($actual, $target, $message, true, $paths, $previous); } public static function withoutValue( @@ -69,6 +72,18 @@ public static function withoutValue( $previous && !$previous instanceof self ? ': ' . $previous->getMessage() : '', ); - return new self(null, $target, $message, $paths, $previous); + return new self(null, $target, $message, false, $paths, $previous); + } + + public function wrap( + mixed $value, + string $target, + ?string $path = null + ): self { + if ($this->withValue) { + return self::withValue($value, $target, $path, $this); + } + + return self::withoutValue($target, $path, $this); } } diff --git a/src/Psl/Type/Internal/ShapeType.php b/src/Psl/Type/Internal/ShapeType.php index ead79632..6116861a 100644 --- a/src/Psl/Type/Internal/ShapeType.php +++ b/src/Psl/Type/Internal/ShapeType.php @@ -148,7 +148,7 @@ private function coerceIterable(mixed $value): array } } catch (CoercionException $e) { throw match (true) { - $element_value_found => CoercionException::withValue($array[$element] ?? null, $this->toString(), PathExpression::path($element), $e), + $element_value_found => $e->wrap($array[$element] ?? null, $this->toString(), PathExpression::path($element)), default => $e }; } diff --git a/tests/unit/Type/ShapeTypeTest.php b/tests/unit/Type/ShapeTypeTest.php index e7c1512f..10cf077d 100644 --- a/tests/unit/Type/ShapeTypeTest.php +++ b/tests/unit/Type/ShapeTypeTest.php @@ -249,6 +249,17 @@ public static function provideCoerceExceptionExpectations(): iterable [], 'Could not coerce to type "array{\'name\': string}" at path "name" as the value was not passed.' ]; + yield 'missing nested key' => [ + Type\shape([ + 'a' => Type\shape([ + 'b' => Type\shape([ + 'c' => Type\mixed(), + ]), + ]), + ]), + ['a' => ['b' => []]], + 'Could not coerce to type "array{\'a\': array{\'b\': array{\'c\': mixed}}}" at path "a.b.c" as the value was not passed.', + ]; yield 'invalid key' => [ Type\shape([ 'name' => Type\string(),