From a3147f97472f7fd3df9e51c58bcf2f593929f870 Mon Sep 17 00:00:00 2001 From: someniatko Date: Wed, 3 Aug 2022 18:37:32 +0300 Subject: [PATCH] introduce `Option::ensure()`, `Result::ensure()` --- src/Error.php | 5 +++++ src/None.php | 5 +++++ src/Option.php | 13 +++++++++++++ src/Result.php | 2 ++ src/ResultInterface.php | 14 ++++++++++++++ src/Some.php | 7 +++++++ src/Success.php | 7 +++++++ test/OptionTest.php | 22 ++++++++++++++++++++++ test/ResultTest.php | 21 +++++++++++++++++++++ 9 files changed, 96 insertions(+) diff --git a/src/Error.php b/src/Error.php index d6d8dd9..44ef760 100644 --- a/src/Error.php +++ b/src/Error.php @@ -52,4 +52,9 @@ public function getOrThrow(\Throwable $e) { throw $e; } + + public function ensure(callable $condition, $else): ResultInterface + { + return $this; + } } diff --git a/src/None.php b/src/None.php index 8c5b757..09c0509 100644 --- a/src/None.php +++ b/src/None.php @@ -34,4 +34,9 @@ public function getOrThrow(\Throwable $e) { throw $e; } + + public function ensure(callable $condition): Option + { + return $this; + } } diff --git a/src/Option.php b/src/Option.php index cdb61ee..be6c96e 100644 --- a/src/Option.php +++ b/src/Option.php @@ -14,6 +14,7 @@ abstract class Option private static ?None $none = null; /** + * @psalm-pure * @template TNew * @param TNew $value * @return Some @@ -24,10 +25,12 @@ public static function some($value): Some } /** + * @psalm-pure * @return None */ public static function none(): None { + /** @psalm-suppress ImpureStaticProperty */ return self::$none ??= new None(); } @@ -113,6 +116,16 @@ abstract public function getOrElse($else); */ abstract public function getOrThrow(\Throwable $e); + /** + * Ensures that Some value also validates against the given condition, + * Otherwise returns None. + * + * @return Option + * + * @param callable(TValue):bool $condition + */ + abstract public function ensure(callable $condition): Option; + /** * @template TElse * @param TElse $else diff --git a/src/Result.php b/src/Result.php index d4f66e0..138a0be 100644 --- a/src/Result.php +++ b/src/Result.php @@ -13,6 +13,7 @@ abstract class Result implements ResultInterface { /** + * @psalm-pure * @template T * @param T $value * @return Success @@ -23,6 +24,7 @@ public static function success($value): Success } /** + * @psalm-pure * @template T * @param T $value * @return Error diff --git a/src/ResultInterface.php b/src/ResultInterface.php index 466b91e..62fb4ad 100644 --- a/src/ResultInterface.php +++ b/src/ResultInterface.php @@ -78,4 +78,18 @@ public function getOr(callable $map); * @return TSuccess|never-return */ public function getOrThrow(\Throwable $e); + + /** + * Ensures that the Success value also validates against the given condition, + * Otherwise returns an Error with a given value. + * + * If this Result is already an Error, nothing will change. + * + * @template TNewError + * + * @param callable(TSuccess):bool $condition + * @param TNewError $else + * @return ResultInterface + */ + public function ensure(callable $condition, $else): self; } diff --git a/src/Some.php b/src/Some.php index 59c968b..8b82188 100644 --- a/src/Some.php +++ b/src/Some.php @@ -46,4 +46,11 @@ public function getOrThrow(\Throwable $e) { return $this->value; } + + public function ensure(callable $condition): Option + { + return $condition($this->value) + ? $this + : Option::none(); + } } diff --git a/src/Success.php b/src/Success.php index edf4b47..fea0ff1 100644 --- a/src/Success.php +++ b/src/Success.php @@ -51,4 +51,11 @@ public function getOrThrow(\Throwable $e) { return $this->value; } + + public function ensure(callable $condition, $else): ResultInterface + { + return $condition($this->value) + ? $this + : Result::error($else); + } } diff --git a/test/OptionTest.php b/test/OptionTest.php index 3c48d24..f90e3ca 100644 --- a/test/OptionTest.php +++ b/test/OptionTest.php @@ -124,4 +124,26 @@ public function testToNullableNone(): void $nullable = $option->toNullable(); self::assertNull($nullable); } + + public function testEnsureSomeReturningTrue(): void + { + $option = Option::some(111); + $ensured = $option->ensure(fn (int $i) => $i > 100); + self::assertEquals(111, $ensured->getOrElse(null)); + } + + public function testEnsureSomeReturningFalse(): void + { + $option = Option::some(111); + $ensured = $option->ensure(fn (int $i) => $i < 100); + self::assertEquals(null, $ensured->getOrElse(null)); + } + + public function testEnsureNone(): void + { + /** @var Option $option */ + $option = Option::none(); + $ensured = $option->ensure(fn (int $i) => $i > 100); + self::assertEquals(null, $ensured->getOrElse(null)); + } } diff --git a/test/ResultTest.php b/test/ResultTest.php index 91ce320..e758d9a 100644 --- a/test/ResultTest.php +++ b/test/ResultTest.php @@ -173,4 +173,25 @@ public function testErrors(): void self::assertEquals([ 3, 5 ], Result::extractErrors($results)); } + + public function testEnsureSuccessReturningTrue(): void + { + $result = Result::success(123); + $ensured = $result->ensure(fn(int $i) => $i > 100, 'failed'); + self::assertEquals(123, $ensured->get()); + } + + public function testEnsureSuccessReturningFalse(): void + { + $result = Result::success(123); + $ensured = $result->ensure(fn(int $i) => $i < 100, 'failed'); + self::assertEquals('failed', $ensured->get()); + } + + public function testEnsureErrorDoesNotChangePreviousError(): void + { + $result = Result::error('old error'); + $ensured = $result->ensure(fn(int $i) => $i > 100, 'new error'); + self::assertEquals('old error', $ensured->get()); + } }