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