diff --git a/src/Type/Types/IntegerRangeType.php b/src/Type/Types/IntegerRangeType.php index 2c282903..7df97ea1 100644 --- a/src/Type/Types/IntegerRangeType.php +++ b/src/Type/Types/IntegerRangeType.php @@ -11,6 +11,9 @@ use CuyZ\Valinor\Type\Parser\Exception\Scalar\SameValueForIntegerRange; use CuyZ\Valinor\Type\Type; +use function is_string; +use function ltrim; +use function preg_match; use function sprintf; /** @internal */ @@ -79,6 +82,12 @@ public function matches(Type $other): bool public function canCast(mixed $value): bool { + if (is_string($value)) { + $value = preg_match('/^0+$/', $value) + ? '0' + : ltrim($value, '0'); + } + return ! is_bool($value) && filter_var($value, FILTER_VALIDATE_INT) !== false && $value >= $this->min diff --git a/src/Type/Types/IntegerValueType.php b/src/Type/Types/IntegerValueType.php index f6cd7033..23a22703 100644 --- a/src/Type/Types/IntegerValueType.php +++ b/src/Type/Types/IntegerValueType.php @@ -13,6 +13,9 @@ use function assert; use function filter_var; use function is_bool; +use function is_string; +use function ltrim; +use function preg_match; /** @internal */ final class IntegerValueType implements IntegerType, FixedType @@ -55,6 +58,12 @@ public function matches(Type $other): bool public function canCast(mixed $value): bool { + if (is_string($value)) { + $value = preg_match('/^0+$/', $value) + ? '0' + : ltrim($value, '0'); + } + return ! is_bool($value) && filter_var($value, FILTER_VALIDATE_INT) !== false && (int)$value === $this->value; // @phpstan-ignore-line; diff --git a/src/Type/Types/NativeIntegerType.php b/src/Type/Types/NativeIntegerType.php index f03f9e57..70249b08 100644 --- a/src/Type/Types/NativeIntegerType.php +++ b/src/Type/Types/NativeIntegerType.php @@ -14,6 +14,8 @@ use function filter_var; use function is_bool; use function is_int; +use function is_string; +use function ltrim; /** @internal */ final class NativeIntegerType implements IntegerType @@ -37,6 +39,10 @@ public function matches(Type $other): bool public function canCast(mixed $value): bool { + if (is_string($value)) { + $value = ltrim($value, '0') . '0'; + } + return ! is_bool($value) && filter_var($value, FILTER_VALIDATE_INT) !== false; } diff --git a/src/Type/Types/NonNegativeIntegerType.php b/src/Type/Types/NonNegativeIntegerType.php index ff6c5f04..8a3b87b8 100644 --- a/src/Type/Types/NonNegativeIntegerType.php +++ b/src/Type/Types/NonNegativeIntegerType.php @@ -10,6 +10,9 @@ use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Utility\IsSingleton; +use function is_string; +use function ltrim; + /** @internal */ final class NonNegativeIntegerType implements IntegerType { @@ -33,6 +36,10 @@ public function matches(Type $other): bool public function canCast(mixed $value): bool { + if (is_string($value)) { + $value = ltrim($value, '0') . '0'; + } + return ! is_bool($value) && filter_var($value, FILTER_VALIDATE_INT) !== false && $value >= 0; diff --git a/src/Type/Types/PositiveIntegerType.php b/src/Type/Types/PositiveIntegerType.php index 1b2e135a..f15882db 100644 --- a/src/Type/Types/PositiveIntegerType.php +++ b/src/Type/Types/PositiveIntegerType.php @@ -14,6 +14,8 @@ use function filter_var; use function is_bool; use function is_int; +use function is_string; +use function ltrim; /** @internal */ final class PositiveIntegerType implements IntegerType @@ -38,6 +40,10 @@ public function matches(Type $other): bool public function canCast(mixed $value): bool { + if (is_string($value)) { + $value = ltrim($value, '0'); + } + return ! is_bool($value) && filter_var($value, FILTER_VALIDATE_INT) !== false && $value > 0; diff --git a/tests/Integration/Mapping/Other/FlexibleCastingMappingTest.php b/tests/Integration/Mapping/Other/FlexibleCastingMappingTest.php index ea630bd3..fb09d16a 100644 --- a/tests/Integration/Mapping/Other/FlexibleCastingMappingTest.php +++ b/tests/Integration/Mapping/Other/FlexibleCastingMappingTest.php @@ -24,6 +24,71 @@ protected function setUp(): void $this->mapper = (new MapperBuilder())->enableFlexibleCasting()->mapper(); } + public function test_leading_zero_in_numeric_is_mapped_properly(): void + { + $source = ['000', '040', '00040', '0001337.404']; + + try { + $result = $this->mapper->map('array', $source); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame([0, 40, 40, 1337.404], $result); + } + + public function test_leading_zero_in_integer_range_is_mapped_properly(): void + { + $source = ['060', '042', '000404']; + + try { + $result = $this->mapper->map('array>', $source); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame([60, 42, 404], $result); + } + + public function test_leading_zero_in_integer_value_is_mapped_properly(): void + { + $source = ['000', '040', '000404']; + + try { + $result = $this->mapper->map('array<0|40|404>', $source); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame([0, 40, 404], $result); + } + + public function test_leading_zero_in_positive_integer_is_mapped_properly(): void + { + $source = ['040', '000404']; + + try { + $result = $this->mapper->map('array', $source); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame([40, 404], $result); + } + + public function test_leading_zero_in_non_negative_integer_is_mapped_properly(): void + { + $source = ['000', '040', '000404']; + + try { + $result = $this->mapper->map('array', $source); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame([0, 40, 404], $result); + } + public function test_array_of_scalars_is_mapped_properly(): void { $source = ['foo', 42, 1337.404];