Skip to content

Commit

Permalink
Add factory class
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmaudo committed Oct 16, 2023
1 parent 251028e commit 0c865b9
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 12 deletions.
6 changes: 4 additions & 2 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ function nav() {
activeMatch: '/reference/',
items: [
{ text: 'Ok', link: '/reference/ok' },
{ text: 'Error', link: '/reference/error' }
{ text: 'Error', link: '/reference/error' },
{ text: 'Result', link: '/reference/result' }
]
}
]
Expand All @@ -55,7 +56,8 @@ function sidebarReference() {
link: '/reference/',
items: [
{ text: 'Ok', link: '/reference/ok' },
{ text: 'Error', link: '/reference/error' }
{ text: 'Error', link: '/reference/error' },
{ text: 'Result', link: '/reference/result' }
]
}
]
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ Results
- [`Ok`](ok) contains the success value.

- [`Error`](error) contains the error exception.

Factory
-------

- [`Result`](result) makes new results.
49 changes: 49 additions & 0 deletions docs/reference/result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

# Result


Factory class to make new `Ok` and `Error` instances.

This class cannot be instantiated.


## Static Methods


### of

```php
public static function of(mixed $value): Ok|Error;
```

Makes an `Ok` with the given `value`.

**Note:** If `value` is a closure, this method will call it and
use the returned value to make the result, returning an `Error`
if any exception is thrown.


### fromFalsable

```php
public static function fromFalsable(mixed $value): Ok|Error;
```

Makes an empty `Error` if the value is `false`. Otherwise,
makes an `Ok` with the given `value`.

**Note:** If `value` is a closure, this method will call it and
use the returned value to make the result.


### fromNullable

```php
public static function fromNullable(mixed $value): Ok|Error;
```

Makes an empty `Error` if the value is `null`. Otherwise,
makes an `Ok` with the given `value`.

**Note:** If `value` is a closure, this method will call it and
use the returned value to make the result.
5 changes: 5 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
</ignoreFiles>
</projectFiles>
<issueHandlers>
<RedundantConditionGivenDocblockType>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</RedundantConditionGivenDocblockType>
<UnusedClass>
<errorLevel type="suppress">
<directory name="tests"/>
Expand Down
99 changes: 99 additions & 0 deletions src/Result.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Hereldar\Results;

use Closure;
use RuntimeException;
use Throwable;

/**
* Factory class to make new `Ok` and `Error` instances.
*
* This class cannot be instantiated.
*/
final class Result
{
private function __construct() {}

/**
* Makes an `Ok` with the given `value`.
*
* **Note:** If `value` is a closure, this method will call it and
* use the returned value to make the result, returning an `Error`
* if any exception is thrown.
*
* @template U
*
* @param U|Closure():U $value
*
* @return Ok<U>|Error<Throwable>
*
* @psalm-suppress MixedAssignment
*/
public static function of(mixed $value): Ok|Error
{
if ($value instanceof Closure) {
try {
$value = $value();
} catch (Throwable $e) {
return Error::withException($e);
}
}

return Ok::withValue($value);
}

/**
* Makes an empty `Error` if the value is `null`. Otherwise, makes
* an `Ok` with the given `value`.
*
* **Note:** If `value` is a closure, this method will call it and
* use the returned value to make the result.
*
* @template U
*
* @param (U|null)|Closure():(U|null) $value
*
* @return Ok<U>|Error<RuntimeException>
*
* @psalm-suppress MixedAssignment
*/
public static function fromNullable(mixed $value): Ok|Error
{
if ($value instanceof Closure) {
$value = $value();
}

return ($value === null)
? Error::empty()
: Ok::withValue($value);
}

/**
* Makes an empty `Error` if the value is `false`. Otherwise,
* makes an `Ok` with the given `value`.
*
* **Note:** If `value` is a closure, this method will call it and
* use the returned value to make the result.
*
* @template U
*
* @param (U|false)|Closure():(U|false) $value
*
* @return Ok<U>|Error<RuntimeException>
*
* @psalm-suppress MixedAssignment
*/
public static function fromFalsable(mixed $value): Ok|Error
{
if ($value instanceof Closure) {
$value = $value();
}

return ($value === false)
? Error::empty()
: Ok::withValue($value);
}
}
193 changes: 193 additions & 0 deletions tests/ResultTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<?php

declare(strict_types=1);

namespace Hereldar\Results\Tests;

use Hereldar\Results\Error;
use Hereldar\Results\Ok;
use Hereldar\Results\Result;
use RuntimeException;

final class ResultTest extends TestCase
{
/**
* @psalm-suppress InaccessibleMethod
*/
public function testPrivateConstructor(): void
{
self::assertException(
\Error::class,
fn() => new Result() // @phpstan-ignore-line
);
}

public function testOfValue(): void
{
$result = Result::of(null);
self::assertInstanceOf(Ok::class, $result);
self::assertNull($result->value());

$result = Result::of(false);
self::assertInstanceOf(Ok::class, $result);
self::assertFalse($result->value());

$value = $this->random()->randomNumber();
$result = Result::of($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->randomFloat();
$result = Result::of($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->word();
$result = Result::of($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());
}

public function testOfClosure(): void
{
$result = Result::of(fn() => null);
self::assertInstanceOf(Ok::class, $result);
self::assertNull($result->value());

$result = Result::of(fn() => false);
self::assertInstanceOf(Ok::class, $result);
self::assertFalse($result->value());

$value = $this->random()->randomNumber();
$result = Result::of(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->randomFloat();
$result = Result::of(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->word();
$result = Result::of(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$exception = new RuntimeException();
$result = Result::of(fn() => throw $exception);
self::assertInstanceOf(Error::class, $result);
self::assertSame($exception, $result->exception());
}

public function testFromNullableValue(): void
{
$result = Result::fromNullable(null);
self::assertInstanceOf(Error::class, $result);

$result = Result::fromNullable(false);
self::assertInstanceOf(Ok::class, $result);
self::assertFalse($result->value());

$value = $this->random()->randomNumber();
$result = Result::fromNullable($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->randomFloat();
$result = Result::fromNullable($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->word();
$result = Result::fromNullable($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());
}

public function testFromNullableClosure(): void
{
$result = Result::fromNullable(fn() => null);
self::assertInstanceOf(Error::class, $result);

$result = Result::fromNullable(fn() => false);
self::assertInstanceOf(Ok::class, $result);
self::assertFalse($result->value());

$value = $this->random()->randomNumber();
$result = Result::fromNullable(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->randomFloat();
$result = Result::fromNullable(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->word();
$result = Result::fromNullable(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$exception = new RuntimeException();
self::assertException(
$exception,
fn() => Result::fromNullable(fn() => throw $exception)
);
}

public function testFromFalsableValue(): void
{
$result = Result::fromFalsable(null);
self::assertInstanceOf(Ok::class, $result);
self::assertNull($result->value());

$result = Result::fromFalsable(false);
self::assertInstanceOf(Error::class, $result);

$value = $this->random()->randomNumber();
$result = Result::fromFalsable($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->randomFloat();
$result = Result::fromFalsable($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->word();
$result = Result::fromFalsable($value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());
}

public function testFromFalsableClosure(): void
{
$result = Result::fromFalsable(fn() => null);
self::assertInstanceOf(Ok::class, $result);
self::assertNull($result->value());

$result = Result::fromFalsable(fn() => false);
self::assertInstanceOf(Error::class, $result);

$value = $this->random()->randomNumber();
$result = Result::fromFalsable(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->randomFloat();
$result = Result::fromFalsable(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$value = $this->random()->word();
$result = Result::fromFalsable(fn() => $value);
self::assertInstanceOf(Ok::class, $result);
self::assertSame($value, $result->value());

$exception = new RuntimeException();
self::assertException(
$exception,
fn() => Result::fromFalsable(fn() => throw $exception)
);
}
}

0 comments on commit 0c865b9

Please sign in to comment.