Valinor/tests/Integration/Mapping/Object/ShapedArrayValuesMappingTest.php

144 lines
5.3 KiB
PHP
Raw Normal View History

2021-11-28 17:43:02 +01:00
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
2021-11-28 17:43:02 +01:00
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use stdClass;
final class ShapedArrayValuesMappingTest extends IntegrationTest
{
public function test_values_are_mapped_properly(): void
{
$source = [
'basicShapedArrayWithExcessiveKey' => [
'foo' => 'foo',
'bar' => 42,
],
2021-11-29 12:15:26 +01:00
'basicShapedArrayWithStringKeys' => [
'foo' => 'fiz',
'bar' => 42,
],
'basicShapedArrayWithIntegerKeys' => [
0 => 'fiz',
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-16 00:00:45 +01:00
1 => 42.404,
2021-11-28 17:43:02 +01:00
],
'shapedArrayWithObject' => [
'foo' => ['value' => 'bar'],
],
'shapedArrayWithOptionalValue' => [
'optionalString' => 'some value',
],
'shapedArrayOnSeveralLines' => [
'foo' => 'fiz',
'bar' => 42,
],
2021-11-28 17:43:02 +01:00
'advancedShapedArray' => [
'mandatoryString' => 'bar',
1337,
'42.404',
],
];
foreach ([ShapedArrayValues::class, ShapedArrayValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
self::assertSame(['foo' => 'foo'], $result->basicShapedArrayWithExcessiveKey);
2021-11-29 12:15:26 +01:00
self::assertSame($source['basicShapedArrayWithStringKeys'], $result->basicShapedArrayWithStringKeys);
self::assertSame($source['basicShapedArrayWithIntegerKeys'], $result->basicShapedArrayWithIntegerKeys);
2021-11-28 17:43:02 +01:00
self::assertInstanceOf(SimpleObject::class, $result->shapedArrayWithObject['foo']);
self::assertSame($source['shapedArrayWithOptionalValue'], $result->shapedArrayWithOptionalValue);
self::assertSame($source['shapedArrayOnSeveralLines'], $result->shapedArrayOnSeveralLines);
2021-11-28 17:43:02 +01:00
self::assertSame('bar', $result->advancedShapedArray['mandatoryString']);
self::assertSame(1337, $result->advancedShapedArray[0]);
self::assertSame(42.404, $result->advancedShapedArray[1]);
}
}
public function test_value_that_cannot_be_casted_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(ShapedArrayValues::class, [
2021-11-29 12:15:26 +01:00
'basicShapedArrayWithStringKeys' => [
'foo' => new stdClass(),
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-16 00:00:45 +01:00
'bar' => 42,
2021-11-29 12:15:26 +01:00
],
2021-11-28 17:43:02 +01:00
]);
} catch (MappingError $exception) {
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-16 00:00:45 +01:00
$error = $exception->node()->children()['basicShapedArrayWithStringKeys']->children()['foo']->messages()[0];
self::assertSame('1618736242', $error->code());
self::assertSame('Cannot cast value of type `stdClass` to `string`.', (string)$error);
2021-11-28 17:43:02 +01:00
}
}
}
class ShapedArrayValues
{
/** @var array{foo: string} */
public array $basicShapedArrayWithExcessiveKey;
2021-11-29 12:15:26 +01:00
/** @var array{foo: string, bar: int} */
public array $basicShapedArrayWithStringKeys;
/** @var array{0: string, 1: float} */
public array $basicShapedArrayWithIntegerKeys;
2021-11-28 17:43:02 +01:00
/** @var array{foo: SimpleObject} */
public array $shapedArrayWithObject;
/** @var array{optionalString?: string} */
public array $shapedArrayWithOptionalValue;
/**
* @var array{
* foo: string,
* bar: int
* }
*/
public array $shapedArrayOnSeveralLines;
2021-11-29 12:15:26 +01:00
/** @var array{0: int, float, optionalString?: string, mandatoryString: string} */
2021-11-28 17:43:02 +01:00
public array $advancedShapedArray;
}
class ShapedArrayValuesWithConstructor extends ShapedArrayValues
{
/**
* @param array{foo: string} $basicShapedArrayWithExcessiveKey
2021-11-29 12:15:26 +01:00
* @param array{foo: string, bar: int} $basicShapedArrayWithStringKeys
* @param array{0: string, 1: float} $basicShapedArrayWithIntegerKeys
2021-11-28 17:43:02 +01:00
* @param array{foo: SimpleObject} $shapedArrayWithObject
* @param array{optionalString?: string} $shapedArrayWithOptionalValue
* @param array{
* foo: string,
* bar: int
* } $shapedArrayOnSeveralLines
2021-11-29 12:15:26 +01:00
* @param array{0: int, float, optionalString?: string, mandatoryString: string} $advancedShapedArray
2021-11-28 17:43:02 +01:00
*/
public function __construct(
array $basicShapedArrayWithExcessiveKey,
2021-11-29 12:15:26 +01:00
array $basicShapedArrayWithStringKeys,
array $basicShapedArrayWithIntegerKeys,
2021-11-28 17:43:02 +01:00
array $shapedArrayWithObject,
array $shapedArrayWithOptionalValue,
array $shapedArrayOnSeveralLines,
2021-11-28 17:43:02 +01:00
array $advancedShapedArray
) {
$this->basicShapedArrayWithExcessiveKey = $basicShapedArrayWithExcessiveKey;
2021-11-29 12:15:26 +01:00
$this->basicShapedArrayWithStringKeys = $basicShapedArrayWithStringKeys;
$this->basicShapedArrayWithIntegerKeys = $basicShapedArrayWithIntegerKeys;
2021-11-28 17:43:02 +01:00
$this->shapedArrayWithObject = $shapedArrayWithObject;
$this->shapedArrayWithOptionalValue = $shapedArrayWithOptionalValue;
$this->shapedArrayOnSeveralLines = $shapedArrayOnSeveralLines;
2021-11-28 17:43:02 +01:00
$this->advancedShapedArray = $advancedShapedArray;
}
}