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: