[Result] Add proceed and map with helper functions (#65)

This commit is contained in:
Toon Verwerft 2020-10-01 13:19:52 +02:00 committed by GitHub
parent ed0cae6421
commit fbc60c93bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 246 additions and 4 deletions

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Psl\Fun;
/**
* This method creates a callback that returns the value passed as argument.
* It can e.g. be used as a success callback.
*
* @template T
* @psalm-return callable(T): T
* @psalm-pure
*/
function passthrough(): callable
{
return static fn ($result) => $result;
}

21
src/Psl/Fun/rethrow.php Normal file
View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Psl\Fun;
use Exception;
/**
* This method creates a callback that throws the exception passed as argument.
* It can e.g. be used as a failure callback.
*
* @psalm-return callable(Exception): no-return
* @psalm-pure
*/
function rethrow(): callable
{
return static function (Exception $exception): void {
throw $exception;
};
}

View File

@ -96,7 +96,9 @@ final class Loader
'Psl\Arr\map_keys',
'Psl\Arr\map_with_key',
'Psl\Fun\after',
'Psl\Fun\passthrough',
'Psl\Fun\pipe',
'Psl\Fun\rethrow',
'Psl\Internal\boolean',
'Psl\Internal\type',
'Psl\Internal\validate_offset',

View File

@ -65,4 +65,25 @@ final class Failure implements ResultInterface
{
return true;
}
/**
* Unwrapping and transforming a result can be done by using the proceed method.
* Since this is a failed result wrapper, the `$on_failure` callback will be triggered.
* The callback will receive the Exception as an argument, so that you can transform it to anything you want.
*/
public function proceed(callable $on_success, callable $on_failure)
{
return $on_failure($this->exception);
}
/**
* The method can be used to transform a result into another result.
* Since this is a failure result wrapper, the `$on_failure` callback will be triggered.
* The callback will receive the Exception as an argument,
* so that you can use it to create a success result or rethrow the Exception.
*/
public function then(callable $on_success, callable $on_failure): ResultInterface
{
return wrap(fn () => $on_failure($this->exception));
}
}

View File

@ -55,4 +55,38 @@ interface ResultInterface
* @return bool - `true` if the operation failed; `false` otherwise
*/
public function isFailed(): bool;
/**
* Unwrapping and transforming a result can be done by using the proceed method.
* The implementation will either run the `$on_success` or `$on_failure` callback.
* The callback will receive the result or Exception as an argument,
* so that you can transform it to anything you want.
*
* @template R
*
* @psalm-param callable(T): R $on_success
* @psalm-param callable(Exception): R $on_failure
*
* @psalm-return R
*/
public function proceed(callable $on_success, callable $on_failure);
/**
* The method can be used to transform a result into another result.
* The implementation will either run the `$on_success` or `$on_failure` callback.
* The callback will receive the result value or Exception as an argument,
* so that you can transform use it to build a new result.
*
* This method is compatible with the `PromiseInterface::then()` function from `reactphp/promise`.
* You can use it in an async context as long as the package you are using is compatible with reactphp promises.
*
* @link https://github.com/reactphp/promise#promiseinterfacethen
*
* @template R
* @psalm-param callable(T): R $on_success
* @psalm-param callable(Exception): R $on_failure
*
* @psalm-return ResultInterface<R>
*/
public function then(callable $on_success, callable $on_failure): ResultInterface;
}

View File

@ -73,4 +73,24 @@ final class Success implements ResultInterface
{
return false;
}
/**
* Unwrapping and transforming a result can be done by using the proceed method.
* Since this is a success result wrapper, the `$on_success` callback will be triggered.
* The callback will receive the result value as an argument, so that you can transform it to anything you want.
*/
public function proceed(callable $on_success, callable $on_failure)
{
return $on_success($this->value);
}
/**
* The method can be used to transform a result into another result.
* Since this is a success result wrapper, the `$on_success` callback will be triggered.
* The callback will receive the result value as an argument, so that you can use it to create a new result.
*/
public function then(callable $on_success, callable $on_failure): ResultInterface
{
return wrap(fn () => $on_success($this->value));
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Fun;
use PHPUnit\Framework\TestCase;
use Psl\Fun;
class PassthroughTest extends TestCase
{
public function testPassthrough(): void
{
$expected = 'x';
$passthrough = Fun\passthrough();
self::assertSame($expected, $passthrough($expected));
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Fun;
use PHPUnit\Framework\TestCase;
use Psl\Fun;
class RethrowTest extends TestCase
{
public function testRethrow(): void
{
$exception = new \Exception('foo');
$rethrow = Fun\rethrow();
$this->expectExceptionObject($exception);
$rethrow($exception);
}
}

View File

@ -4,26 +4,29 @@ declare(strict_types=1);
namespace Psl\Tests\Result;
use Exception;
use PHPUnit\Framework\TestCase;
use Psl\Fun;
use Psl\Result\Failure;
use stdClass;
class FailureTest extends TestCase
{
public function testIsSucceeded(): void
{
$wrapper = new Failure(new \Exception('foo'));
$wrapper = new Failure(new Exception('foo'));
self::assertFalse($wrapper->isSucceeded());
}
public function testIsFailed(): void
{
$wrapper = new Failure(new \Exception('foo'));
$wrapper = new Failure(new Exception('foo'));
self::assertTrue($wrapper->isFailed());
}
public function testGetResult(): void
{
$exception = new \Exception('bar');
$exception = new Exception('bar');
$wrapper = new Failure($exception);
$this->expectExceptionObject($exception);
@ -32,9 +35,51 @@ class FailureTest extends TestCase
public function testGetException(): void
{
$exception = new \Exception('bar');
$exception = new Exception('bar');
$wrapper = new Failure($exception);
$e = $wrapper->getException();
self::assertSame($exception, $e);
}
public function testProceed(): void
{
$exception = new Exception('bar');
$wrapper = new Failure($exception);
$actual = $wrapper->proceed(
static fn (string $result): int => 200,
static fn (Exception $exception): int => 404
);
self::assertSame(404, $actual);
}
public function testThenToSuccess(): void
{
$exception = new Exception('bar');
$wrapper = new Failure($exception);
$actual = $wrapper->then(
static function () {
throw new \Exception('Dont call us, we\'ll call you!');
},
static fn (Exception $exception): string => $exception->getMessage()
);
self::assertTrue($actual->isSucceeded());
self::assertSame($actual->getResult(), 'bar');
}
public function testThenToFailure(): void
{
$exception = new Exception('bar');
$wrapper = new Failure($exception);
$actual = $wrapper->then(
static function () {
throw new \Exception('Dont call us, we\'ll call you!');
},
Fun\rethrow()
);
self::assertFalse($actual->isSucceeded());
self::assertSame($actual->getException(), $exception);
}
}

View File

@ -4,9 +4,12 @@ declare(strict_types=1);
namespace Psl\Tests\Result;
use Exception;
use PHPUnit\Framework\TestCase;
use Psl\Fun;
use Psl\Result\Success;
use Psl\Exception\InvariantViolationException;
use stdClass;
class SuccessTest extends TestCase
{
@ -37,4 +40,43 @@ class SuccessTest extends TestCase
$wrapper->getException();
}
public function testProceed(): void
{
$wrapper = new Success('hello');
$actual = $wrapper->proceed(
static fn (string $result): int => 200,
static fn (Exception $exception): int => 404
);
self::assertSame(200, $actual);
}
public function testThenToSuccess(): void
{
$wrapper = new Success('hello');
$actual = $wrapper->then(
Fun\passthrough(),
Fun\rethrow()
);
self::assertNotSame($wrapper, $actual);
self::assertTrue($actual->isSucceeded());
self::assertSame('hello', $actual->getResult());
}
public function testThenToFailure(): void
{
$exception = new Exception('bar');
$wrapper = new Success('hello');
$actual = $wrapper->then(
static function () use ($exception) {
throw $exception;
},
Fun\rethrow()
);
self::assertFalse($actual->isSucceeded());
self::assertSame($actual->getException(), $exception);
}
}