1: | <?php declare(strict_types=1); |
2: | |
3: | namespace TH\Maybe\Result; |
4: | |
5: | use TH\DocTest\Attributes\ExamplesSetup; |
6: | use TH\Maybe\Option; |
7: | use TH\Maybe\Result; |
8: | use TH\Maybe\Tests\Helpers\IgnoreUnusedResults; |
9: | use function TH\Maybe\Internal\isOfAnyClass; |
10: | |
11: | /** |
12: | * Return a `Result\Ok` Result containing `$value`. |
13: | * |
14: | * ``` |
15: | * $x = Result\ok(3); |
16: | * self::assertTrue($x->isOk()); |
17: | * self::assertSame(3, $x->unwrap()); |
18: | * ``` |
19: | * |
20: | * @template U |
21: | * @param U $value |
22: | * @return Result\Ok<U> |
23: | */ |
24: | function ok(mixed $value): Result\Ok |
25: | { |
26: | return new Result\Ok($value); |
27: | } |
28: | |
29: | /** |
30: | * Return a `Result\Err` result. |
31: | * |
32: | * # Examples |
33: | * |
34: | * ``` |
35: | * $x = Result\err("nope"); |
36: | * self::assertTrue($x->isErr()); |
37: | * self::assertSame("nope", $x->unwrapErr()); |
38: | * $x->unwrap(); // @throws RuntimeException Unwrapping `Err`: s:4:"nope"; |
39: | * ``` |
40: | * |
41: | * @template F |
42: | * @param F $value |
43: | * @return Result\Err<F> |
44: | */ |
45: | function err(mixed $value): Result\Err |
46: | { |
47: | return new Result\Err($value); |
48: | } |
49: | |
50: | /** |
51: | * Execute a callable and transform the result into an `Result`. |
52: | * It will be a `Result\Ok` containing the result or, if it threw an exception |
53: | * matching $exceptionClass, a `Result\Err` containing the exception. |
54: | * |
55: | * # Examples |
56: | * |
57: | * Successful execution: |
58: | * |
59: | * ``` |
60: | * self::assertEq(Result\ok(3), Result\trap(fn () => 3)); |
61: | * ``` |
62: | * |
63: | * Checked exception: |
64: | * |
65: | * ``` |
66: | * $x = Result\trap(fn () => new \DateTimeImmutable("2020-30-30 UTC")); |
67: | * self::assertTrue($x->isErr()); |
68: | * $x->unwrap(); |
69: | * // @throws Exception Failed to parse time string (2020-30-30 UTC) at position 6 (0): Unexpected character |
70: | * ``` |
71: | * |
72: | * Unchecked exception: |
73: | * |
74: | * ``` |
75: | * Result\trap(fn () => 1/0); |
76: | * // @throws DivisionByZeroError Division by zero |
77: | * ``` |
78: | * |
79: | * @template U |
80: | * @template E of \Throwable |
81: | * @param callable(mixed...):U $callback |
82: | * @param list<class-string<E>>|class-string<E> $exceptionClass |
83: | * @return Result<U,E> |
84: | * @throws \Throwable |
85: | */ |
86: | #[ExamplesSetup(IgnoreUnusedResults::class)] |
87: | function trap(callable $callback, array|string $exceptionClass = \Exception::class): Result |
88: | { |
89: | try { |
90: | /** @var Result<U,E> */ |
91: | return Result\ok($callback()); |
92: | } catch (\Throwable $th) { |
93: | if (isOfAnyClass($th, (array) $exceptionClass)) { |
94: | return Result\err($th); |
95: | } |
96: | |
97: | throw $th; |
98: | } |
99: | } |
100: | |
101: | /** |
102: | * Converts from `Result<Result<T, E>, E>` to `Result<T, E>`. |
103: | * |
104: | * # Examples |
105: | * |
106: | * ``` |
107: | * $x = Result\ok(3); |
108: | * self::assertSame(Result\flatten(Result\ok($x)), $x); |
109: | * |
110: | * $x = Result\err("deity"); |
111: | * self::assertSame(Result\flatten($y = Result\ok($x)), $x); |
112: | * |
113: | * self::assertEq(Result\flatten($x), Result\err("deity")); |
114: | * ``` |
115: | * |
116: | * @template T |
117: | * @template E |
118: | * @template E1 of E |
119: | * @template E2 of E |
120: | * @param Result<Result<T, E1>, E2> $result |
121: | * @return Result<T, E> |
122: | */ |
123: | #[ExamplesSetup(IgnoreUnusedResults::class)] |
124: | function flatten(Result $result): Result |
125: | { |
126: | /** @phpstan-ignore return.type */ |
127: | return $result->mapOrElse( |
128: | static fn (Result $r) => $r, |
129: | static fn (mixed $err) => Result\err($err), |
130: | ); |
131: | } |
132: | |
133: | /** |
134: | * Transposes a `Result` of an `Option` into an `Option` of a `Result`. |
135: | * |
136: | * `Ok(None)` will be mapped to `None`. |
137: | * `Ok(Some(_))` and `Err(_)` will be mapped to `Some(Ok(_))` and `Some(Err(_))`. |
138: | * |
139: | * ``` |
140: | * use TH\Maybe\Option; |
141: | * |
142: | * self::assertSame(Result\transpose(Result\ok(Option\none())), Option\none()); |
143: | * |
144: | * $x = Result\ok(Option\some(4)); |
145: | * self::assertEq(Result\transpose($x), Option\some(Result\ok(4))); |
146: | * |
147: | * $x = Result\err("meat"); |
148: | * self::assertEq(Result\transpose($x), Option\some($x)); |
149: | * ``` |
150: | * |
151: | * @template U |
152: | * @template F |
153: | * @param Result<Option<U>, F> $result |
154: | * @return Option<Result<U, F>> |
155: | */ |
156: | #[ExamplesSetup(IgnoreUnusedResults::class)] |
157: | function transpose(Result $result): Option |
158: | { |
159: | /** @var Option<Result<U, F>> */ |
160: | return $result->mapOrElse( |
161: | static fn (Option $option) => $option->map(Result\ok(...)), |
162: | static fn () => Option\some(clone $result), |
163: | ); |
164: | } |
165: |