Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions packages/mapper/src/Casters/BooleanCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@

use Tempest\Core\Priority;
use Tempest\Mapper\Caster;
use Tempest\Mapper\ConfigurableCaster;
use Tempest\Mapper\Context;
use Tempest\Mapper\DynamicCaster;
use Tempest\Reflection\PropertyReflector;
use Tempest\Reflection\TypeReflector;

#[Priority(Priority::NORMAL)]
final readonly class BooleanCaster implements Caster, DynamicCaster
final readonly class BooleanCaster implements Caster, DynamicCaster, ConfigurableCaster
{
public function __construct(
private bool $nullable = false,
) {}

public static function accepts(PropertyReflector|TypeReflector $input): bool
{
$type = $input instanceof PropertyReflector
Expand All @@ -22,10 +28,19 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool
return in_array($type->getName(), ['bool', 'boolean'], strict: true);
}

public function cast(mixed $input): bool
public static function configure(PropertyReflector $property, Context $context): self
{
return new self(nullable: $property->isNullable());
}

public function cast(mixed $input): ?bool
{
if (is_string($input)) {
$input = mb_strtolower($input);
$input = mb_strtolower(trim($input));
}

if ($this->nullable && ($input === null || $input === '' || $input === 'null')) {
return null;
}

return match ($input) {
Expand Down
14 changes: 13 additions & 1 deletion packages/mapper/src/Casters/EnumCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/
public function __construct(
private string $enum,
private bool $nullable = false,
) {}

public static function accepts(PropertyReflector|TypeReflector $input): bool
Expand All @@ -35,11 +36,22 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool

public static function configure(PropertyReflector $property, Context $context): self
{
return new self(enum: $property->getType()->getName());
return new self(
enum: $property->getType()->getName(),
nullable: $property->isNullable(),
);
}

public function cast(mixed $input): ?object
{
if (is_string($input)) {
$input = trim($input);
}

if ($this->nullable && ($input === null || $input === '' || is_string($input) && mb_strtolower($input) === 'null')) {
return null;
}

if ($input === null) {
return null;
}
Expand Down
23 changes: 21 additions & 2 deletions packages/mapper/src/Casters/FloatCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@

use Tempest\Core\Priority;
use Tempest\Mapper\Caster;
use Tempest\Mapper\ConfigurableCaster;
use Tempest\Mapper\Context;
use Tempest\Mapper\DynamicCaster;
use Tempest\Reflection\PropertyReflector;
use Tempest\Reflection\TypeReflector;

#[Priority(Priority::NORMAL)]
final readonly class FloatCaster implements Caster, DynamicCaster
final readonly class FloatCaster implements Caster, DynamicCaster, ConfigurableCaster
{
public function __construct(
private bool $nullable = false,
) {}

public static function accepts(PropertyReflector|TypeReflector $input): bool
{
$type = $input instanceof PropertyReflector
Expand All @@ -22,8 +28,21 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool
return in_array($type->getName(), ['float', 'double'], strict: true);
}

public function cast(mixed $input): float
public static function configure(PropertyReflector $property, Context $context): self
{
return new self(nullable: $property->isNullable());
}

public function cast(mixed $input): ?float
{
if (is_string($input)) {
$input = mb_strtolower(trim($input));
}

if ($this->nullable && ($input === null || $input === '' || $input === 'null')) {
return null;
}

return floatval($input);
}
}
23 changes: 21 additions & 2 deletions packages/mapper/src/Casters/IntegerCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@

use Tempest\Core\Priority;
use Tempest\Mapper\Caster;
use Tempest\Mapper\ConfigurableCaster;
use Tempest\Mapper\Context;
use Tempest\Mapper\DynamicCaster;
use Tempest\Reflection\PropertyReflector;
use Tempest\Reflection\TypeReflector;

#[Priority(Priority::NORMAL)]
final readonly class IntegerCaster implements Caster, DynamicCaster
final readonly class IntegerCaster implements Caster, DynamicCaster, ConfigurableCaster
{
public function __construct(
private bool $nullable = false,
) {}

public static function accepts(PropertyReflector|TypeReflector $input): bool
{
$type = $input instanceof PropertyReflector
Expand All @@ -22,8 +28,21 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool
return in_array($type->getName(), ['int', 'integer'], strict: true);
}

public function cast(mixed $input): int
public static function configure(PropertyReflector $property, Context $context): self
{
return new self(nullable: $property->isNullable());
}

public function cast(mixed $input): ?int
{
if (is_string($input)) {
$input = mb_strtolower(trim($input));
}

if ($this->nullable && ($input === null || $input === '' || $input === 'null')) {
return null;
}

return intval($input);
}
}
3 changes: 3 additions & 0 deletions tests/Integration/Mapper/CasterFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ public function test_for_property(): void
$class = reflect(ObjectWithSerializerProperties::class);

$this->assertInstanceOf(IntegerCaster::class, $factory->forProperty($class->getProperty('intProp')));
$this->assertInstanceOf(IntegerCaster::class, $factory->forProperty($class->getProperty('nullableIntProp')));
$this->assertInstanceOf(FloatCaster::class, $factory->forProperty($class->getProperty('floatProp')));
$this->assertInstanceOf(FloatCaster::class, $factory->forProperty($class->getProperty('nullableFloatProp')));
$this->assertInstanceOf(BooleanCaster::class, $factory->forProperty($class->getProperty('boolProp')));
$this->assertInstanceOf(BooleanCaster::class, $factory->forProperty($class->getProperty('nullableBoolProp')));
$this->assertInstanceOf(NativeDateTimeCaster::class, $factory->forProperty($class->getProperty('nativeDateTimeImmutableProp')));
$this->assertInstanceOf(NativeDateTimeCaster::class, $factory->forProperty($class->getProperty('nativeDateTimeProp')));
$this->assertInstanceOf(NativeDateTimeCaster::class, $factory->forProperty($class->getProperty('nativeDateTimeInterfaceProp')));
Expand Down
26 changes: 25 additions & 1 deletion tests/Integration/Mapper/Casters/BooleanCasterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

namespace Tests\Tempest\Integration\Mapper\Casters;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use Tempest\Mapper\Casters\BooleanCaster;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

final class BooleanCasterTest extends FrameworkIntegrationTestCase
{
#[Test]
#[TestWith(['true', true])]
#[TestWith(['false', false])]
#[TestWith([true, true])]
Expand All @@ -21,8 +23,30 @@ final class BooleanCasterTest extends FrameworkIntegrationTestCase
#[TestWith(['off', false])]
#[TestWith(['disabled', false])]
#[TestWith(['no', false])]
public function test_cast(mixed $input, bool $expected): void
public function cast(mixed $input, bool $expected): void
{
$this->assertSame($expected, new BooleanCaster()->cast($input));
}

#[Test]
#[TestWith(['true', true])]
#[TestWith([true, true])]
#[TestWith(['false', false])]
#[TestWith([false, false])]
#[TestWith(['on', true])]
#[TestWith(['off', false])]
public function nullable_cast_with_values(mixed $input, bool $expected): void
{
$this->assertSame($expected, new BooleanCaster(nullable: true)->cast($input));
}

#[Test]
#[TestWith([null])]
#[TestWith([''])]
#[TestWith(['null'])]
#[TestWith([' '])]
public function nullable_cast_returns_null_for_empty_input(mixed $input): void
{
$this->assertNull(new BooleanCaster(nullable: true)->cast($input));
}
}
27 changes: 26 additions & 1 deletion tests/Integration/Mapper/Casters/EnumCasterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\Tempest\Integration\Mapper\Casters;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use Tempest\Mapper\Casters\EnumCaster;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
Expand All @@ -11,12 +12,36 @@

final class EnumCasterTest extends FrameworkIntegrationTestCase
{
#[Test]
#[TestWith(['FOO', UnitEnumToSerialize::FOO, UnitEnumToSerialize::class])]
#[TestWith(['BAR', UnitEnumToSerialize::BAR, UnitEnumToSerialize::class])]
#[TestWith(['foo', BackedEnumToSerialize::FOO, BackedEnumToSerialize::class])]
#[TestWith(['bar', BackedEnumToSerialize::BAR, BackedEnumToSerialize::class])]
public function test_cast(mixed $input, UnitEnum $expected, string $class): void
public function cast(mixed $input, UnitEnum $expected, string $class): void
{
$this->assertSame($expected, new EnumCaster($class)->cast($input));
}

#[Test]
#[TestWith([null])]
#[TestWith([''])]
#[TestWith(['null'])]
#[TestWith(['NULL'])]
#[TestWith([' '])]
public function nullable_cast_returns_null_for_empty_input(mixed $input): void
{
$caster = new EnumCaster(enum: BackedEnumToSerialize::class, nullable: true);

$this->assertNull($caster->cast($input));
}

#[Test]
#[TestWith(['foo', BackedEnumToSerialize::FOO])]
#[TestWith(['bar', BackedEnumToSerialize::BAR])]
public function nullable_cast_with_valid_values(mixed $input, UnitEnum $expected): void
{
$caster = new EnumCaster(enum: BackedEnumToSerialize::class, nullable: true);

$this->assertSame($expected, $caster->cast($input));
}
}
54 changes: 54 additions & 0 deletions tests/Integration/Mapper/Casters/FloatCasterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Mapper\Casters;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use Tempest\Mapper\Casters\FloatCaster;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

final class FloatCasterTest extends FrameworkIntegrationTestCase
{
#[Test]
#[TestWith(['3.14', 3.14])]
#[TestWith([3.14, 3.14])]
#[TestWith(['0', 0.0])]
#[TestWith([0, 0.0])]
#[TestWith(['', 0.0])]
#[TestWith([null, 0.0])]
public function cast(mixed $input, float $expected): void
{
$caster = new FloatCaster();

$this->assertSame($expected, $caster->cast($input));
}

#[Test]
#[TestWith(['3.14', 3.14])]
#[TestWith([3.14, 3.14])]
#[TestWith(['0', 0.0])]
#[TestWith([0, 0.0])]
#[TestWith([' 3.14 ', 3.14])]
public function nullable_cast_with_values(mixed $input, float $expected): void
{
$caster = new FloatCaster(nullable: true);

$this->assertSame($expected, $caster->cast($input));
}

#[Test]
#[TestWith([null])]
#[TestWith([''])]
#[TestWith(['null'])]
#[TestWith(['NULL'])]
#[TestWith(['Null'])]
#[TestWith([' '])]
public function nullable_cast_returns_null_for_empty_input(mixed $input): void
{
$caster = new FloatCaster(nullable: true);

$this->assertNull($caster->cast($input));
}
}
54 changes: 54 additions & 0 deletions tests/Integration/Mapper/Casters/IntegerCasterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Mapper\Casters;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use Tempest\Mapper\Casters\IntegerCaster;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

final class IntegerCasterTest extends FrameworkIntegrationTestCase
{
#[Test]
#[TestWith(['42', 42])]
#[TestWith([42, 42])]
#[TestWith(['0', 0])]
#[TestWith([0, 0])]
#[TestWith(['', 0])]
#[TestWith([null, 0])]
public function cast(mixed $input, int $expected): void
{
$caster = new IntegerCaster();

$this->assertSame($expected, $caster->cast($input));
}

#[Test]
#[TestWith(['42', 42])]
#[TestWith([42, 42])]
#[TestWith(['0', 0])]
#[TestWith([0, 0])]
#[TestWith([' 42 ', 42])]
public function nullable_cast_with_values(mixed $input, int $expected): void
{
$caster = new IntegerCaster(nullable: true);

$this->assertSame($expected, $caster->cast($input));
}

#[Test]
#[TestWith([null])]
#[TestWith([''])]
#[TestWith(['null'])]
#[TestWith(['NULL'])]
#[TestWith(['Null'])]
#[TestWith([' '])]
public function nullable_cast_returns_null_for_empty_input(mixed $input): void
{
$caster = new IntegerCaster(nullable: true);

$this->assertNull($caster->cast($input));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ final class ObjectWithSerializerProperties

public int $intProp = 1;

public ?int $nullableIntProp = null;

public float $floatProp = 0.1;

public ?float $nullableFloatProp = null;

public bool $boolProp = true;

public ?bool $nullableBoolProp = null;

public array $arrayProp = ['a'];

#[SerializeWith(DoubleStringSerializer::class)]
Expand Down
Loading
Loading