Skip to content

Commit

Permalink
fix: error when mapping null to existing target (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi authored Oct 30, 2024
1 parent 5a9f076 commit 3c40856
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* fix: rethrow our exceptions in `writeTargetProperty`, fix confusing error
message
* fix: error when mapping null to existing target

## 1.13.2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Rekalogika\Mapper\MainTransformer\Exception;

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Debug\TraceableTransformer;
use Rekalogika\Mapper\Exception\UnexpectedValueException;
use Rekalogika\Mapper\Transformer\MixedType;
use Rekalogika\Mapper\Transformer\TransformerInterface;
Expand All @@ -29,6 +30,10 @@ public function __construct(
TransformerInterface $transformer,
Context $context,
) {
if ($transformer instanceof TraceableTransformer) {
$transformer = $transformer->getDecorated();
}

$message = \sprintf(
'Trying to map source type "%s" to target type "%s", but the assigned transformer "%s" returns an unexpected type "%s".',
get_debug_type($source),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Transformer\Exception;

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Exception\RuntimeException;
use Rekalogika\Mapper\Util\TypeFactory;
use Rekalogika\Mapper\Util\TypeUtil;
use Symfony\Component\PropertyInfo\Type;

class NullSourceButMandatoryTargetException extends RuntimeException
{
public function __construct(
?Type $targetType,
?\Throwable $previous = null,
?Context $context = null,
) {
parent::__construct(
message: \sprintf(
'The source is null, the target is mandatory & expected to be of type "%s". But no transformer is able to handle this case.',
TypeUtil::getTypeString($targetType ?? TypeFactory::mixed()),
),
previous: $previous,
context: $context,
);
}
}
17 changes: 17 additions & 0 deletions src/Transformer/Implementation/NullTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Exception\InvalidArgumentException;
use Rekalogika\Mapper\Transformer\Exception\NullSourceButMandatoryTargetException;
use Rekalogika\Mapper\Transformer\TransformerInterface;
use Rekalogika\Mapper\Transformer\TypeMapping;
use Rekalogika\Mapper\Util\TypeCheck;
Expand All @@ -31,6 +32,21 @@ public function transform(
?Type $targetType,
Context $context,
): mixed {
// if the source is null & the target is an object, ignore it by
// returning the existing target. if the existing target is not already
// an object, throw an exception

if (TypeCheck::isObject($targetType)) {
if ($target === null) {
throw new NullSourceButMandatoryTargetException(
targetType: $targetType,
context: $context,
);
}

return $target;
}

if ($target !== null) {
throw new InvalidArgumentException('Target must be null');
}
Expand Down Expand Up @@ -70,6 +86,7 @@ public function getSupportedTransformation(): iterable
yield new TypeMapping(TypeFactory::null(), TypeFactory::float());
yield new TypeMapping(TypeFactory::null(), TypeFactory::bool());
yield new TypeMapping(TypeFactory::null(), TypeFactory::array());
yield new TypeMapping(TypeFactory::null(), TypeFactory::object(), true);
yield new TypeMapping(TypeFactory::mixed(), TypeFactory::null());
}
}
2 changes: 2 additions & 0 deletions src/Util/TypeUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Rekalogika\Mapper\Tests\IntegrationTest\MapPropertyPathTest;
use Rekalogika\Mapper\Tests\UnitTest\Util\TypeUtil2Test;
use Rekalogika\Mapper\Tests\UnitTest\Util\TypeUtilTest;
use Rekalogika\Mapper\Transformer\Exception\NullSourceButMandatoryTargetException;
use Rekalogika\Mapper\Transformer\MixedType;
use Rekalogika\Mapper\TypeResolver\Implementation\TypeResolver;
use Symfony\Component\PropertyInfo\Type;
Expand Down Expand Up @@ -257,6 +258,7 @@ public static function getDebugType(null|Type|MixedType|array $type): string
TypeUtilTest::class,
TraceData::class,
MapPropertyPathTest::class,
NullSourceButMandatoryTargetException::class,
)]
public static function getTypeString(Type|MixedType $type): string
{
Expand Down
12 changes: 12 additions & 0 deletions tests/config/rekalogika-mapper/generated-mappings.php
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,18 @@
target: \Rekalogika\Mapper\Tests\Fixtures\Money\ObjectWithIntegerBackedMoneyProperty::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/NullSourceTest.php on line 36
source: \Rekalogika\Mapper\Tests\Fixtures\NullSource\Source::class,
target: \Rekalogika\Mapper\Tests\Fixtures\NullSource\TargetString::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/NullSourceTest.php on line 27
source: \Rekalogika\Mapper\Tests\Fixtures\NullSource\Source::class,
target: \Rekalogika\Mapper\Tests\Fixtures\NullSource\TargetUuid::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/ObjectKeysTest.php on line 27
source: \Rekalogika\Mapper\Tests\Fixtures\ObjectKeys\RelationshipMap::class,
Expand Down
23 changes: 23 additions & 0 deletions tests/src/Fixtures/NullSource/Source.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\Fixtures\NullSource;

final readonly class Source
{
// @phpstan-ignore return.unusedType
public function getProperty(): ?string
{
return null;
}
}
31 changes: 31 additions & 0 deletions tests/src/Fixtures/NullSource/TargetString.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\Fixtures\NullSource;

use Symfony\Component\Uid\Uuid;

class TargetString
{
private string $property;

public function __construct()
{
$this->property = (string) Uuid::v7();
}

public function getProperty(): string
{
return $this->property;
}
}
31 changes: 31 additions & 0 deletions tests/src/Fixtures/NullSource/TargetUuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\Fixtures\NullSource;

use Symfony\Component\Uid\Uuid;

class TargetUuid
{
private Uuid $property;

public function __construct()
{
$this->property = Uuid::v7();
}

public function getProperty(): Uuid
{
return $this->property;
}
}
4 changes: 2 additions & 2 deletions tests/src/IntegrationTest/ConstructorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace Rekalogika\Mapper\Tests\IntegrationTest;

use Rekalogika\Mapper\MainTransformer\Exception\CannotFindTransformerException;
use Rekalogika\Mapper\Tests\Common\FrameworkTestCase;
use Rekalogika\Mapper\Tests\Fixtures\Constructor\ObjectWithConstructorAndExtraMandatoryArgumentDto;
use Rekalogika\Mapper\Tests\Fixtures\Constructor\ObjectWithConstructorAndPropertiesDto;
Expand All @@ -25,6 +24,7 @@
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarPropertiesAndAdditionalNullProperty;
use Rekalogika\Mapper\Transformer\Exception\ClassNotInstantiableException;
use Rekalogika\Mapper\Transformer\Exception\InstantiationFailureException;
use Rekalogika\Mapper\Transformer\Exception\NullSourceButMandatoryTargetException;

class ConstructorTest extends FrameworkTestCase
{
Expand Down Expand Up @@ -91,7 +91,7 @@ public function testFromEmptyStdClassToMandatoryArguments(): void

public function testFromEmptyStdClassToMandatoryArgumentsThatCannotBeCastFromNull(): void
{
$this->expectException(CannotFindTransformerException::class);
$this->expectException(NullSourceButMandatoryTargetException::class);
$source = new \stdClass();
$this->mapper->map($source, ObjectWithMandatoryConstructorThatCannotBeCastFromNullDto::class);
}
Expand Down
41 changes: 41 additions & 0 deletions tests/src/IntegrationTest/NullSourceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\IntegrationTest;

use Rekalogika\Mapper\Tests\Common\FrameworkTestCase;
use Rekalogika\Mapper\Tests\Fixtures\NullSource\Source;
use Rekalogika\Mapper\Tests\Fixtures\NullSource\TargetString;
use Rekalogika\Mapper\Tests\Fixtures\NullSource\TargetUuid;

class NullSourceTest extends FrameworkTestCase
{
public function testNullSourceToUuid(): void
{
$source = new Source();
$target = new TargetUuid();
$newTarget = $this->mapper->map($source, $target);

$this->assertSame($target, $newTarget);
}

public function testNullSourceToString(): void
{
$source = new Source();
$target = new TargetString();
$newTarget = $this->mapper->map($source, $target);

$this->assertSame($target, $newTarget);
}

}

0 comments on commit 3c40856

Please sign in to comment.