Skip to content
Merged
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `trasformValidationSuccess` function for decoders. Structurally equivalent to a map. (#24)
### Fixed
- Used Psalm specific annotations to avoid confusing IDEs without Psalm support. (#26)
- Used Psalm specific annotations to avoid confusing IDEs without Psalm support. (#26)
### Deprecated
- Usage of codecs is deprecated in favour of decoders. (#24)

## [0.0.1] - 2021-04-30
### Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sh:
docker-compose exec php bash

psalm:
./vendor/bin/psalm --no-cache
./vendor/bin/psalm src --no-cache

type-assertions:
./vendor/bin/psalm tests/type-assertions --no-cache
Expand Down
27 changes: 22 additions & 5 deletions src/Codecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
use Facile\PhpCodec\Internal\Primitives\BoolType;
use Facile\PhpCodec\Internal\Primitives\CallableDecoder;
use Facile\PhpCodec\Internal\Primitives\FloatType;
use Facile\PhpCodec\Internal\Primitives\IntType;
use Facile\PhpCodec\Internal\Primitives\IntDecoder;
use Facile\PhpCodec\Internal\Primitives\MixedDecoder;
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\IntFromStringDecoder;
use Facile\PhpCodec\Internal\Useful\RegexType;
use Facile\PhpCodec\Utils\ConcreteCodec;

final class Codecs
{
Expand All @@ -42,11 +43,15 @@ public static function string(): Codec
}

/**
* @psalm-return Codec<int, mixed, int>
* @template I of mixed
* @psalm-return Codec<int, I, int>
*
* @deprecated use decoder instead
* @see Decoders::int()
*/
public static function int(): Codec
{
return new IntType();
return self::fromDecoder(new IntDecoder());
}

/**
Expand Down Expand Up @@ -79,10 +84,13 @@ public static function literal($x): Codec

/**
* @psalm-return Codec<int, string, int>
*
* @deprecated use decoder instead
* @see Decoders::intFromString()
*/
public static function intFromString(): Codec
{
return new IntFromStringType();
return self::fromDecoder(new IntFromStringDecoder());
}

/**
Expand Down Expand Up @@ -200,6 +208,9 @@ public static function fromDecoder(Decoder $decoder): Codec
* @psalm-return Codec<U, mixed, U>
*
* @param null|mixed $default
*
* @deprecated use decoder instead
* @see Decoders::undefined()
*/
public static function undefined($default = null): Codec
{
Expand All @@ -209,6 +220,9 @@ public static function undefined($default = null): Codec
/**
* @psalm-template U of mixed
* @psalm-return Codec<U, mixed, U>
*
* @deprecated use decoder instead
* @see Decoders::mixed()
*/
public static function mixed(): Codec
{
Expand All @@ -217,6 +231,9 @@ public static function mixed(): Codec

/**
* @psalm-return Codec<callable, mixed, callable>
*
* @deprecated use decoder instead
* @see Decoders::callable()
*/
public static function callable(): Codec
{
Expand Down
88 changes: 88 additions & 0 deletions src/Decoders.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,70 @@

namespace Facile\PhpCodec;

use Facile\PhpCodec\Internal\Combinators\ComposeDecoder;
use Facile\PhpCodec\Internal\Combinators\MapDecoder;
use Facile\PhpCodec\Internal\Primitives\CallableDecoder;
use Facile\PhpCodec\Internal\Primitives\IntDecoder;
use Facile\PhpCodec\Internal\Primitives\MixedDecoder;
use Facile\PhpCodec\Internal\Primitives\UndefinedDecoder;
use Facile\PhpCodec\Internal\Useful\IntFromStringDecoder;
use Facile\PhpCodec\Utils\ConcreteDecoder;
use Facile\PhpCodec\Validation\Context;
use Facile\PhpCodec\Validation\Validation;

final class Decoders
{
private function __construct()
{
}

/**
* @template I
* @template A
* @psalm-param callable(I, Context):Validation<A> $f
* @psalm-param string $name
* @psalm-return Decoder<I, A>
*/
public static function make(callable $f, string $name = 'anon'): Decoder
{
return new ConcreteDecoder($f, $name);
}

/**
* @template I
* @template A
* @template B
* @psalm-param Decoder<A, B> $db
* @psalm-param Decoder<I, A> $da
* @psalm-return Decoder<I, B>
*/
public static function compose(Decoder $db, Decoder $da): Decoder
{
return new ComposeDecoder($db, $da);
}

/**
* This is structurally equivalent to a map function
* map :: (a -> b) -> Decoder a -> Decoder b
*
* I still don't know if decoders could be functors or something more complicated.
* By now, let me introduce it with this strange name. I just need this feature.
*
* @template I
* @template A
* @template B
* @psalm-param callable(A):B $f
* @psalm-param Decoder<I, A> $da
* @psalm-return Decoder<I, B>
*/
public static function transformValidationSuccess(callable $f, Decoder $da): Decoder
{
return self::compose(
new MapDecoder($f, $da->getName()),
$da
);
}

/**
* @psalm-template U
* @psalm-param U $default
Expand All @@ -23,4 +79,36 @@ public static function undefined($default = null): Decoder
{
return new UndefinedDecoder($default);
}

/**
* @psalm-return Decoder<mixed, int>
*/
public static function int(): Decoder
{
return new IntDecoder();
}

/**
* @psalm-return Decoder<string, int>
*/
public static function intFromString(): Decoder
{
return new IntFromStringDecoder();
}

/**
* @psalm-return Decoder<mixed, mixed>
*/
public static function mixed(): Decoder
{
return new MixedDecoder();
}

/**
* @psalm-return Decoder<mixed, callable>
*/
public static function callable(): Decoder
{
return new CallableDecoder();
}
}
69 changes: 69 additions & 0 deletions src/Internal/Combinators/ComposeDecoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Facile\PhpCodec\Internal\Combinators;

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

/**
* @psalm-template IA
* @psalm-template A
* @psalm-template B
*
* @template-implements Decoder<IA, B>
*/
class ComposeDecoder implements Decoder
{
/** @var Decoder<A, B> */
private $db;
/** @var Decoder<IA, A> */
private $da;

/**
* @psalm-param Decoder<A, B> $db
* @psalm-param Decoder<IA, A> $da
*/
public function __construct(
Decoder $db,
Decoder $da
) {
$this->db = $db;
$this->da = $da;
}

/**
* @psalm-param IA $i
* @psalm-param Context $context
* @psalm-return Validation<B>
*
* @param mixed $i
*/
public function validate($i, Context $context): Validation
{
return Validation::bind(
/**
* @psalm-param A $aValue
*
* @param mixed $aValue
*/
function ($aValue) use ($context): Validation {
return $this->db->validate($aValue, $context);
},
$this->da->validate($i, $context)
);
}

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

public function getName(): string
{
return $this->db->getName();
}
}
49 changes: 49 additions & 0 deletions src/Internal/Combinators/MapDecoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Facile\PhpCodec\Internal\Combinators;

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

/**
* @template A
* @template B
* @implements Decoder<A, B>
*/
class MapDecoder implements Decoder
{
/** @var callable(A):B */
private $f;
/** @var string */
private $name;

/**
* @psalm-param callable(A):B $f
*
* @param callable $f
*/
public function __construct(callable $f, string $name = 'map')
{
$this->f = $f;
$this->name = $name;
}

public function validate($i, Context $context): Validation
{
return Validation::success(($this->f)($i));
}

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

public function getName(): string
{
return $this->name;
}
}
34 changes: 34 additions & 0 deletions src/Internal/Primitives/IntDecoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Facile\PhpCodec\Internal\Primitives;

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

/**
* @template I of mixed
* @implements Decoder<I, int>
*/
class IntDecoder implements Decoder
{
public function validate($i, Context $context): Validation
{
return \is_int($i)
? Validation::success($i)
: Validation::failure($i, $context);
}

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

public function getName(): string
{
return 'int';
}
}
2 changes: 2 additions & 0 deletions src/Internal/Primitives/IntRefiner.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/**
* @implements Refiner<int>
*
* @deprecated
*/
class IntRefiner implements Refiner
{
Expand Down
Loading