Skip to content

Commit 4226956

Browse files
authored
Merge pull request #80 from Innmind/recover-case
Add `Attempt::recoverCase()` and `::mapErrorCase()`
2 parents e83a01c + b700967 commit 4226956

4 files changed

Lines changed: 131 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### Added
66

77
- `Innmind\Immutable\Validation::attempt()`
8+
- `Innmind\Immutable\Attempt::mapErrorCase()`
9+
- `Innmind\Immutable\Attempt::recoverCase()`
810

911
## 6.0.0 - 2026-01-11
1012

docs/structures/attempt.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,24 @@ $attempt = Attempt::of(static fn() => 1/0)
139139

140140
Here `#!php $attempt` contains an `\Exception` because the first `Attempt` raised a `DivisionByZeroError`.
141141

142+
## `->mapErrorCase()`
143+
144+
This is similar to `mapError` except you can specify the type of error you want to change (like in a `catch` statement).
145+
146+
```php
147+
$attempt = Attempt::of(static fn() => 1/0)
148+
->mapErrorCase(
149+
\DivisionByZeroError::class,
150+
static fn(\DivisionByZeroError $e) => new \Exception('Division by zero, fix the code'),
151+
)
152+
->mapError(static fn(\Throwable $e) => new \Exception('something bad happened', 0, $e));
153+
```
154+
155+
!!! warning ""
156+
It uses `instanceof` to check the error case. This means the order in which you apply these calls is important!
157+
158+
This is to match the behaviour of the `catch` statement.
159+
142160
## `->recover()`
143161

144162
This will allow you to recover in case of a previous error.
@@ -150,6 +168,26 @@ $attempt = Attempt::of(static fn() => 1/0)
150168

151169
Here `#!php $attempt` is `#!php 42` because the first `Attempt` raised a `DivisionByZeroError`.
152170

171+
## `->recoverCase()`
172+
173+
This is similar to `recover` except you can specify the type of error you want to recover (like in a `catch` statement).
174+
175+
```php
176+
$attempt = Attempt::of(static fn() => 1/0)
177+
->recoverCase(
178+
\DivisionByZeroError::class,
179+
static fn(\DivisionByZeroError $e) => Attempt::result(0),
180+
)
181+
->recover(static fn(\Throwable $e) => Attempt::result(42));
182+
```
183+
184+
Here `#!php $attempt` is `#!php 0` because the first `Attempt` raised a `DivisionByZeroError`.
185+
186+
!!! warning ""
187+
It uses `instanceof` to check the error case. This means the order in which you apply these calls is important!
188+
189+
This is to match the behaviour of the `catch` statement.
190+
153191
## `->xrecover()`
154192

155193
This behaves like [`->recover()`](#-recover) except when conjointly used with [`->guard()`](#-guard). Guarded errors can't be recovered.

proofs/attempt.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,35 @@ static function($assert, $start, $end, $value) {
152152
},
153153
);
154154

155+
yield proof(
156+
'Attempt::mapErrorCase()',
157+
given(
158+
$exceptions,
159+
$exceptions,
160+
$exceptions,
161+
)->filter(static fn($a, $b, $c) => !($a instanceof $c)),
162+
static function($assert, $start, $end, $other) {
163+
$assert->same(
164+
$end,
165+
Attempt::error($start)
166+
->mapErrorCase($start::class, static fn() => $end)
167+
->match(
168+
static fn() => null,
169+
static fn($e) => $e,
170+
),
171+
);
172+
$assert->same(
173+
$start,
174+
Attempt::error($start)
175+
->mapErrorCase($other::class, static fn() => $end)
176+
->match(
177+
static fn() => null,
178+
static fn($e) => $e,
179+
),
180+
);
181+
},
182+
);
183+
155184
yield proof(
156185
'Attempt::recover()',
157186
given(
@@ -203,6 +232,33 @@ static function($assert, $start, $end, $value) {
203232
},
204233
);
205234

235+
yield proof(
236+
'Attempt::recoverCase()',
237+
given(
238+
$exceptions,
239+
$exceptions,
240+
$exceptions,
241+
Set::type(),
242+
)->filter(static fn($a, $b, $c) => !($a instanceof $c)),
243+
static function($assert, $start, $end, $other, $value) {
244+
$assert->same(
245+
$value,
246+
Attempt::error($start)
247+
->recoverCase($start::class, static fn() => Attempt::result($value))
248+
->unwrap(),
249+
);
250+
$assert->same(
251+
$start,
252+
Attempt::error($start)
253+
->recoverCase($other::class, static fn() => Attempt::result($value))
254+
->match(
255+
static fn() => null,
256+
static fn($e) => $e,
257+
),
258+
);
259+
},
260+
);
261+
206262
yield proof(
207263
'Attempt::maybe()',
208264
given(

src/Attempt.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ public function mapError(callable $map): self
172172
return new self($this->implementation->mapError($map));
173173
}
174174

175+
/**
176+
* @template E of \Throwable
177+
*
178+
* @param class-string<E> $case
179+
* @param callable(E): \Throwable $map
180+
*
181+
* @return self<T>
182+
*/
183+
#[\NoDiscard]
184+
public function mapErrorCase(string $case, callable $map): self
185+
{
186+
return $this->mapError(static fn($e) => match (true) {
187+
$e instanceof $case => $map($e),
188+
default => $e,
189+
});
190+
}
191+
175192
/**
176193
* @template U
177194
*
@@ -188,6 +205,24 @@ public function recover(callable $recover): self
188205
));
189206
}
190207

208+
/**
209+
* @template E of \Throwable
210+
* @template U
211+
*
212+
* @param class-string<E> $case
213+
* @param callable(E): self<U> $recover
214+
*
215+
* @return self<T|U>
216+
*/
217+
#[\NoDiscard]
218+
public function recoverCase(string $case, callable $recover): self
219+
{
220+
return $this->recover(static fn($e) => match (true) {
221+
$e instanceof $case => $recover($e),
222+
default => self::error($e),
223+
});
224+
}
225+
191226
/**
192227
* This prevents guarded errors from being recovered.
193228
*

0 commit comments

Comments
 (0)