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: