From 9c1e7c928b5ae518d34e50576e90568110662fc6 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Tue, 26 Jul 2022 22:55:32 +0200 Subject: [PATCH] feat: display more information in mapping error message The message will now display the source and the number of errors, and even the original error message if only one error was encountered. --- src/Mapper/MappingError.php | 21 ++++++-- src/Mapper/Tree/Message/MessagesFlattener.php | 6 +-- .../Integration/Mapping/MappingErrorTest.php | 51 +++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 tests/Integration/Mapping/MappingErrorTest.php diff --git a/src/Mapper/MappingError.php b/src/Mapper/MappingError.php index 8ca3995..da0f923 100644 --- a/src/Mapper/MappingError.php +++ b/src/Mapper/MappingError.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace CuyZ\Valinor\Mapper; +use CuyZ\Valinor\Mapper\Tree\Message\MessagesFlattener; use CuyZ\Valinor\Mapper\Tree\Node; +use CuyZ\Valinor\Utility\ValueDumper; use RuntimeException; /** @api */ @@ -16,10 +18,21 @@ final class MappingError extends RuntimeException { $this->node = $node; - parent::__construct( - "Could not map type `{$node->type()}` with the given source.", - 1617193185 - ); + $source = ValueDumper::dump($node->sourceValue()); + + $errors = (new MessagesFlattener($node))->errors(); + $errorsCount = count($errors); + + $body = "Could not map type `{$node->type()}` with value $source. A total of $errorsCount errors were encountered."; + + if ($errorsCount === 1) { + $body = $errors->getIterator()->current() + ->withParameter('root_type', $node->type()) + ->withBody("Could not map type `{root_type}`. An error occurred at path {node_path}: {original_message}") + ->toString(); + } + + parent::__construct($body, 1617193185); } public function node(): Node diff --git a/src/Mapper/Tree/Message/MessagesFlattener.php b/src/Mapper/Tree/Message/MessagesFlattener.php index 65efa8e..90aeb0d 100644 --- a/src/Mapper/Tree/Message/MessagesFlattener.php +++ b/src/Mapper/Tree/Message/MessagesFlattener.php @@ -7,8 +7,8 @@ namespace CuyZ\Valinor\Mapper\Tree\Message; use Countable; use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\NodeTraverser; +use Iterator; use IteratorAggregate; -use Traversable; use function array_filter; use function count; @@ -66,9 +66,9 @@ final class MessagesFlattener implements IteratorAggregate, Countable } /** - * @return Traversable + * @return Iterator */ - public function getIterator(): Traversable + public function getIterator(): Iterator { yield from $this->messages; } diff --git a/tests/Integration/Mapping/MappingErrorTest.php b/tests/Integration/Mapping/MappingErrorTest.php new file mode 100644 index 0000000..eff3499 --- /dev/null +++ b/tests/Integration/Mapping/MappingErrorTest.php @@ -0,0 +1,51 @@ +mapper()->map('string', ['foo']); + + self::fail(); + } catch (MappingError $exception) { + self::assertSame(1617193185, $exception->getCode()); + } + } + + public function test_single_error_details_are_reported_in_exception_message(): void + { + try { + (new MapperBuilder())->mapper()->map('string', ['foo']); + + self::fail(); + } catch (MappingError $exception) { + self::assertSame("Could not map type `string`. An error occurred at path *root*: Value array{0: 'foo'} does not match type `string`.", $exception->getMessage()); + } + } + + public function test_several_errors_count_are_reported_in_exception_message(): void + { + try { + (new MapperBuilder())->mapper()->map( + 'array{foo: string, bar: int}', + ['foo' => 42, 'bar' => 'some string'] + ); + + self::fail(); + } catch (MappingError $exception) { + self::assertSame( + "Could not map type `array{foo: string, bar: int}` with value array{foo: 42, bar: 'some string'}. A total of 2 errors were encountered.", + $exception->getMessage() + ); + } + } +}