Skip to content

Commit e257fed

Browse files
committed
add integer ranges constraints
1 parent 466c5c0 commit e257fed

File tree

6 files changed

+249
-7
lines changed

6 files changed

+249
-7
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
- `Innmind\Validation\Constraint::object()`
88
- `Innmind\Validation\Constraint::failWith()`
99
- `Innmind\Validation\Constraint::string()->nonEmpty()`
10+
- `Innmind\Validation\Constraint::int()->positive()`
11+
- `Innmind\Validation\Constraint::int()->negative()`
12+
- `Innmind\Validation\Constraint::int()->range()`
1013
- `Innmind\Validation\Constraint\Provider`
1114

1215
### Changed

docs/constraints/primitives.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@
2121
$validate = Is::int();
2222
```
2323

24+
=== "`int<1, max>`"
25+
```php
26+
use Innmind\Validation\Is;
27+
28+
$validate = Is::int()->positive();
29+
```
30+
31+
=== "`int<min, -1>`"
32+
```php
33+
use Innmind\Validation\Is;
34+
35+
$validate = Is::int()->negative();
36+
```
37+
38+
=== "`int` range"
39+
```php
40+
use Innmind\Validation\Is;
41+
42+
$min = 0;
43+
$max = 100;
44+
45+
$validate = Is::int()->range($min, $max);
46+
```
47+
2448
=== "`float`"
2549
```php
2650
use Innmind\Validation\Is;

proofs/is.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,135 @@ static function($assert, $int, $other) {
193193
},
194194
);
195195

196+
yield proof(
197+
'Is::int()->positive()',
198+
given(
199+
Set::integers()->above(1),
200+
Set::integers()->below(0),
201+
),
202+
static function($assert, $int, $other) {
203+
$constraint = Is::int()->positive();
204+
205+
$assert->true(
206+
$constraint->asPredicate()($int),
207+
);
208+
$assert->same(
209+
$int,
210+
$constraint($int)->match(
211+
static fn($value) => $value,
212+
static fn() => null,
213+
),
214+
);
215+
$assert->false(
216+
$constraint->asPredicate()($other),
217+
);
218+
$assert->same(
219+
[['$', 'Integer must be above 0']],
220+
$constraint($other)->match(
221+
static fn() => null,
222+
static fn($failures) => $failures
223+
->map(static fn($failure) => [
224+
$failure->path()->toString(),
225+
$failure->message(),
226+
])
227+
->toList(),
228+
),
229+
);
230+
},
231+
);
232+
233+
yield proof(
234+
'Is::int()->negative()',
235+
given(
236+
Set::integers()->below(-1),
237+
Set::integers()->above(0),
238+
),
239+
static function($assert, $int, $other) {
240+
$constraint = Is::int()->negative();
241+
242+
$assert->true(
243+
$constraint->asPredicate()($int),
244+
);
245+
$assert->same(
246+
$int,
247+
$constraint($int)->match(
248+
static fn($value) => $value,
249+
static fn() => null,
250+
),
251+
);
252+
$assert->false(
253+
$constraint->asPredicate()($other),
254+
);
255+
$assert->same(
256+
[['$', 'Integer must be below 0']],
257+
$constraint($other)->match(
258+
static fn() => null,
259+
static fn($failures) => $failures
260+
->map(static fn($failure) => [
261+
$failure->path()->toString(),
262+
$failure->message(),
263+
])
264+
->toList(),
265+
),
266+
);
267+
},
268+
);
269+
270+
yield proof(
271+
'Is::int()->range()',
272+
given(
273+
Set::compose(
274+
static fn($min, $max) => [$min, $max],
275+
Set::integers(),
276+
Set::integers(),
277+
)
278+
->filter(static fn($bounds) => $bounds[0] < $bounds[1])
279+
->flatMap(
280+
static fn($bounds) => Set::compose(
281+
static fn($valid, $invalid) => [
282+
$bounds->unwrap()[0],
283+
$bounds->unwrap()[1],
284+
$valid,
285+
$invalid,
286+
],
287+
Set::integers()->between(
288+
$bounds->unwrap()[0],
289+
$bounds->unwrap()[1],
290+
),
291+
Set::either(
292+
Set::integers()->below($bounds->unwrap()[0] - 1),
293+
Set::integers()->above($bounds->unwrap()[1] + 1),
294+
),
295+
),
296+
),
297+
),
298+
static function($assert, $in) {
299+
[$min, $max, $valid, $invalid] = $in;
300+
301+
$constraint = Is::int()->range($min, $max);
302+
303+
$assert->true(
304+
$constraint->asPredicate()($valid),
305+
);
306+
$assert->same(
307+
$valid,
308+
$constraint($valid)->match(
309+
static fn($value) => $value,
310+
static fn() => null,
311+
),
312+
);
313+
$assert->false(
314+
$constraint->asPredicate()($invalid),
315+
);
316+
$assert->false(
317+
$constraint($invalid)->match(
318+
static fn() => true,
319+
static fn() => false,
320+
),
321+
);
322+
},
323+
);
324+
196325
yield proof(
197326
'Is::float()',
198327
given(

src/Constraint.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,10 @@ public static function string(): Provider\Str
7070

7171
/**
7272
* @psalm-pure
73-
*
74-
* @return self<mixed, int>
7573
*/
76-
public static function int(): self
74+
public static function int(): Provider\Integer
7775
{
78-
return new self(Constraint\Primitive::int());
76+
return Provider\Integer::of(self::build(...));
7977
}
8078

8179
/**

src/Constraint/Provider/Integer.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Innmind\Validation\Constraint\Provider;
5+
6+
use Innmind\Validation\{
7+
Constraint,
8+
Constraint\Provider,
9+
Constraint\Implementation,
10+
Constraint\Primitive,
11+
Constraint\Like,
12+
Failure,
13+
};
14+
use Innmind\Immutable\Validation;
15+
16+
/**
17+
* @psalm-immutable
18+
* @implements Provider<mixed, int>
19+
*/
20+
final class Integer implements Provider
21+
{
22+
/** @use Like<mixed, int> */
23+
use Like;
24+
25+
/**
26+
* @param pure-Closure(Implementation): Constraint $build
27+
*/
28+
private function __construct(
29+
private \Closure $build,
30+
) {
31+
}
32+
33+
/**
34+
* @internal
35+
* @psalm-pure
36+
*
37+
* @param pure-Closure(Implementation): Constraint $build
38+
*/
39+
public static function of(\Closure $build): self
40+
{
41+
return new self($build);
42+
}
43+
44+
#[\Override]
45+
public function toConstraint(): Constraint
46+
{
47+
/** @var Constraint<mixed, int> */
48+
return ($this->build)(Primitive::int());
49+
}
50+
51+
/**
52+
* @return Constraint<mixed, int<1, max>>
53+
*/
54+
public function positive(): Constraint
55+
{
56+
return $this
57+
->toConstraint()
58+
->and(Constraint::of(static fn(int $int) => match (true) {
59+
$int <= 0 => Validation::fail(Failure::of('Integer must be above 0')),
60+
default => Validation::success($int),
61+
}));
62+
}
63+
64+
/**
65+
* @return Constraint<mixed, int<min, -1>>
66+
*/
67+
public function negative(): Constraint
68+
{
69+
return $this
70+
->toConstraint()
71+
->and(Constraint::of(static fn(int $int) => match (true) {
72+
$int >= 0 => Validation::fail(Failure::of('Integer must be below 0')),
73+
default => Validation::success($int),
74+
}));
75+
}
76+
77+
/**
78+
* @return Constraint<mixed, int>
79+
*/
80+
public function range(int $min, int $max): Constraint
81+
{
82+
return $this
83+
->toConstraint()
84+
->and(Constraint::of(static fn(int $int) => match (true) {
85+
$int < $min => Validation::fail(Failure::of("Integer cannot be lower than $min")),
86+
$int > $max => Validation::fail(Failure::of("Integer cannot be higher than $max")),
87+
default => Validation::success($int),
88+
}));
89+
}
90+
}

src/Is.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ public static function string(): Provider\Str
2929

3030
/**
3131
* @psalm-pure
32-
*
33-
* @return Constraint<mixed, int>
3432
*/
35-
public static function int(): Constraint
33+
public static function int(): Provider\Integer
3634
{
3735
return Constraint::int();
3836
}

0 commit comments

Comments
 (0)