Skip to content

Commit

Permalink
add AssertListLength and support for non-empty-list type
Browse files Browse the repository at this point in the history
  • Loading branch information
JanTvrdik committed Feb 21, 2024
1 parent 6735fa4 commit 4eb5447
Show file tree
Hide file tree
Showing 14 changed files with 785 additions and 68 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ composer require shipmonk/input-mapper
Input Mapper comes with built-in mappers for the following types:

* `array`, `bool`, `float`, `int`, `mixed`, `string`, `list`
* `positive-int`, `negative-int`, `int<TMin, TMax>`
* `array<V>`, `array<K, V>`, `list<V>`
* `positive-int`, `negative-int`, `int<TMin, TMax>`, `non-empty-list`
* `array<V>`, `array<K, V>`, `list<V>`, `non-empty-list<V>`
* `array{K1: V1, ...}`
* `?T`, `Optional<T>`
* `DateTimeInterface`, `DateTimeImmutable`
Expand Down Expand Up @@ -53,6 +53,7 @@ Input Mapper comes with some built-in validators:
* `AssertUrl`
* list validators:
* `AssertListItem`
* `AssertListLength`
* date time validators:
* `AssertDateTimeRange`

Expand All @@ -73,15 +74,15 @@ class Person
{
public function __construct(
public readonly string $name,

public readonly int $age,

/** @var Optional<string> */
public readonly Optional $email,

/** @var list<string> */
public readonly array $hobbies,

/** @var Optional<list<self>> */
public readonly Optional $friends,
) {}
Expand Down Expand Up @@ -138,7 +139,7 @@ class Person
{
public function __construct(
public readonly string $name,

#[AssertIntRange(gte: 18, lte: 99)]
public readonly int $age,
) {}
Expand Down
6 changes: 6 additions & 0 deletions src/Compiler/MapperFactory/DefaultMapperCompilerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use ShipMonk\InputMapper\Compiler\Mapper\Wrapper\MapOptional;
use ShipMonk\InputMapper\Compiler\Mapper\Wrapper\ValidatedMapperCompiler;
use ShipMonk\InputMapper\Compiler\Type\PhpDocTypeUtils;
use ShipMonk\InputMapper\Compiler\Validator\Array\AssertListLength;
use ShipMonk\InputMapper\Compiler\Validator\Int\AssertIntRange;
use ShipMonk\InputMapper\Compiler\Validator\Int\AssertNegativeInt;
use ShipMonk\InputMapper\Compiler\Validator\Int\AssertNonNegativeInt;
Expand Down Expand Up @@ -120,6 +121,7 @@ public function create(TypeNode $type, array $options = []): MapperCompiler

default => match ($type->name) {
'list' => new MapList(new MapMixed()),
'non-empty-list' => new ValidatedMapperCompiler(new MapList(new MapMixed()), [new AssertListLength(min: 1)]),
'negative-int' => new ValidatedMapperCompiler(new MapInt(), [new AssertNegativeInt()]),
'non-negative-int' => new ValidatedMapperCompiler(new MapInt(), [new AssertNonNegativeInt()]),
'non-positive-int' => new ValidatedMapperCompiler(new MapInt(), [new AssertNonPositiveInt()]),
Expand Down Expand Up @@ -154,6 +156,10 @@ public function create(TypeNode $type, array $options = []): MapperCompiler
1 => new MapList($this->createInner($type->genericTypes[0], $options)),
default => throw CannotCreateMapperCompilerException::fromType($type),
},
'non-empty-list' => match (count($type->genericTypes)) {
1 => new ValidatedMapperCompiler(new MapList($this->createInner($type->genericTypes[0], $options)), [new AssertListLength(min: 1)]),
default => throw CannotCreateMapperCompilerException::fromType($type),
},
Optional::class => match (count($type->genericTypes)) {
1 => new MapOptional($this->createInner($type->genericTypes[0], $options)),
default => throw CannotCreateMapperCompilerException::fromType($type),
Expand Down
30 changes: 30 additions & 0 deletions src/Compiler/Type/NativeTypeUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use function array_splice;
use function count;
use function get_debug_type;
use function is_a;
use function sprintf;
use function strcasecmp;

class NativeTypeUtils
{
Expand Down Expand Up @@ -83,11 +86,38 @@ public static function createIntersection(ComplexType|Identifier|Name ...$member
throw new LogicException(sprintf('Unexpected intersection member type: %s', get_debug_type($member)));
}

for ($i = 0; $i < count($types); $i++) {
for ($j = $i + 1; $j < count($types); $j++) {
if (self::isSubTypeOf($types[$i], $types[$j])) {
array_splice($types, $j--, 1);
continue;
}

if (self::isSubTypeOf($types[$j], $types[$i])) {
array_splice($types, $i--, 1);
continue 2;
}
}
}

return match (count($types)) {
0 => new Identifier('mixed'),
1 => $types[0],
default => new IntersectionType($types),
};
}

public static function isSubTypeOf(Identifier|Name $a, Identifier|Name $b): bool
{
if ($a instanceof Identifier && $b instanceof Identifier) {
return strcasecmp($a->name, $b->name) === 0;
}

if ($a instanceof Name && $b instanceof Name) {
return is_a($a->toString(), $b->toString(), true);
}

return false;
}

}
Loading

0 comments on commit 4eb5447

Please sign in to comment.