2021-11-28 17:43:02 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2021-12-29 00:09:34 +01:00
|
|
|
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 DateTime;
|
|
|
|
use DateTimeImmutable;
|
|
|
|
use DateTimeInterface;
|
|
|
|
use stdClass;
|
|
|
|
use stdClass as ObjectAlias;
|
|
|
|
|
|
|
|
final class ScalarValuesMappingTest extends IntegrationTest
|
|
|
|
{
|
|
|
|
public function test_values_are_mapped_properly(): void
|
|
|
|
{
|
|
|
|
$source = [
|
|
|
|
'boolean' => true,
|
|
|
|
'float' => 42.404,
|
|
|
|
'integer' => 1337,
|
|
|
|
'positiveInteger' => 1337,
|
|
|
|
'negativeInteger' => -1337,
|
2021-12-06 13:14:54 +01:00
|
|
|
'integerRangeWithPositiveValue' => 1337,
|
|
|
|
'integerRangeWithNegativeValue' => -1337,
|
|
|
|
'integerRangeWithMinAndMax' => 42,
|
2021-11-29 11:33:23 +01:00
|
|
|
'integerValue' => 42,
|
2021-11-28 17:43:02 +01:00
|
|
|
'string' => 'foo',
|
|
|
|
'nonEmptyString' => 'bar',
|
2021-11-29 11:33:23 +01:00
|
|
|
'stringValueWithSingleQuote' => 'baz',
|
|
|
|
'stringValueWithDoubleQuote' => 'fiz',
|
2021-11-28 17:43:02 +01:00
|
|
|
'classString' => self::class,
|
|
|
|
'classStringOfDateTime' => DateTimeImmutable::class,
|
|
|
|
'classStringOfAlias' => stdClass::class,
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ([ScalarValues::class, ScalarValuesWithConstructor::class] as $class) {
|
|
|
|
try {
|
|
|
|
$result = $this->mapperBuilder->mapper()->map($class, $source);
|
|
|
|
} catch (MappingError $error) {
|
|
|
|
$this->mappingFail($error);
|
|
|
|
}
|
|
|
|
|
|
|
|
self::assertSame(true, $result->boolean);
|
|
|
|
self::assertSame(42.404, $result->float);
|
|
|
|
self::assertSame(1337, $result->integer);
|
|
|
|
self::assertSame(1337, $result->positiveInteger);
|
|
|
|
self::assertSame(-1337, $result->negativeInteger);
|
2021-12-06 13:14:54 +01:00
|
|
|
self::assertSame(1337, $result->integerRangeWithPositiveValue);
|
|
|
|
self::assertSame(-1337, $result->integerRangeWithNegativeValue);
|
|
|
|
self::assertSame(42, $result->integerRangeWithMinAndMax);
|
2021-11-29 11:33:23 +01:00
|
|
|
self::assertSame(42, $result->integerValue);
|
2021-11-28 17:43:02 +01:00
|
|
|
self::assertSame('foo', $result->string);
|
|
|
|
self::assertSame('bar', $result->nonEmptyString);
|
2021-11-29 11:33:23 +01:00
|
|
|
self::assertSame('baz', $result->stringValueWithSingleQuote);
|
|
|
|
self::assertSame('fiz', $result->stringValueWithDoubleQuote);
|
2021-11-28 17:43:02 +01:00
|
|
|
self::assertSame(self::class, $result->classString);
|
|
|
|
self::assertSame(DateTimeImmutable::class, $result->classStringOfDateTime);
|
|
|
|
self::assertSame(stdClass::class, $result->classStringOfAlias);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_value_that_cannot_be_casted_throws_exception(): void
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->mapperBuilder->mapper()->map(SimpleObject::class, [
|
|
|
|
'value' => new stdClass(),
|
|
|
|
]);
|
|
|
|
} 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()['value']->messages()[0];
|
|
|
|
|
2021-12-31 13:10:20 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_empty_mandatory_value_throws_exception(): void
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->mapperBuilder->mapper()->map(SimpleObject::class, [
|
|
|
|
'value' => null,
|
|
|
|
]);
|
|
|
|
} 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()['value']->messages()[0];
|
|
|
|
|
2021-12-31 13:10:20 +01:00
|
|
|
self::assertSame('1618736242', $error->code());
|
|
|
|
self::assertSame('Cannot be empty and must be filled with a value of type `string`.', (string)$error);
|
2021-11-28 17:43:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ScalarValues
|
|
|
|
{
|
|
|
|
public bool $boolean = false;
|
|
|
|
|
|
|
|
public float $float = -1.0;
|
|
|
|
|
|
|
|
public int $integer = -1;
|
|
|
|
|
|
|
|
/** @var positive-int */
|
|
|
|
public int $positiveInteger = 1;
|
|
|
|
|
|
|
|
/** @var negative-int */
|
|
|
|
public int $negativeInteger = -1;
|
|
|
|
|
2021-12-06 13:14:54 +01:00
|
|
|
/** @var int<-1337, 1337> */
|
|
|
|
public int $integerRangeWithPositiveValue = -1;
|
|
|
|
|
|
|
|
/** @var int<-1337, 1337> */
|
|
|
|
public int $integerRangeWithNegativeValue = -1;
|
|
|
|
|
|
|
|
/** @var int<min, max> */
|
|
|
|
public int $integerRangeWithMinAndMax = -1;
|
|
|
|
|
2021-11-29 11:33:23 +01:00
|
|
|
/** @var 42 */
|
|
|
|
public int $integerValue;
|
|
|
|
|
2021-11-28 17:43:02 +01:00
|
|
|
public string $string = 'Schwifty!';
|
|
|
|
|
|
|
|
/** @var non-empty-string */
|
|
|
|
public string $nonEmptyString = 'Schwifty!';
|
|
|
|
|
2021-11-29 11:33:23 +01:00
|
|
|
/** @var 'baz' */
|
|
|
|
public string $stringValueWithSingleQuote;
|
|
|
|
|
|
|
|
/** @var "fiz" */
|
|
|
|
public string $stringValueWithDoubleQuote;
|
|
|
|
|
2021-11-28 17:43:02 +01:00
|
|
|
/** @var class-string */
|
|
|
|
public string $classString = stdClass::class;
|
|
|
|
|
|
|
|
/** @var class-string<DateTimeInterface> */
|
|
|
|
public string $classStringOfDateTime = DateTime::class;
|
|
|
|
|
|
|
|
/** @var class-string<ObjectAlias> */
|
|
|
|
public string $classStringOfAlias;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ScalarValuesWithConstructor extends ScalarValues
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @param positive-int $positiveInteger
|
|
|
|
* @param negative-int $negativeInteger
|
2021-12-06 13:14:54 +01:00
|
|
|
* @param int<-1337, 1337> $integerRangeWithPositiveValue
|
|
|
|
* @param int<-1337, 1337> $integerRangeWithNegativeValue
|
|
|
|
* @param int<min, max> $integerRangeWithMinAndMax
|
2021-11-29 11:33:23 +01:00
|
|
|
* @param 42 $integerValue
|
2021-11-28 17:43:02 +01:00
|
|
|
* @param non-empty-string $nonEmptyString
|
2021-11-29 11:33:23 +01:00
|
|
|
* @param 'baz' $stringValueWithSingleQuote
|
|
|
|
* @param "fiz" $stringValueWithDoubleQuote
|
2021-11-28 17:43:02 +01:00
|
|
|
* @param class-string $classString
|
|
|
|
* @param class-string<DateTimeInterface> $classStringOfDateTime
|
|
|
|
* @param class-string<ObjectAlias> $classStringOfAlias
|
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
bool $boolean,
|
|
|
|
float $float,
|
|
|
|
int $integer,
|
|
|
|
int $positiveInteger,
|
|
|
|
int $negativeInteger,
|
2021-12-06 13:14:54 +01:00
|
|
|
int $integerRangeWithPositiveValue,
|
|
|
|
int $integerRangeWithNegativeValue,
|
|
|
|
int $integerRangeWithMinAndMax,
|
2021-11-29 11:33:23 +01:00
|
|
|
int $integerValue,
|
2021-11-28 17:43:02 +01:00
|
|
|
string $string,
|
|
|
|
string $nonEmptyString,
|
2021-11-29 11:33:23 +01:00
|
|
|
string $stringValueWithSingleQuote,
|
|
|
|
string $stringValueWithDoubleQuote,
|
2021-11-28 17:43:02 +01:00
|
|
|
string $classString,
|
|
|
|
string $classStringOfDateTime,
|
|
|
|
string $classStringOfAlias
|
|
|
|
) {
|
|
|
|
$this->boolean = $boolean;
|
|
|
|
$this->float = $float;
|
|
|
|
$this->integer = $integer;
|
|
|
|
$this->positiveInteger = $positiveInteger;
|
|
|
|
$this->negativeInteger = $negativeInteger;
|
2021-12-06 13:14:54 +01:00
|
|
|
$this->integerRangeWithPositiveValue = $integerRangeWithPositiveValue;
|
|
|
|
$this->integerRangeWithNegativeValue = $integerRangeWithNegativeValue;
|
|
|
|
$this->integerRangeWithMinAndMax = $integerRangeWithMinAndMax;
|
2021-11-29 11:33:23 +01:00
|
|
|
$this->integerValue = $integerValue;
|
2021-11-28 17:43:02 +01:00
|
|
|
$this->string = $string;
|
|
|
|
$this->nonEmptyString = $nonEmptyString;
|
2021-11-29 11:33:23 +01:00
|
|
|
$this->stringValueWithSingleQuote = $stringValueWithSingleQuote;
|
|
|
|
$this->stringValueWithDoubleQuote = $stringValueWithDoubleQuote;
|
2021-11-28 17:43:02 +01:00
|
|
|
$this->classString = $classString;
|
|
|
|
$this->classStringOfDateTime = $classStringOfDateTime;
|
|
|
|
$this->classStringOfAlias = $classStringOfAlias;
|
|
|
|
}
|
|
|
|
}
|