Skip to content

Commit

Permalink
[Result] Add proceed and map with helper functions (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee authored Oct 1, 2020
1 parent ed0cae6 commit fbc60c9
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 4 deletions.
18 changes: 18 additions & 0 deletions src/Psl/Fun/passthrough.php
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions src/Psl/Fun/rethrow.php
Original file line number Diff line number Diff line change
@@ -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;
};
}
2 changes: 2 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
21 changes: 21 additions & 0 deletions src/Psl/Result/Failure.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,25 @@ public function isFailed(): bool
{
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));
}
}
34 changes: 34 additions & 0 deletions src/Psl/Result/ResultInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,38 @@ public function isSucceeded(): bool;
* @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;
}
20 changes: 20 additions & 0 deletions src/Psl/Result/Success.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,24 @@ public function isFailed(): bool
{
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));
}
}
19 changes: 19 additions & 0 deletions tests/Psl/Fun/PassthroughTest.php
Original file line number Diff line number Diff line change
@@ -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));
}
}
20 changes: 20 additions & 0 deletions tests/Psl/Fun/RethrowTest.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
53 changes: 49 additions & 4 deletions tests/Psl/Result/FailureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,29 @@

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);
Expand All @@ -32,9 +35,51 @@ public function testGetResult(): void

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);
}
}
42 changes: 42 additions & 0 deletions tests/Psl/Result/SuccessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

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
{
Expand Down Expand Up @@ -37,4 +40,43 @@ public function testGetException(): void

$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);
}
}

0 comments on commit fbc60c9

Please sign in to comment.