diff --git a/README.md b/README.md index 535fc44..7e0a922 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,8 @@ All Result types (both Ok and Err) implement these methods: #### Value Extraction - `unwrap(): mixed` - Returns the success value or throws UnwrapException (extends LogicException) - `unwrapErr(): mixed` - Returns the error value or throws UnwrapException (extends LogicException) +- `expect(string $message): mixed` - Returns the success value or throws UnwrapException with the given message and a summary of the error value +- `expectErr(string $message): mixed` - Returns the error value or throws UnwrapException with the given message and a summary of the success value - `unwrapOr(mixed $default): mixed` - Returns the success value or a default - `unwrapOrElse(callable $fn): mixed` - Returns the success value or computes it from the error diff --git a/src/Err.php b/src/Err.php index ade25ab..476c171 100644 --- a/src/Err.php +++ b/src/Err.php @@ -62,6 +62,21 @@ public function unwrapErr(): mixed return $this->value; } + #[Override] + public function expect(string $message): never + { + throw UnwrapException::withMessage($message, $this->value); + } + + /** + * @return E + */ + #[Override] + public function expectErr(string $message): mixed + { + return $this->value; + } + /** * @template U * @param U $default diff --git a/src/Ok.php b/src/Ok.php index d494672..865ee7c 100644 --- a/src/Ok.php +++ b/src/Ok.php @@ -62,6 +62,21 @@ public function unwrapErr(): never throw UnwrapException::unwrapErrOnOk($this->value); } + /** + * @return T + */ + #[Override] + public function expect(string $message): mixed + { + return $this->value; + } + + #[Override] + public function expectErr(string $message): never + { + throw UnwrapException::withMessage($message, $this->value); + } + /** * @template U * @param U $default diff --git a/src/Result.php b/src/Result.php index c5e72a1..c10998f 100644 --- a/src/Result.php +++ b/src/Result.php @@ -74,6 +74,28 @@ public function unwrap(): mixed; */ public function unwrapErr(): mixed; + /** + * 成功値を返します。失敗の場合は指定したメッセージで例外を投げます. + * + * @param string $message 失敗時の例外メッセージ(エラー値の要約が付加されます) + * + * @return ($this is Ok ? T : never) + * + * @throws UnwrapException $this が Err の場合 + */ + public function expect(string $message): mixed; + + /** + * エラー値を返します。成功の場合は指定したメッセージで例外を投げます. + * + * @param string $message 成功時の例外メッセージ(成功値の要約が付加されます) + * + * @return ($this is Err ? E : never) + * + * @throws UnwrapException $this が Ok の場合 + */ + public function expectErr(string $message): mixed; + /** * 成功値またはデフォルト値を返します. * diff --git a/src/UnwrapException.php b/src/UnwrapException.php index 6fb9eaf..ed8dca5 100644 --- a/src/UnwrapException.php +++ b/src/UnwrapException.php @@ -35,6 +35,14 @@ public static function unwrapErrOnOk(mixed $value): self return new self(\sprintf('called Result::unwrapErr() on an Ok value: %s', self::describe($value))); } + /** + * expect() / expectErr() 用に、呼び出し側のメッセージと値の要約から例外を生成します. + */ + public static function withMessage(string $message, mixed $value): self + { + return new self(\sprintf('%s: %s', $message, self::describe($value))); + } + /** * 例外メッセージ用に値の要約を生成します. * diff --git a/tests/ErrTest.php b/tests/ErrTest.php index 01b44af..97b1d31 100644 --- a/tests/ErrTest.php +++ b/tests/ErrTest.php @@ -59,6 +59,22 @@ public function unwrap_throws_exception(): void $err->unwrap(); } + #[Test] + public function expect_throws_withGivenMessage(): void + { + $err = new Err('error'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('config file should be readable'); + $err->expect('config file should be readable'); + } + + #[Test] + public function expectErr_returns_error_value(): void + { + $err = new Err('error'); + $this->assertSame('error', $err->expectErr('should have an error')); + } + #[Test] public function unwrap_throwsUnwrapException_withErrorValueInMessage(): void { @@ -146,6 +162,15 @@ public function unwrap_withMultilineStringError_keepsMessageSingleLine(): void } } + #[Test] + public function expect_throwsUnwrapException_withErrorValueInMessage(): void + { + $err = new Err(new \RuntimeException('boom')); + $this->expectException(UnwrapException::class); + $this->expectExceptionMessage('config file should be readable: RuntimeException: boom'); + $err->expect('config file should be readable'); + } + #[Test] public function unwrapErr_returns_error_value(): void { diff --git a/tests/OkTest.php b/tests/OkTest.php index 5235ab9..f8bf470 100644 --- a/tests/OkTest.php +++ b/tests/OkTest.php @@ -90,6 +90,22 @@ public function unwrapErr_throws_exception(): void $ok->unwrapErr(); } + #[Test] + public function expect_returns_value(): void + { + $ok = new Ok(42); + $this->assertSame(42, $ok->expect('should have a value')); + } + + #[Test] + public function expectErr_throws_withGivenMessage(): void + { + $ok = new Ok(42); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('should have an error'); + $ok->expectErr('should have an error'); + } + #[Test] public function unwrapErr_throwsUnwrapException_withValueInMessage(): void { @@ -114,6 +130,15 @@ public function __toString(): string $ok->unwrapErr(); } + #[Test] + public function expectErr_throwsUnwrapException_withValueInMessage(): void + { + $ok = new Ok(42); + $this->expectException(UnwrapException::class); + $this->expectExceptionMessage('should have an error: 42'); + $ok->expectErr('should have an error'); + } + #[Test] public function unwrapOr_returns_value(): void { diff --git a/tests/Types/result.php b/tests/Types/result.php index 1ba6366..5b7ec14 100644 --- a/tests/Types/result.php +++ b/tests/Types/result.php @@ -254,6 +254,17 @@ function testUnwrapOnGenericReceiver(Result $result): void assertType('RuntimeException', $result->unwrapErr()); } +/** + * expect / expectErr も unwrap / unwrapErr と同じ条件付き戻り値型が解決される. + * + * @param Result $result + */ +function testExpectOnGenericReceiver(Result $result): void +{ + assertType('int', $result->expect('should have a value')); + assertType('RuntimeException', $result->expectErr('should have an error')); +} + /** * 具象レシーバでの unwrapOr / unwrapOrElse: 実行時に起こり得ない側の型を混ぜない. *