Skip to content

Commit

Permalink
Introduce nested type exceptions with paths
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Mar 28, 2024
1 parent 6559c40 commit db953bd
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 46 deletions.
1 change: 0 additions & 1 deletion src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,6 @@ final class Loader
'Psl\\Type\\Internal\\LiteralScalarType' => 'Psl/Type/Internal/LiteralScalarType.php',
'Psl\\Type\\Internal\\BackedEnumType' => 'Psl/Type/Internal/BackedEnumType.php',
'Psl\\Type\\Internal\\UnitEnumType' => 'Psl/Type/Internal/UnitEnumType.php',
'Psl\\Type\\Exception\\TypeTrace' => 'Psl/Type/Exception/TypeTrace.php',
'Psl\\Type\\Exception\\AssertException' => 'Psl/Type/Exception/AssertException.php',
'Psl\\Type\\Exception\\CoercionException' => 'Psl/Type/Exception/CoercionException.php',
'Psl\\Type\\Exception\\Exception' => 'Psl/Type/Exception/Exception.php',
Expand Down
25 changes: 22 additions & 3 deletions src/Psl/Type/Exception/AssertException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,31 @@
namespace Psl\Type\Exception;

use Psl\Str;
use Psl\Vec;
use Throwable;

use function get_debug_type;

final class AssertException extends Exception
{
private string $expected;

public function __construct(string $actual, string $expected)
/**
* @param list<string> $paths
*/
public function __construct(string $actual, string $expected, array $paths = [], ?Throwable $previous = null)
{
parent::__construct(Str\format('Expected "%s", got "%s".', $expected, $actual), $actual);
parent::__construct(
Str\format(
'Expected "%s", got "%s"%s.',
$expected,
$actual,
$paths ? ' at path "' . Str\join($paths, '.') . '"' : ''
),
$actual,
$paths,
$previous
);

$this->expected = $expected;
}
Expand All @@ -27,7 +42,11 @@ public function getExpectedType(): string
public static function withValue(
mixed $value,
string $expected_type,
?string $path = null,
?Throwable $previous = null
): self {
return new self(get_debug_type($value), $expected_type);
$paths = $previous instanceof Exception ? [$path, ...$previous->getPaths()] : [$path];

return new self(get_debug_type($value), $expected_type, Vec\filter_nulls($paths), $previous);
}
}
29 changes: 13 additions & 16 deletions src/Psl/Type/Exception/CoercionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Psl\Type\Exception;

use Psl\Str;
use Psl\Vec;
use Throwable;

use function get_debug_type;
Expand All @@ -13,17 +14,21 @@ final class CoercionException extends Exception
{
private string $target;

public function __construct(string $actual, string $target, string $additionalInfo = '')
/**
* @param list<string> $paths
*/
public function __construct(string $actual, string $target, array $paths = [], ?Throwable $previous = null)
{
parent::__construct(
Str\format(
'Could not coerce "%s" to type "%s"%s%s',
'Could not coerce "%s" to type "%s"%s.',
$actual,
$target,
$additionalInfo ? ': ' : '.',
$additionalInfo
$paths ? ' at path "' . Str\join($paths, '.') . '"' : ''
),
$actual,
$paths,
$previous
);

$this->target = $target;
Expand All @@ -37,19 +42,11 @@ public function getTargetType(): string
public static function withValue(
mixed $value,
string $target,
?string $path = null,
?Throwable $previous = null
): self {
return new self(get_debug_type($value), $target);
}
$paths = $previous instanceof Exception ? [$path, ...$previous->getPaths()] : [$path];

public static function withConversionFailureOnValue(
mixed $value,
string $target,
Throwable $failure,
): self {
return new self(
get_debug_type($value),
$target,
$failure->getMessage()
);
return new self(get_debug_type($value), $target, Vec\filter_nulls($paths), $previous);
}
}
24 changes: 22 additions & 2 deletions src/Psl/Type/Exception/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,38 @@
namespace Psl\Type\Exception;

use Psl\Exception\RuntimeException;
use Throwable;

abstract class Exception extends RuntimeException implements ExceptionInterface
{
private string $actual;

/**
* @var list<string>
*/
private array $paths;

/**
* @param list<string> $paths
*/
public function __construct(
string $message,
string $actual,
array $paths,
?Throwable $previous = null
) {
parent::__construct($message);
parent::__construct($message, 0, $previous);

$this->paths = $paths;
$this->actual = $actual;
}

$this->actual = $actual;
/**
* @return list<string>
*/
public function getPaths(): array
{
return $this->paths;
}

public function getActualType(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Psl/Type/Internal/ConvertedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function coerce(mixed $value): mixed
try {
$converted = ($this->converter)($coercedInput);
} catch (Throwable $failure) {
throw CoercionException::withConversionFailureOnValue($value, $this->toString(), $failure);
throw CoercionException::withValue($value, $this->toString(), previous: $failure);
}

return $this->into->coerce($converted);
Expand Down
34 changes: 28 additions & 6 deletions src/Psl/Type/Internal/DictType.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,28 @@ public function coerce(mixed $value): array
throw CoercionException::withValue($value, $this->toString());
}

$result = [];
$key_type = $this->key_type;
$value_type = $this->value_type;

$result = [];

/**
* @var Tk $k
* @var Tv $v
*/
foreach ($value as $k => $v) {
$result[$key_type->coerce($k)] = $value_type->coerce($v);
try {
$k_result = $key_type->coerce($k);
} catch (CoercionException $e) {
throw CoercionException::withValue($k, $this->toString(), 'key(' . (string) $k . ')', $e);
}

try {
$v_result = $value_type->coerce($v);
} catch (CoercionException $e) {
throw CoercionException::withValue($v, $this->toString(), (string) $k, $e);
}

$result[$k_result] = $v_result;
}

return $result;
Expand All @@ -71,17 +82,28 @@ public function assert(mixed $value): array
throw AssertException::withValue($value, $this->toString());
}

$result = [];
$key_type = $this->key_type;
$value_type = $this->value_type;

$result = [];

/**
* @var Tk $k
* @var Tv $v
*/
foreach ($value as $k => $v) {
$result[$key_type->assert($k)] = $value_type->assert($v);
try {
$k_result = $key_type->assert($k);
} catch (AssertException $e) {
throw AssertException::withValue($k, $this->toString(), 'key(' . (string) $k . ')', $e);
}

try {
$v_result = $value_type->assert($v);
} catch (AssertException $e) {
throw AssertException::withValue($v, $this->toString(), (string) $k, $e);
}

$result[$k_result] = $v_result;
}

return $result;
Expand Down
21 changes: 17 additions & 4 deletions src/Psl/Type/Internal/ShapeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ private function coerceIterable(mixed $value): array
$result = [];
foreach ($this->elements_types as $element => $type) {
if (Iter\contains_key($array, $element)) {
$result[$element] = $type->coerce($array[$element]);
try {
$e_result = $type->coerce($array[$element]);
} catch (CoercionException $e) {
throw CoercionException::withValue($array[$element], $this->toString(), (string) $element, $e);
}

$result[$element] = $e_result;

continue;
}
Expand All @@ -128,7 +134,7 @@ private function coerceIterable(mixed $value): array
continue;
}

throw CoercionException::withValue($value, $this->toString());
throw CoercionException::withValue($value, $this->toString(), (string) $element);
}

if ($this->allow_unknown_fields) {
Expand Down Expand Up @@ -159,7 +165,13 @@ public function assert(mixed $value): array
$result = [];
foreach ($this->elements_types as $element => $type) {
if (Iter\contains_key($value, $element)) {
$result[$element] = $type->assert($value[$element]);
try {
$e_result = $type->assert($value[$element]);
} catch (AssertException $e) {
throw AssertException::withValue($value[$element], $this->toString(), (string) $element, $e);
}

$result[$element] = $e_result;

continue;
}
Expand All @@ -168,7 +180,7 @@ public function assert(mixed $value): array
continue;
}

throw AssertException::withValue($value, $this->toString());
throw AssertException::withValue($value, $this->toString(), (string) $element);
}

/**
Expand All @@ -183,6 +195,7 @@ public function assert(mixed $value): array
throw AssertException::withValue(
$value,
$this->toString(),
(string) $k
);
}
}
Expand Down
35 changes: 24 additions & 11 deletions src/Psl/Type/Internal/VecType.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,24 @@ public function coerce(mixed $value): iterable
throw CoercionException::withValue($value, $this->toString());
}

/** @var Type\Type<Tv> $value_type */
$value_type = $this->value_type;

/**
* @var list<Tv> $entries
*/
$result = [];
$value_type = $this->value_type;

/** @var Tv $v */
foreach ($value as $v) {
$result[] = $value_type->coerce($v);
/**
* @var Tv $v
* @var array-key $i
*/
foreach ($value as $i => $v) {
try {
$v_result = $value_type->coerce($v);
} catch (CoercionException $e) {
throw CoercionException::withValue($v, $this->toString(), (string) $i, $e);
}

$result[] = $v_result;
}

return $result;
Expand All @@ -87,16 +94,22 @@ public function assert(mixed $value): array
throw AssertException::withValue($value, $this->toString());
}

/** @var Type\Type<Tv> $value_type */
$value_type = $this->value_type;

$result = [];
$value_type = $this->value_type;

/**
* @var Tv $v
*
* @param array-key $i
*/
foreach ($value as $v) {
$result[] = $value_type->assert($v);
foreach ($value as $i => $v) {
try {
$v_result = $value_type->coerce($v);
} catch (AssertException $e) {
throw AssertException::withValue($v, $this->toString(), (string) $i, $e);
}

$result[] = $v_result;
}

return $result;
Expand Down
1 change: 0 additions & 1 deletion tests/unit/Type/Exception/TypeAssertExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use PHPUnit\Framework\TestCase;
use Psl\Collection;
use Psl\Iter;
use Psl\Str;
use Psl\Type;

Expand Down
1 change: 0 additions & 1 deletion tests/unit/Type/Exception/TypeCoercionExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use PHPUnit\Framework\TestCase;
use Psl\Collection;
use Psl\Iter;
use Psl\Str;
use Psl\Type;
use RuntimeException;
Expand Down

0 comments on commit db953bd

Please sign in to comment.