Skip to content

Commit a3147f9

Browse files
committed
introduce Option::ensure(), Result::ensure()
1 parent 780fd1b commit a3147f9

9 files changed

+96
-0
lines changed

src/Error.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,9 @@ public function getOrThrow(\Throwable $e)
5252
{
5353
throw $e;
5454
}
55+
56+
public function ensure(callable $condition, $else): ResultInterface
57+
{
58+
return $this;
59+
}
5560
}

src/None.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ public function getOrThrow(\Throwable $e)
3434
{
3535
throw $e;
3636
}
37+
38+
public function ensure(callable $condition): Option
39+
{
40+
return $this;
41+
}
3742
}

src/Option.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ abstract class Option
1414
private static ?None $none = null;
1515

1616
/**
17+
* @psalm-pure
1718
* @template TNew
1819
* @param TNew $value
1920
* @return Some<TNew>
@@ -24,10 +25,12 @@ public static function some($value): Some
2425
}
2526

2627
/**
28+
* @psalm-pure
2729
* @return None
2830
*/
2931
public static function none(): None
3032
{
33+
/** @psalm-suppress ImpureStaticProperty */
3134
return self::$none ??= new None();
3235
}
3336

@@ -113,6 +116,16 @@ abstract public function getOrElse($else);
113116
*/
114117
abstract public function getOrThrow(\Throwable $e);
115118

119+
/**
120+
* Ensures that Some value also validates against the given condition,
121+
* Otherwise returns None.
122+
*
123+
* @return Option<TValue>
124+
*
125+
* @param callable(TValue):bool $condition
126+
*/
127+
abstract public function ensure(callable $condition): Option;
128+
116129
/**
117130
* @template TElse
118131
* @param TElse $else

src/Result.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
abstract class Result implements ResultInterface
1414
{
1515
/**
16+
* @psalm-pure
1617
* @template T
1718
* @param T $value
1819
* @return Success<T>
@@ -23,6 +24,7 @@ public static function success($value): Success
2324
}
2425

2526
/**
27+
* @psalm-pure
2628
* @template T
2729
* @param T $value
2830
* @return Error<T>

src/ResultInterface.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,18 @@ public function getOr(callable $map);
7878
* @return TSuccess|never-return
7979
*/
8080
public function getOrThrow(\Throwable $e);
81+
82+
/**
83+
* Ensures that the Success value also validates against the given condition,
84+
* Otherwise returns an Error with a given value.
85+
*
86+
* If this Result is already an Error, nothing will change.
87+
*
88+
* @template TNewError
89+
*
90+
* @param callable(TSuccess):bool $condition
91+
* @param TNewError $else
92+
* @return ResultInterface<TSuccess, TError|TNewError>
93+
*/
94+
public function ensure(callable $condition, $else): self;
8195
}

src/Some.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,11 @@ public function getOrThrow(\Throwable $e)
4646
{
4747
return $this->value;
4848
}
49+
50+
public function ensure(callable $condition): Option
51+
{
52+
return $condition($this->value)
53+
? $this
54+
: Option::none();
55+
}
4956
}

src/Success.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,11 @@ public function getOrThrow(\Throwable $e)
5151
{
5252
return $this->value;
5353
}
54+
55+
public function ensure(callable $condition, $else): ResultInterface
56+
{
57+
return $condition($this->value)
58+
? $this
59+
: Result::error($else);
60+
}
5461
}

test/OptionTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,26 @@ public function testToNullableNone(): void
124124
$nullable = $option->toNullable();
125125
self::assertNull($nullable);
126126
}
127+
128+
public function testEnsureSomeReturningTrue(): void
129+
{
130+
$option = Option::some(111);
131+
$ensured = $option->ensure(fn (int $i) => $i > 100);
132+
self::assertEquals(111, $ensured->getOrElse(null));
133+
}
134+
135+
public function testEnsureSomeReturningFalse(): void
136+
{
137+
$option = Option::some(111);
138+
$ensured = $option->ensure(fn (int $i) => $i < 100);
139+
self::assertEquals(null, $ensured->getOrElse(null));
140+
}
141+
142+
public function testEnsureNone(): void
143+
{
144+
/** @var Option<int> $option */
145+
$option = Option::none();
146+
$ensured = $option->ensure(fn (int $i) => $i > 100);
147+
self::assertEquals(null, $ensured->getOrElse(null));
148+
}
127149
}

test/ResultTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,25 @@ public function testErrors(): void
173173

174174
self::assertEquals([ 3, 5 ], Result::extractErrors($results));
175175
}
176+
177+
public function testEnsureSuccessReturningTrue(): void
178+
{
179+
$result = Result::success(123);
180+
$ensured = $result->ensure(fn(int $i) => $i > 100, 'failed');
181+
self::assertEquals(123, $ensured->get());
182+
}
183+
184+
public function testEnsureSuccessReturningFalse(): void
185+
{
186+
$result = Result::success(123);
187+
$ensured = $result->ensure(fn(int $i) => $i < 100, 'failed');
188+
self::assertEquals('failed', $ensured->get());
189+
}
190+
191+
public function testEnsureErrorDoesNotChangePreviousError(): void
192+
{
193+
$result = Result::error('old error');
194+
$ensured = $result->ensure(fn(int $i) => $i > 100, 'new error');
195+
self::assertEquals('old error', $ensured->get());
196+
}
176197
}

0 commit comments

Comments
 (0)