Skip to content

Commit 55cd331

Browse files
committed
Merge branch 'develop'
* develop: specify next release simplify example make Shape accept mixed as input add shortcut to specify the constraint of each element of a list shorten Is::array()->and(Is::list()) to Is::list()
2 parents 2d34070 + deb8943 commit 55cd331

File tree

6 files changed

+149
-50
lines changed

6 files changed

+149
-50
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## 1.4.0 - 2024-03-24
4+
5+
### Added
6+
7+
- `Is::list()->and(Each::of(Constraint))` has been shortened to `Is::list(Constraint)`
8+
9+
### Changed
10+
11+
- `Is::array()->and(Is::list())` has been shortened to `Is::list()`
12+
- `Is::array()->and(Shape::of(...$args))` has been shortened to `Shape::of(...$args)`
13+
314
## 1.3.0 - 2024-03-05
415

516
### Added

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ $validate = Shape::of('id', Is::int())
4545
->with('username', Is::string())
4646
->with(
4747
'addresses',
48-
Is::array()
49-
->and(Is::list())
50-
->and(Each::of(Is::string())->map(
48+
Is::list(
49+
Is::string()->map(
5150
static fn(string $address) => new YourModel($address),
52-
))
51+
)
52+
)
5353
);
5454
$result = $validate($valid)->match(
5555
static fn(array $value) => $value,

proofs/is.php

+39
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,45 @@ static function($assert, $array, $other) {
370370
},
371371
);
372372

373+
yield proof(
374+
'Is::list() with inner type',
375+
given(
376+
Set\Sequence::of(
377+
Set\Strings::any(),
378+
)->atLeast(1),
379+
Set\Sequence::of(
380+
Set\Integers::any(),
381+
)->atLeast(1),
382+
),
383+
static function($assert, $strings, $ints) {
384+
$assert->true(
385+
Is::list(Is::string())->asPredicate()($strings),
386+
);
387+
$assert->same(
388+
$strings,
389+
Is::list(Is::string())($strings)->match(
390+
static fn($value) => $value,
391+
static fn() => null,
392+
),
393+
);
394+
$assert->false(
395+
Is::list(Is::string())->asPredicate()($ints),
396+
);
397+
$assert->same(
398+
[['$', 'Value is not of type string']],
399+
Is::list(Is::string())($ints)->match(
400+
static fn() => null,
401+
static fn($failures) => $failures
402+
->map(static fn($failure) => [
403+
$failure->path()->toString(),
404+
$failure->message(),
405+
])
406+
->toList(),
407+
),
408+
);
409+
},
410+
);
411+
373412
yield proof(
374413
'Is::shape()',
375414
given(

proofs/shape.php

+32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Shape,
66
Is,
77
};
8+
use Innmind\BlackBox\Set;
89

910
return static function() {
1011
yield test(
@@ -175,4 +176,35 @@ static function($assert) {
175176
);
176177
},
177178
);
179+
180+
yield proof(
181+
'Shape validates non arrays',
182+
given(
183+
Set\Either::any(
184+
Set\Strings::any(),
185+
Set\Integers::any(),
186+
Set\RealNumbers::any(),
187+
Set\Elements::of(
188+
true,
189+
false,
190+
new stdClass,
191+
),
192+
),
193+
),
194+
static function($assert, $value) {
195+
$assert->same(
196+
[['$', 'Value is not of type array']],
197+
Shape::of('bar', Is::int())($value)
198+
->match(
199+
static fn() => null,
200+
static fn($failures) => $failures
201+
->map(static fn($failure) => [
202+
$failure->path()->toString(),
203+
$failure->message(),
204+
])
205+
->toList(),
206+
),
207+
);
208+
},
209+
);
178210
};

src/Is.php

+17-8
Original file line numberDiff line numberDiff line change
@@ -109,24 +109,33 @@ public static function null(): self
109109
/**
110110
* @psalm-pure
111111
*
112-
* @return self<array, list>
112+
* @template E
113+
*
114+
* @param Constraint<mixed, E> $each
115+
*
116+
* @return Constraint<mixed, list<E>>
113117
*/
114-
public static function list(): self
118+
public static function list(Constraint $each = null): Constraint
115119
{
116-
/** @var self<array, list> */
117-
return new self(\array_is_list(...), 'list');
120+
/** @var self<array, list<mixed>> */
121+
$list = new self(\array_is_list(...), 'list');
122+
123+
$constraint = self::array()->and($list);
124+
125+
return match ($each) {
126+
null => $constraint,
127+
default => $constraint->and(Each::of($each)),
128+
};
118129
}
119130

120131
/**
121132
* @psalm-pure
122133
*
123134
* @param non-empty-string $key
124-
*
125-
* @return Constraint<mixed, non-empty-array<non-empty-string, mixed>>
126135
*/
127-
public static function shape(string $key, Constraint $constraint): Constraint
136+
public static function shape(string $key, Constraint $constraint): Shape
128137
{
129-
return self::array()->and(Shape::of($key, $constraint));
138+
return Shape::of($key, $constraint);
130139
}
131140

132141
public function and(Constraint $constraint): Constraint

src/Shape.php

+46-38
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
};
1010

1111
/**
12-
* @implements Constraint<array, non-empty-array<non-empty-string, mixed>>
12+
* @implements Constraint<mixed, non-empty-array<non-empty-string, mixed>>
1313
* @psalm-immutable
1414
*/
1515
final class Shape implements Constraint
@@ -31,43 +31,7 @@ private function __construct(array $constraints, array $optional)
3131

3232
public function __invoke(mixed $value): Validation
3333
{
34-
$optional = new \stdClass;
35-
/** @var Validation<Failure, non-empty-array<non-empty-string, mixed>> */
36-
$validation = Validation::success([]);
37-
38-
foreach ($this->constraints as $key => $constraint) {
39-
$keyValidation = Has::key($key);
40-
41-
if (\in_array($key, $this->optional, true)) {
42-
/** @psalm-suppress MixedArgumentTypeCoercion */
43-
$keyValidation = $keyValidation->or(Of::callable(
44-
static fn() => Validation::success($optional),
45-
));
46-
}
47-
48-
$ofType = Of::callable(
49-
static fn($value) => match ($value) {
50-
$optional => Validation::success($optional),
51-
default => $constraint($value)->mapFailures(
52-
static fn($failure) => $failure->under($key),
53-
),
54-
},
55-
);
56-
57-
$validation = $validation->and(
58-
$keyValidation->and($ofType)($value),
59-
static function($array, $value) use ($key, $optional) {
60-
if ($value !== $optional) {
61-
/** @psalm-suppress MixedAssignment */
62-
$array[$key] = $value;
63-
}
64-
65-
return $array;
66-
},
67-
);
68-
}
69-
70-
return $validation;
34+
return Is::array()($value)->flatMap($this->validate(...));
7135
}
7236

7337
/**
@@ -126,4 +90,48 @@ public function asPredicate(): PredicateInterface
12690
{
12791
return Predicate::of($this);
12892
}
93+
94+
/**
95+
* @return Validation<Failure, non-empty-array<non-empty-string, mixed>>
96+
*/
97+
private function validate(array $value): Validation
98+
{
99+
$optional = new \stdClass;
100+
/** @var Validation<Failure, non-empty-array<non-empty-string, mixed>> */
101+
$validation = Validation::success([]);
102+
103+
foreach ($this->constraints as $key => $constraint) {
104+
$keyValidation = Has::key($key);
105+
106+
if (\in_array($key, $this->optional, true)) {
107+
/** @psalm-suppress MixedArgumentTypeCoercion */
108+
$keyValidation = $keyValidation->or(Of::callable(
109+
static fn() => Validation::success($optional),
110+
));
111+
}
112+
113+
$ofType = Of::callable(
114+
static fn($value) => match ($value) {
115+
$optional => Validation::success($optional),
116+
default => $constraint($value)->mapFailures(
117+
static fn($failure) => $failure->under($key),
118+
),
119+
},
120+
);
121+
122+
$validation = $validation->and(
123+
$keyValidation->and($ofType)($value),
124+
static function($array, $value) use ($key, $optional) {
125+
if ($value !== $optional) {
126+
/** @psalm-suppress MixedAssignment */
127+
$array[$key] = $value;
128+
}
129+
130+
return $array;
131+
},
132+
);
133+
}
134+
135+
return $validation;
136+
}
129137
}

0 commit comments

Comments
 (0)