Commit Graph

2 Commits

Author SHA1 Message Date
Romain Canon
2c7e1156db test: improve FakeType and introduce FakeNode 2022-01-06 14:11:42 +01:00
Romain Canon
54f608e5b1 feat!: add access to root node when error occurs during mapping
When an error occurs during mapping, the root instance of `Node` can now
be accessed from the exception. This recursive object allows retrieving
all needed information through the whole mapping tree: path, values,
types and messages, including the issues that caused the exception.

It can be used like the following:

```php
try {
   (new \CuyZ\Valinor\MapperBuilder())
       ->mapper()
       ->map(SomeClass::class, [/* ... */]);
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
    // Do something with `$error->node()`
    // See README for more information
}
```

This change removes the method `MappingError::describe()` which provided
a flattened view of messages of all the errors that were encountered
during the mapping. The same behaviour can still be retrieved, see the
example below:

```php
use CuyZ\Valinor\Mapper\Tree\Message\Message;
use CuyZ\Valinor\Mapper\Tree\Node;

/**
 * @implements \IteratorAggregate<string, array<\Throwable&Message>>
 */
final class MappingErrorList implements \IteratorAggregate
{
    private Node $node;

    public function __construct(Node $node)
    {
        $this->node = $node;
    }

    /**
     * @return \Traversable<string, array<\Throwable&Message>>
     */
    public function getIterator(): \Traversable
    {
        yield from $this->errors($this->node);
    }

    /**
     * @return \Traversable<string, array<\Throwable&Message>>
     */
    private function errors(Node $node): \Traversable
    {
        $errors = array_filter(
            $node->messages(),
            static fn (Message $m) => $m instanceof \Throwable
        );

        if (! empty($errors)) {
            yield $node->path() => array_values($errors);
        }

        foreach ($node->children() as $child) {
            yield from $this->errors($child);
        }
    }
}

try {
   (new \CuyZ\Valinor\MapperBuilder())
       ->mapper()
       ->map(SomeClass::class, [/* ... */]);
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
    $errors = iterator_to_array(new MappingErrorList($error->node()));
}
```

The class `CannotMapObject` is deleted, as it does not provide any
value; this means that `MappingError` which was previously an interface
becomes a class.
2021-12-27 13:52:36 +01:00