Merge pull request #16 from azjezz/asio

[Asio] introduce ResultOrException wrapper
This commit is contained in:
Saif Eddin G 2020-03-01 12:00:15 +01:00 committed by GitHub
commit c74da9d328
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 404 additions and 4 deletions

View File

@ -16,6 +16,9 @@
<testsuite name="Psl Arr">
<directory>tests/Psl/Arr</directory>
</testsuite>
<testsuite name="Psl Asio">
<directory>tests/Psl/Asio</directory>
</testsuite>
<testsuite name="Psl Collection">
<directory>tests/Psl/Collection</directory>
</testsuite>

View File

@ -13,4 +13,39 @@
<file name="src/bootstrap.php"/>
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="error"/>
<DeprecatedMethod errorLevel="error"/>
<DeprecatedProperty errorLevel="error"/>
<DeprecatedClass errorLevel="error"/>
<DeprecatedConstant errorLevel="error"/>
<DeprecatedInterface errorLevel="error"/>
<DeprecatedTrait errorLevel="error"/>
<ForbiddenCode errorLevel="suppress"/>
<InternalMethod errorLevel="suppress"/>
<InternalProperty errorLevel="error"/>
<InternalClass errorLevel="error"/>
<MissingClosureReturnType errorLevel="error"/>
<MissingReturnType errorLevel="error"/>
<MissingPropertyType errorLevel="error"/>
<InvalidDocblock errorLevel="error"/>
<MisplacedRequiredParam errorLevel="error"/>
<PropertyNotSetInConstructor errorLevel="suppress"/>
<MissingConstructor errorLevel="error"/>
<MissingClosureParamType errorLevel="error"/>
<MissingParamType errorLevel="error"/>
<RedundantCondition errorLevel="error"/>
<DocblockTypeContradiction errorLevel="error"/>
<RedundantConditionGivenDocblockType errorLevel="error"/>
<RawObjectIteration errorLevel="error"/>
<InvalidStringClass errorLevel="error"/>
<UnresolvableInclude errorLevel="suppress"/>
</issueHandlers>
</psalm>

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Psl\Asio;
use Psl;
use Exception;
/**
* Represents a result of operation that either has a successful result or the exception object if that operation failed.
*
* This is an interface. You get generally `IResultOrExceptionWrapper<T>` by calling `wrap<T>()`, passing in the `callable(): T`,
* and a `WrappedResult<T>` or `WrappedException<Te>` is returned.
*
* @template T
*/
interface IResultOrExceptionWrapper
{
/**
* Return the result of the operation, or throw underlying exception.
*
* - if the operation succeeded: return its result.
* - if the operation failed: throw the exception inciting failure.
*
* @psalm-return T - The result of the operation upon success
*/
public function getResult();
/**
* Return the underlying exception, or fail with a invariant violation exception exception.
*
* - if the operation succeeded: fails with a invariant violation exception.
* - if the operation failed: returns the exception indicating failure.
*
* @throws Psl\Exception\InvariantViolationException - When the operation succeeded
*/
public function getException(): Exception;
/**
* Indicates whether the operation associated with this wrapper existed normally.
*
* if `isSucceeded()` returns `true`, `isFailed()` returns false.
*
* @return bool - `true` if the operation succeeded; `false` otherwise
*/
public function isSucceeded(): bool;
/**
* Indicates whether the operation associated with this wrapper exited abnormally via an exception of some sort.
*
* if `isFailed()` returns `true`, `isSucceeded()` returns false.
*
* @return bool - `true` if the operation failed; `false` otherwise
*/
public function isFailed(): bool;
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Psl\Asio;
use Psl;
use Psl\Str;
use Exception;
/**
* Represents the result of failed operation.
*
* @template T
* @template Te of Exception
*
* @implements IResultOrExceptionWrapper<T>
*/
final class WrappedException implements IResultOrExceptionWrapper
{
/**
* @psalm-var Te
*/
private Exception $exception;
/**
* @psalm-param Te $exception
*/
public function __construct(Exception $exception)
{
$this->exception = $exception;
}
/**
* Since this is a failed result wrapper, this always throws the exception thrown during the operation.
*
* @throws Exception
*/
public function getResult(): void
{
throw $this->exception;
}
/**
* Since this is a failed result wrapper, this always returns the exception thrown during the operation.
*
* @psalm-return Te - The exception thrown during the operation.
*/
public function getException(): Exception
{
return $this->exception;
}
/**
* Since this is a failed result wrapper, this always returns `false`.
*/
public function isSucceeded(): bool
{
return false;
}
/**
* Since this is a failed result wrapper, this always returns `true`.
*/
public function isFailed(): bool
{
return true;
}
}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Psl\Asio;
use Psl;
use Psl\Str;
use Exception;
/**
* Represents the result of successful operation.
*
* @template T
*
* @implements IResultOrExceptionWrapper<T>
*/
final class WrappedResult implements IResultOrExceptionWrapper
{
/**
* @psalm-var T
*/
private $value;
/**
* @psalm-param T $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Since this is a successful result wrapper, this always returns the actual result of the operation.
*
* @psalm-return T
*/
public function getResult()
{
return $this->value;
}
/**
* Since this is a successful result wrapper, this always throws a
* `Psl\Exception\InvariantViolationException` saying that there was no exception thrown from the operation.
*
* @throws Psl\Exception\InvariantViolationException
*
* @psalm-return no-return
*/
public function getException(): Exception
{
Psl\invariant_violation('No exception thrown from the operation.');
}
/**
* Since this is a successful result wrapper, this always returns `true`.
*
* @return true
*/
public function isSucceeded(): bool
{
return true;
}
/**
* Since this is a successful result wrapper, this always returns `false`.
*
* @return false
*/
public function isFailed(): bool
{
return false;
}
}

25
src/Psl/Asio/wrap.php Normal file
View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Psl\Asio;
/**
* Wrap the given callable result in a `WrappedResult`, or `WrappedException` if the callable throws
* an `Exception`.
*
* @template T
*
* @psalm-param (callable(): T) $fun
*
* @psalm-return IResultOrExceptionWrapper<T>
*/
function wrap(callable $fun): IResultOrExceptionWrapper
{
try {
$result = $fun();
return new WrappedResult($result);
} catch (\Exception $e) {
return new WrappedException($e);
}
}

View File

@ -26,7 +26,7 @@ namespace Psl\Str;
* Str\format('%s is %d character(s) long.', 'س', Str\length('س'));
* => Str('س is 1 character(s) long.')
*
* @psalm-param int|float|string|bool ...$args
* @psalm-param int|float|string ...$args
*
* @return string a string produced according to the formatting string
* format

View File

@ -60,3 +60,15 @@ function boolean($val): bool
{
return (bool) $val;
}
/**
* @param mixed $value
*
* @return string
*
* @codeCoverageIgnore
*/
function type($value): string
{
return \is_object($value) ? \get_class($value) : \gettype($value);
}

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Psl;
/**
* @psalm-param int|float|string|bool ...$args
* @psalm-param int|float|string ...$args
*
* @psalm-assert true $fact
*/
@ -17,9 +17,11 @@ function invariant(bool $fact, string $message, ...$args): void
}
/**
* @psalm-param int|float|string|bool ...$args
* @psalm-param int|float|string ...$args
*
* @pslam-return no-return
* @psalm-return no-return
*
* @throws Exception\InvariantViolationException
*/
function invariant_violation(string $format, ...$args): void
{

View File

@ -11,6 +11,7 @@ function __bootstrap(): void
{
static $booted = false;
static $files = [
'Psl/Asio/wrap.php',
'/Psl/Random/string.php',
'/Psl/Random/int.php',
'/Psl/Random/float.php',

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Asio;
use Psl\Asio;
use PHPUnit\Framework\TestCase;
use Psl\Exception\InvariantViolationException;
class WrapTest extends TestCase
{
public function testWrapException(): void
{
$exception = new \Exception('foo');
$wrapper = Asio\wrap(static function() use ($exception): void {
throw $exception;
});
$this->assertFalse($wrapper->isSucceeded());
$this->assertTrue($wrapper->isFailed());
$this->assertSame($exception, $wrapper->getException());
$this->expectExceptionObject($exception);
$wrapper->getResult();
}
public function testWrapResult(): void
{
$wrapper = Asio\wrap(static function(): string {
return 'foo';
});
$this->assertTrue($wrapper->isSucceeded());
$this->assertFalse($wrapper->isFailed());
$this->assertSame('foo', $wrapper->getResult());
$this->expectException(InvariantViolationException::class);
$this->expectExceptionMessage('No exception thrown');
$wrapper->getException();
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Asio;
use Psl\Asio\WrappedException;
use PHPUnit\Framework\TestCase;
class WrappedExceptionTest extends TestCase
{
public function testIsSucceeded(): void
{
$wrapper = new WrappedException(new \Exception('foo'));
$this->assertFalse($wrapper->isSucceeded());
}
public function testIsFailed(): void
{
$wrapper = new WrappedException(new \Exception('foo'));
$this->assertTrue($wrapper->isFailed());
}
public function testGetResult(): void
{
$exception = new \Exception('bar');
$wrapper = new WrappedException($exception);
$this->expectExceptionObject($exception);
$wrapper->getResult();
}
public function testGetException(): void
{
$exception = new \Exception('bar');
$wrapper = new WrappedException($exception);
$e = $wrapper->getException();
$this->assertSame($exception, $e);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Psl\Tests\Asio;
use Psl\Asio\WrappedResult;
use PHPUnit\Framework\TestCase;
use Psl\Exception\InvariantViolationException;
class WrappedResultTest extends TestCase
{
public function testIsSucceeded(): void
{
$wrapper = new WrappedResult('hello');
$this->assertTrue($wrapper->isSucceeded());
}
public function testIsFailed(): void
{
$wrapper = new WrappedResult('hello');
$this->assertFalse($wrapper->isFailed());
}
public function testGetResult(): void
{
$wrapper = new WrappedResult('hello');
$this->assertSame('hello', $wrapper->getResult());
}
public function testGetException(): void
{
$wrapper = new WrappedResult('hello');
$this->expectException(InvariantViolationException::class);
$this->expectExceptionMessage('No exception thrown');
$wrapper->getException();
}
}