Skip to content

Commit

Permalink
Failing test case: Valinor cannot understand array{k: T|list<T>}
Browse files Browse the repository at this point in the history
In this test, Valinor is being challenged to understand de-serialization of
a given hashmap when given a type `array{a-key: T|list<T>}`.

Specifically, at the time of this writing, Valinor only picks `T` during de-serialization,
and seems to completely ignore the `list<T>` portion of the data.

This kind of de-serialization is relevant when dealing with XML structures,
where an element could be singular or plural depending on context:

```xml
<Invoices>
  <Invoice>
    <!-- in this example, `Item` is singular, and needs to be handled as `Item` -->
    <Item><Price>123</Price></Item>
  </Invoice>
  <Invoice>
    <!-- in this example, `Item` is plural, and needs to be handled as `list<Item>` -->
    <Item><Price>456</Price></Item>
    <Item><Price>789</Price></Item>
  </Invoice>
</Invoices>
```

Initially discovered by @Tigerman55
  • Loading branch information
Ocramius committed Mar 5, 2024
1 parent 4b68835 commit ffe0f0f
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
12 changes: 12 additions & 0 deletions tests/Integration/Mapping/Fixture/SimpleObjectWithConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Integration\Mapping\Fixture;

final class SimpleObjectWithConstructor
{
public function __construct(public readonly string $value)
{
}
}
72 changes: 72 additions & 0 deletions tests/Integration/Mapping/UnionMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use CuyZ\Valinor\Tests\Integration\IntegrationTestCase;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\City;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObjectWithConstructor;

final class UnionMappingTest extends IntegrationTestCase
{
Expand Down Expand Up @@ -68,4 +69,75 @@ public function test_union_of_objects(): void
self::assertInstanceOf(City::class, $array[1]);
self::assertInstanceOf(SimpleObject::class, $array[2]);
}

/**
* @dataProvider expectedDeSerializationForUnionHashmapTypes
* @param array{key: SimpleObjectWithConstructor|list<SimpleObjectWithConstructor>} $expectedMap
*/
public function test_union_of_object_and_list_types_in_hashmap_key(mixed $input, array $expectedMap): void
{
try {
self::assertEquals(
$expectedMap,
$this->mapperBuilder()
->mapper()
->map(
"array{key:" . SimpleObjectWithConstructor::class . "|list<" . SimpleObjectWithConstructor::class . ">}",
$input
)
);
} catch (MappingError $error) {
var_dump($input);
var_dump($expectedMap);
$this->mappingFail($error);
}
}

/**
* @return non-empty-array<
* non-empty-string,
* array{mixed, array{key: SimpleObjectWithConstructor|list<SimpleObjectWithConstructor>}}
* >
*/
public static function expectedDeSerializationForUnionHashmapTypes(): array
{
return [
'single hashmap scalar value gets de-serialized into a hashmap containing a single value' => [
['key' => 'single-value'],
['key' => new SimpleObjectWithConstructor('single-value')],
],
'single hashmap value gets de-serialized into a hashmap containing a single value' => [
['key' => ['value' => 'single-value']],
['key' => new SimpleObjectWithConstructor('single-value')],
],
'multiple hashmap scalar values get de-serialized into a hashmap containing a a list of values' => [
[
'key' => [
'multi-value-1',
'multi-value-2',
],
],
[
'key' => [
new SimpleObjectWithConstructor('multi-value-1'),
new SimpleObjectWithConstructor('multi-value-2'),
],
],
],
'multiple hashmap values get de-serialized into a hashmap containing a a list of values' => [
[
'key' => [
['value' => 'multi-value-1'],
['value' => 'multi-value-2'],
],
],
[
'key' => [
new SimpleObjectWithConstructor('multi-value-1'),
new SimpleObjectWithConstructor('multi-value-2'),
],
],
],
];
}
}

0 comments on commit ffe0f0f

Please sign in to comment.