Skip to content

Commit

Permalink
Undefined values are decoded with the specified default value (#15)
Browse files Browse the repository at this point in the history
* Implementation of the codec laws as phpunit assertions
  • Loading branch information
ilario-pierbattista authored Apr 6, 2021
1 parent 0d88c71 commit 4a12b5a
Show file tree
Hide file tree
Showing 17 changed files with 458 additions and 27 deletions.
29 changes: 28 additions & 1 deletion src/Codecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
use Facile\PhpCodec\Internal\Arrays\MapType;
use Facile\PhpCodec\Internal\Combinators\ClassFromArray;
use Facile\PhpCodec\Internal\Combinators\ComposeCodec;
use Facile\PhpCodec\Internal\Combinators\LiteralType;
use Facile\PhpCodec\Internal\Combinators\UnionCodec;
use Facile\PhpCodec\Internal\IdentityEncoder;
use Facile\PhpCodec\Internal\Primitives\BoolType;
use Facile\PhpCodec\Internal\Primitives\FloatType;
use Facile\PhpCodec\Internal\Primitives\IntType;
use Facile\PhpCodec\Internal\Primitives\LiteralType;
use Facile\PhpCodec\Internal\Primitives\NullType;
use Facile\PhpCodec\Internal\Primitives\StringType;
use Facile\PhpCodec\Internal\Primitives\UndefinedDecoder;
use Facile\PhpCodec\Internal\Useful\DateTimeFromIsoStringType;
use Facile\PhpCodec\Internal\Useful\IntFromStringType;
use Facile\PhpCodec\Internal\Useful\RegexType;
Expand Down Expand Up @@ -189,4 +191,29 @@ public static function regex(string $regex): Codec
{
return new RegexType($regex);
}

/**
* @template I
* @template T
*
* @param Decoder<I, T> $decoder
*
* @return Codec<T, I, T>
*/
public static function fromDecoder(Decoder $decoder): Codec
{
return new ConcreteCodec($decoder, new IdentityEncoder());
}

/**
* @template U
*
* @param U | null $default
*
* @return Codec<U, mixed, U>
*/
public static function undefined($default = null): Codec
{
return self::fromDecoder(new UndefinedDecoder($default));
}
}
26 changes: 26 additions & 0 deletions src/Decoders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Facile\PhpCodec;

use Facile\PhpCodec\Internal\Primitives\UndefinedDecoder;

final class Decoders
{
private function __construct()
{
}

/**
* @template U
*
* @param U $default
*
* @return Decoder<mixed, U>
*/
public static function undefined($default = null): Decoder
{
return new UndefinedDecoder($default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Facile\PhpCodec\Internal\Primitives;
namespace Facile\PhpCodec\Internal\Combinators;

use Facile\PhpCodec\Refiner;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Facile\PhpCodec\Internal\Primitives;
namespace Facile\PhpCodec\Internal\Combinators;

use Facile\PhpCodec\Internal\Encode;
use Facile\PhpCodec\Internal\Type;
Expand Down
20 changes: 20 additions & 0 deletions src/Internal/IdentityEncoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Facile\PhpCodec\Internal;

use Facile\PhpCodec\Encoder;

/**
* @template T
*
* @implements Encoder<T, T>
*/
final class IdentityEncoder implements Encoder
{
public function encode($a)
{
return $a;
}
}
46 changes: 46 additions & 0 deletions src/Internal/Primitives/UndefinedDecoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Facile\PhpCodec\Internal\Primitives;

use Facile\PhpCodec\Decoder;
use function Facile\PhpCodec\Internal\standardDecode;
use Facile\PhpCodec\Internal\Undefined;
use Facile\PhpCodec\Validation\Context;
use Facile\PhpCodec\Validation\Validation;

/**
* @template U
* @implements Decoder<mixed, U>
*/
class UndefinedDecoder implements Decoder
{
/** @var U */
private $default;

/**
* @param U $default
*/
public function __construct($default)
{
$this->default = $default;
}

public function validate($i, Context $context): Validation
{
return $i instanceof Undefined
? Validation::success($this->default)
: Validation::failure($i, $context);
}

public function decode($i): Validation
{
return standardDecode($this, $i);
}

public function getName(): string
{
return 'undefined';
}
}
62 changes: 62 additions & 0 deletions tests/examples/DecodePartialPropertiesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Examples\Facile\PhpCodec;

use Facile\PhpCodec\Codecs;
use Tests\Facile\PhpCodec\BaseTestCase;

class DecodePartialPropertiesTest extends BaseTestCase
{
public function test(): void
{
$c = Codecs::classFromArray(
[
'foo' => Codecs::string(),
'bar' => Codecs::union(Codecs::int(), Codecs::undefined(-1)),
],
function (string $foo, int $bar): DecodePartialPropertiesTest\A {
return new DecodePartialPropertiesTest\A($foo, $bar);
},
DecodePartialPropertiesTest\A::class
);

self::asserSuccessInstanceOf(
DecodePartialPropertiesTest\A::class,
$c->decode(['foo' => 'str']),
function (DecodePartialPropertiesTest\A $a): void {
self::assertSame('str', $a->getFoo());
self::assertSame(-1, $a->getBar());
}
);
}
}

namespace Examples\Facile\PhpCodec\DecodePartialPropertiesTest;

class A
{
/** @var string */
private $foo;
/** @var int */
private $bar;

public function __construct(
string $foo,
int $bar
) {
$this->foo = $foo;
$this->bar = $bar;
}

public function getFoo(): string
{
return $this->foo;
}

public function getBar(): int
{
return $this->bar;
}
}
2 changes: 1 addition & 1 deletion tests/type-assertions/RefineTypeAssertions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace TypeAssertions\Facile\PhpCodec;

use Facile\PhpCodec\Internal\Arrays\MapRefiner;
use Facile\PhpCodec\Internal\Combinators\LiteralRefiner;
use Facile\PhpCodec\Internal\Primitives\InstanceOfRefiner;
use Facile\PhpCodec\Internal\Primitives\LiteralRefiner;

class RefineTypeAssertions extends TypeAssertion
{
Expand Down
65 changes: 65 additions & 0 deletions tests/unit/BaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tests\Facile\PhpCodec;

use Facile\PhpCodec\Codec;
use Facile\PhpCodec\PathReporter;
use Facile\PhpCodec\Validation\Validation;
use Facile\PhpCodec\Validation\ValidationSuccess;
Expand Down Expand Up @@ -90,4 +91,68 @@ public static function asserSuccessAnd(
/** @var ValidationSuccess<T> $v */
return $thenDo($v->getValue());
}

/**
* @template I
* @template A
* @template O
*
* @param Codec<A, I, O> $codec
*
* @return \Closure(I, A): void
*/
public static function codecLaws(
Codec $codec
): \Closure {
return function ($input, $a) use ($codec): void {
self::assertCodecLaw1($codec, $input);
self::assertCodecLaw2($codec, $a);
};
}

/**
* @template I
* @template A
* @template O
*
* @param Codec<A, I, O> $codec
* @param I $input
*/
public static function assertCodecLaw1(
Codec $codec,
$input
): void {
self::assertEquals(
$input,
Validation::fold(
function () use ($input) {
return $input;
},
function ($a) use ($codec) {
return $codec->encode($a);
},
$codec->decode($input)
)
);
}

/**
* @template A
* @template I
* @template O
*
* @param Codec<A, I, O> $codec
* @param A $a
*/
public static function assertCodecLaw2(
Codec $codec,
$a
): void {
self::asserSuccessAnd(
$codec->decode($codec->encode($a)),
function ($r) use ($a): void {
self::assertEquals($a, $r);
}
);
}
}
20 changes: 20 additions & 0 deletions tests/unit/GeneratorUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Tests\Facile\PhpCodec;

use Eris\Generator as g;

final class GeneratorUtils
{
public static function scalar(): g
{
return g\oneOf(
g\int(),
g\float(),
g\bool(),
g\string()
);
}
}
45 changes: 45 additions & 0 deletions tests/unit/Internal/Combinators/LiteralTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Tests\Facile\PhpCodec\Internal\Combinators;

use Eris\Generator as g;
use Eris\TestTrait;
use Facile\PhpCodec\Codec;
use function Facile\PhpCodec\destructureIn;
use Facile\PhpCodec\Internal\Combinators\LiteralType;
use Tests\Facile\PhpCodec\BaseTestCase;
use Tests\Facile\PhpCodec\GeneratorUtils;

class LiteralTypeTest extends BaseTestCase
{
use TestTrait;

public function testLaws(): void
{
$this
->forAll(
g\bind(
g\oneOf(
g\int(),
g\string(),
g\bool()
),
function ($literal): g {
return g\tuple(
g\constant(new LiteralType($literal)),
g\oneOf(
GeneratorUtils::scalar(),
g\constant($literal)
),
g\constant($literal)
);
}
)
)
->then(destructureIn(function (Codec $codec, $u, $a): void {
self::codecLaws($codec)($u, $a);
}));
}
}
Loading

0 comments on commit 4a12b5a

Please sign in to comment.