diff --git a/Makefile b/Makefile index 6476e3d..f8745a6 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ help: .PHONY: run run-php7.4 run-php8.0 run-php8.1 run-php8.2 run-php7.4: @# Help: It creates and runs a docker image with PHP 7.4 - docker-compose run --rm php74 bash -c "rm composer.lock || true; composer install --no-interaction; bash" + docker compose run --rm php74 bash -c "rm composer.lock || true; composer install --no-interaction; bash" run-php8.0: @# Help: It creates and runs a docker image with PHP 8.0 docker-compose run --rm php80 bash -c "rm composer.lock || true; composer install --no-interaction; bash" diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5605487..246ad85 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,12 +1,42 @@ - - - - - - Generators::oneOf(Generators::int(), Generators::float(), Generators::bool()) - Generators::oneOf(Generators::string(), Generators::float(), Generators::bool()) - + + + new BoolDecoder() + new BoolDecoder() + + + decode + decode + + + + + new CallableDecoder() + new CallableDecoder() + + + decode + decode + + + + + assertSame + + + + + new BoundedIntDecoder(10, 20) + new BoundedIntDecoder(10, 20) + new BoundedIntDecoder(20, 10) + + + decode + decode + new BoundedIntDecoder(10, 20) + new BoundedIntDecoder(10, 20) + new BoundedIntDecoder(20, 10) + diff --git a/src/Internal/Primitives/FloatDecoder.php b/src/Internal/Primitives/FloatDecoder.php index 090aa3a..a90f233 100644 --- a/src/Internal/Primitives/FloatDecoder.php +++ b/src/Internal/Primitives/FloatDecoder.php @@ -13,8 +13,6 @@ * @template I of mixed * * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec */ final class FloatDecoder implements Decoder { diff --git a/src/Internal/Primitives/IntDecoder.php b/src/Internal/Primitives/IntDecoder.php index 41f4b7c..493e1af 100644 --- a/src/Internal/Primitives/IntDecoder.php +++ b/src/Internal/Primitives/IntDecoder.php @@ -13,8 +13,6 @@ * @template I of mixed * * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec */ final class IntDecoder implements Decoder { diff --git a/src/Internal/Primitives/MixedDecoder.php b/src/Internal/Primitives/MixedDecoder.php index b143307..09b4914 100644 --- a/src/Internal/Primitives/MixedDecoder.php +++ b/src/Internal/Primitives/MixedDecoder.php @@ -13,8 +13,6 @@ * @psalm-template U of mixed * * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec */ final class MixedDecoder implements Decoder { diff --git a/src/Internal/Primitives/NullDecoder.php b/src/Internal/Primitives/NullDecoder.php index 3718c9f..429d901 100644 --- a/src/Internal/Primitives/NullDecoder.php +++ b/src/Internal/Primitives/NullDecoder.php @@ -13,8 +13,6 @@ * @template I of mixed * * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec */ final class NullDecoder implements Decoder { diff --git a/src/Internal/Primitives/StringDecoder.php b/src/Internal/Primitives/StringDecoder.php index 912aede..b2e1542 100644 --- a/src/Internal/Primitives/StringDecoder.php +++ b/src/Internal/Primitives/StringDecoder.php @@ -13,8 +13,6 @@ * @template I of mixed * * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec */ final class StringDecoder implements Decoder { diff --git a/src/Internal/Primitives/UndefinedDecoder.php b/src/Internal/Primitives/UndefinedDecoder.php index 3c9dd6b..a28d5aa 100644 --- a/src/Internal/Primitives/UndefinedDecoder.php +++ b/src/Internal/Primitives/UndefinedDecoder.php @@ -14,8 +14,6 @@ * @psalm-template U * * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec */ final class UndefinedDecoder implements Decoder { diff --git a/src/Internal/Useful/BoundedIntDecoder.php b/src/Internal/Useful/BoundedIntDecoder.php new file mode 100644 index 0000000..25172de --- /dev/null +++ b/src/Internal/Useful/BoundedIntDecoder.php @@ -0,0 +1,61 @@ + + * + * @psalm-internal Facile\PhpCodec + */ +final class BoundedIntDecoder implements Decoder +{ + /** + * @psalm-readonly + */ + private int $min; + + /** + * @psalm-readonly + */ + private int $max; + + public function __construct(int $min, int $max) + { + if ($min > $max) { + throw new \InvalidArgumentException('Lower bound cannot be greater than upper bound.'); + } + + $this->min = $min; + $this->max = $max; + } + + public function validate($i, Context $context): Validation + { + if (! \is_int($i)) { + return Validation::failure($i, $context); + } + + if ($i < $this->min || $i > $this->max) { + return Validation::failure($i, $context); + } + + return Validation::success($i); + } + + public function decode($i): Validation + { + return FunctionUtils::standardDecode($this, $i); + } + + public function getName(): string + { + return sprintf('BoundedInt(%d, %d)', $this->min, $this->max); + } +} diff --git a/tests/unit/DecodersTest.php b/tests/unit/DecodersTest.php index c288c8a..cc14455 100644 --- a/tests/unit/DecodersTest.php +++ b/tests/unit/DecodersTest.php @@ -23,20 +23,21 @@ public function testMap(): void /** @psalm-suppress UndefinedFunction */ $this ->forAll( - Generators::int() + Generators::int() // provare il decoder con molti interi casuali ) ->then(function (int $i) use ($decoder): void { $a = self::assertSuccessInstanceOf( DecodersTest\A::class, $decoder->decode($i) ); - self::assertSame($i, $a->getValue()); + self::assertSame($i, $a->getValue()); // verifica che il valore dentro l'oggetto A sia uguale a $i }); } } namespace Tests\Facile\PhpCodec\DecodersTest; +// wrapper class usata per testare la trasformazione: prende un int e lo incapsula in un oggetto class A { private int $v; diff --git a/tests/unit/Internal/Primitives/BoolDecoderTest.php b/tests/unit/Internal/Primitives/BoolDecoderTest.php new file mode 100644 index 0000000..310e15a --- /dev/null +++ b/tests/unit/Internal/Primitives/BoolDecoderTest.php @@ -0,0 +1,59 @@ +decode($input); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + } + + /** + * @dataProvider invalidBoolProvider + * + * @param mixed $input + */ + public function testInvalidBools($input): void + { + $decoder = new BoolDecoder(); + $result = $decoder->decode($input); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function validBoolProvider(): array + { + return [ + 'true' => [true], + 'false' => [false], + ]; + } + + public static function invalidBoolProvider(): array + { + return [ + 'int' => [1], + 'string true' => ['true'], + 'string false' => ['false'], + 'null' => [null], + 'float' => [1.0], + 'array' => [[true]], + ]; + } +} diff --git a/tests/unit/Internal/Primitives/CallableDecoderTest.php b/tests/unit/Internal/Primitives/CallableDecoderTest.php new file mode 100644 index 0000000..addba04 --- /dev/null +++ b/tests/unit/Internal/Primitives/CallableDecoderTest.php @@ -0,0 +1,64 @@ +decode($input); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + } + + /** + * @dataProvider invalidCallableProvider + * + * @param mixed $input + */ + public function testInvalidCallables($input): void + { + $decoder = new CallableDecoder(); + $result = $decoder->decode($input); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function validCallableProvider(): array + { + return [ + 'anonymous function' => [fn() => null], + 'named function' => ['strlen'], + 'static method as array' => [[self::class, 'helperStatic']], + ]; + } + + public static function invalidCallableProvider(): array + { + return [ + 'int' => [123], + 'string' => ['not_callable'], + 'array of strings' => [['a', 'b']], + 'null' => [null], + 'object' => [new \stdClass()], + ]; + } + + public static function helperStatic(): void + { + // Metodo statico valido per test + } +} diff --git a/tests/unit/Internal/Primitives/FloatDecoderTest.php b/tests/unit/Internal/Primitives/FloatDecoderTest.php new file mode 100644 index 0000000..876c730 --- /dev/null +++ b/tests/unit/Internal/Primitives/FloatDecoderTest.php @@ -0,0 +1,45 @@ +decode(3.14); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + } + + /** + * @dataProvider invalidFloatProvider + * + * @param mixed $invalidValue + */ + public function testInvalidValues($invalidValue): void + { + $decoder = new FloatDecoder(); + $result = $decoder->decode($invalidValue); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function invalidFloatProvider(): array + { + return [ + 'int' => [42], + 'string' => ['3.14'], + 'bool' => [true], + 'array' => [[3.14]], + 'null' => [null], + ]; + } +} diff --git a/tests/unit/Internal/Primitives/IntDecoderTest.php b/tests/unit/Internal/Primitives/IntDecoderTest.php new file mode 100644 index 0000000..1bdaa05 --- /dev/null +++ b/tests/unit/Internal/Primitives/IntDecoderTest.php @@ -0,0 +1,46 @@ +decode(42); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + } + + /** + * @dataProvider invalidIntProvider + * + * @param mixed $input + */ + public function testInvalidValues($input): void + { + $decoder = new IntDecoder(); + $result = $decoder->decode($input); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function invalidIntProvider(): array + { + return [ + 'float' => [3.14], + 'string' => ['42'], + 'bool' => [true], + 'null' => [null], + 'array' => [[1]], + 'object' => [new \stdClass()], + ]; + } +} diff --git a/tests/unit/Internal/Primitives/MixedDecoderTest.php b/tests/unit/Internal/Primitives/MixedDecoderTest.php new file mode 100644 index 0000000..1bca640 --- /dev/null +++ b/tests/unit/Internal/Primitives/MixedDecoderTest.php @@ -0,0 +1,41 @@ +decode($value); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + $this->assertSame($value, $result->getValue()); + } + + public static function provideValues(): array + { + return [ + 'string' => ['hello'], + 'int' => [42], + 'float' => [3.14], + 'bool true' => [true], + 'bool false' => [false], + 'null' => [null], + 'array' => [[1, 2, 3]], + 'object' => [new \stdClass()], + 'callable' => [fn() => 'test'], + ]; + } +} diff --git a/tests/unit/Internal/Primitives/NullDecoderTest.php b/tests/unit/Internal/Primitives/NullDecoderTest.php new file mode 100644 index 0000000..0711d6e --- /dev/null +++ b/tests/unit/Internal/Primitives/NullDecoderTest.php @@ -0,0 +1,48 @@ +decode(null); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + $this->assertSame(null, $result->getValue()); + } + + /** + * @dataProvider provideInvalidValues + * + * @param mixed $value + */ + public function testInvalidValues($value): void + { + $decoder = new NullDecoder(); + $result = $decoder->decode($value); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function provideInvalidValues(): array + { + return [ + 'int' => [42], + 'string' => ['null'], + 'bool' => [true], + 'float' => [3.14], + 'array' => [[null]], + 'object' => [new \stdClass()], + 'callable' => [fn() => null], + ]; + } +} diff --git a/tests/unit/Internal/Primitives/StringDecoderTest.php b/tests/unit/Internal/Primitives/StringDecoderTest.php new file mode 100644 index 0000000..a0604f4 --- /dev/null +++ b/tests/unit/Internal/Primitives/StringDecoderTest.php @@ -0,0 +1,46 @@ +decode('ciao'); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + } + + /** + * @dataProvider invalidValuesProvider + * + * @param mixed $input + */ + public function testInvalidValues($input): void + { + $decoder = new StringDecoder(); + $result = $decoder->decode($input); + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function invalidValuesProvider(): array + { + return [ + 'integer' => [123], + 'float' => [3.14], + 'boolean' => [true], + 'array' => [['not', 'a', 'string']], + 'object' => [new \stdClass()], + 'null' => [null], + ]; + } +} diff --git a/tests/unit/Internal/Primitives/UndefinedDecoderTest.php b/tests/unit/Internal/Primitives/UndefinedDecoderTest.php index e541180..ce17022 100644 --- a/tests/unit/Internal/Primitives/UndefinedDecoderTest.php +++ b/tests/unit/Internal/Primitives/UndefinedDecoderTest.php @@ -6,7 +6,10 @@ use Eris\TestTrait; use Facile\PhpCodec\Decoders; +use Facile\PhpCodec\Internal\Primitives\UndefinedDecoder; use Facile\PhpCodec\Internal\Undefined; +use Facile\PhpCodec\Validation\ValidationFailures; +use Facile\PhpCodec\Validation\ValidationSuccess; use Tests\Facile\PhpCodec\BaseTestCase; use Tests\Facile\PhpCodec\GeneratorUtils; @@ -29,8 +32,51 @@ function ($default): void { $x = self::assertValidationSuccess( Decoders::undefined($default)->decode(new Undefined()) ); + self::assertSame($default, $x); } ); } + + // Aggiunti Michele + public function testValidUndefined(): void + { + $default = 'default-value'; + $decoder = new UndefinedDecoder($default); + $result = $decoder->decode(new Undefined()); + + // fwrite(STDOUT, "\n[testValidUndefined] result: " . var_export($result, true) . "\n"); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + + $this->assertSame($default, $result->getValue()); + } + + /** + * @dataProvider provideInvalidValues + * + * @param mixed $input + */ + public function testInvalidValues($input): void + { + $decoder = new UndefinedDecoder('default'); + $result = $decoder->decode($input); + + // fwrite(STDOUT, "\n[testInvalidValues] input: " . var_export($input, true) . "\n"); + // fwrite(STDOUT, "[testInvalidValues] result: " . var_export($result, true) . "\n"); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function provideInvalidValues(): array + { + return [ + 'null' => [null], + 'string' => ['undefined'], + 'int' => [0], + 'bool' => [true], + 'array' => [[]], + 'object' => [new \stdClass()], + ]; + } } diff --git a/tests/unit/Internal/Useful/BoundedIntDecoderTest.php b/tests/unit/Internal/Useful/BoundedIntDecoderTest.php new file mode 100644 index 0000000..55a837f --- /dev/null +++ b/tests/unit/Internal/Useful/BoundedIntDecoderTest.php @@ -0,0 +1,61 @@ +decode($input); + + $this->assertInstanceOf(ValidationSuccess::class, $result); + } + + /** + * @dataProvider invalidIntProvider + * + * @param mixed $input + */ + public function testInvalidInts($input): void + { + $decoder = new BoundedIntDecoder(10, 20); + $result = $decoder->decode($input); + + $this->assertInstanceOf(ValidationFailures::class, $result); + } + + public static function validIntProvider(): array + { + return [ + 'lower bound' => [10], + 'middle' => [15], + 'upper bound' => [20], + ]; + } + + public static function invalidIntProvider(): array + { + return [ + 'below range' => [9], + 'above range' => [21], + 'string' => ['15'], + 'float' => [15.0], + 'null' => [null], + 'bool' => [true], + 'array' => [[15]], + 'object' => [new \stdClass()], + ]; +}