Add toPhpOption; convert both to mostly non-static
This commit is contained in:
parent
7d25b9ea28
commit
5a8a41a660
@ -17,7 +17,7 @@
|
||||
"rss": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp.rss"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8"
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^11",
|
||||
|
265
src/Option.php
265
src/Option.php
@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace BitBadger\InspiredByFSharp;
|
||||
|
||||
use Error;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
@ -25,7 +26,7 @@ use InvalidArgumentException;
|
||||
*/
|
||||
readonly class Option
|
||||
{
|
||||
/** @var ?T $value The value for this option */
|
||||
/** @var T|null $value The value for this option */
|
||||
private mixed $value;
|
||||
|
||||
/**
|
||||
@ -49,6 +50,138 @@ readonly class Option
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this option have a `None` value?
|
||||
*
|
||||
* @return bool True if the option is `None`, false if it is `Some`
|
||||
*/
|
||||
public function isNone(): bool
|
||||
{
|
||||
return is_null($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this option have a `Some` value?
|
||||
*
|
||||
* @return bool True if the option is `Some`, false if it is `None`
|
||||
*/
|
||||
public function isSome(): bool
|
||||
{
|
||||
return !$this->isNone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value, or a default value, from an option
|
||||
*
|
||||
* @param T $default The default value to return if the option is `None`
|
||||
* @return T The `Some` value, or the default value if the option is `None`
|
||||
*/
|
||||
public function getOrDefault(mixed $default): mixed
|
||||
{
|
||||
return $this->value ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value, or return the value of a callable function
|
||||
*
|
||||
* @template U The return type of the callable provided
|
||||
* @param callable(): U $f The callable function to use for `None` options
|
||||
* @return T|mixed The value if `Some`, the result of the callable if `None`
|
||||
*/
|
||||
public function getOrCall(callable $f): mixed
|
||||
{
|
||||
return $this->value ?? $f();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map this optional value to another value
|
||||
*
|
||||
* @template U The type of the mapping function
|
||||
* @param callable(T): U $f The mapping function
|
||||
* @return Option<U> A `Some` instance with the transformed value if `Some`, `None` otherwise
|
||||
*/
|
||||
public function map(callable $f): self
|
||||
{
|
||||
return $this->isSome() ? self::Some($f($this->get())) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function on the value (if it exists)
|
||||
*
|
||||
* @param callable(T): void $f The function to call
|
||||
*/
|
||||
public function iter(callable $f): void
|
||||
{
|
||||
if ($this->isSome()) {
|
||||
$f($this->value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an option into `None` if it does not match the given function
|
||||
*
|
||||
* @param callable(T): bool $f The filter function to run
|
||||
* @return Option<T> The option, if it was `Some` and the function returned true; `None` otherwise
|
||||
*/
|
||||
public function filter(callable $f): self
|
||||
{
|
||||
return match (true) {
|
||||
$this->isNone() => $this,
|
||||
default => $f($this->value) ? $this : self::None(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the option have the given value?
|
||||
*
|
||||
* @param T $value The value to be checked
|
||||
* @param bool $strict True for strict equality (`===`), false for loose equality (`==`)
|
||||
* @return bool True if the value matches, false if not; `None` always returns false
|
||||
*/
|
||||
public function is(mixed $value, bool $strict = true): bool
|
||||
{
|
||||
return match (true) {
|
||||
$this->isNone() => false,
|
||||
default => $strict ? $this->value === $value : $this->value == $value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely retrieve the optional value as a nullable value
|
||||
*
|
||||
* @return T|null The value for `Some` instances, `null` for `None` instances
|
||||
*/
|
||||
public function unwrap(): mixed
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap into the `Result` for a secondary action, returning the result
|
||||
*
|
||||
* @param callable(Option<T>): mixed $f The function to run (return value is ignored)
|
||||
* @return Option<T> The same option provided
|
||||
*/
|
||||
public function tap(callable $f): Option
|
||||
{
|
||||
$f($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this to a PhpOption option
|
||||
*
|
||||
* @return mixed An option from the PhpOption library
|
||||
*/
|
||||
public function toPhpOption(): mixed
|
||||
{
|
||||
return match (true) {
|
||||
$this->isNone() && class_exists('PhpOption\None') => call_user_func('PhpOption\None::create'),
|
||||
class_exists('PhpOption\Some') => call_user_func('PhpOption\Some::create', $this->value),
|
||||
default => throw new Error('PhpOption types could not be found'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Some` option with the given value
|
||||
*
|
||||
@ -83,136 +216,8 @@ readonly class Option
|
||||
{
|
||||
return match (true) {
|
||||
is_object($value) && is_a($value, 'PhpOption\Option') =>
|
||||
$value->isDefined() ? self::Some($value->get()) : self::None(),
|
||||
$value->isDefined() ? self::Some($value->get()) : self::None(),
|
||||
default => new self($value),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this option have a `None` value?
|
||||
*
|
||||
* @param Option<T> $it The option in question
|
||||
* @return bool True if the option is `None`, false if it is `Some`
|
||||
*/
|
||||
public static function isNone(Option $it): bool
|
||||
{
|
||||
return is_null($it->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this option have a `Some` value?
|
||||
*
|
||||
* @param Option<T> $it The option in question
|
||||
* @return bool True if the option is `Some`, false if it is `None`
|
||||
*/
|
||||
public static function isSome(Option $it): bool
|
||||
{
|
||||
return !self::isNone($it);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value, or a default value, from an option
|
||||
*
|
||||
* @param T $default The default value to return if the option is `None`
|
||||
* @param Option<T> $it The option in question
|
||||
* @return T The `Some` value, or the default value if the option is `None`
|
||||
*/
|
||||
public static function defaultValue(mixed $default, Option $it): mixed
|
||||
{
|
||||
return self::isSome($it) ? $it->get() : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value, or return the value of a callable function
|
||||
*
|
||||
* @template U The return type of the callable provided
|
||||
* @param callable(): U $f The callable function to use for `None` options
|
||||
* @param Option<T> $it The option in question
|
||||
* @return T|mixed The value if `Some`, the result of the callable if `None`
|
||||
*/
|
||||
public static function getOrCall(callable $f, Option $it): mixed
|
||||
{
|
||||
return self::isSome($it) ? $it->get() : $f();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map this optional value to another value
|
||||
*
|
||||
* @template U The type of the mapping function
|
||||
* @param callable(T): U $f The mapping function
|
||||
* @param Option<T> $it The option in question
|
||||
* @return Option<U> A `Some` instance with the transformed value if `Some`, `None` otherwise
|
||||
*/
|
||||
public static function map(callable $f, Option $it): self
|
||||
{
|
||||
return self::isSome($it) ? self::Some($f($it->get())) : self::None();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function on the value (if it exists)
|
||||
*
|
||||
* @param callable(T): void $f The function to call
|
||||
* @param Option<T> $it The option in question
|
||||
*/
|
||||
public static function iter(callable $f, Option $it): void
|
||||
{
|
||||
if (self::isSome($it)) {
|
||||
$f($it->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an option into `None` if it does not match the given function
|
||||
*
|
||||
* @param callable(T): bool $f The filter function to run
|
||||
* @param Option<T> $it The option in question
|
||||
* @return Option<T> The option, if it was `Some` and the function returned true; `None` otherwise
|
||||
*/
|
||||
public static function filter(callable $f, Option $it): self
|
||||
{
|
||||
return match (true) {
|
||||
self::isNone($it) => self::None(),
|
||||
default => $f($it->get()) ? self::Some($it->get()) : self::None(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the option have the given value?
|
||||
*
|
||||
* @param T $value The value to be checked
|
||||
* @param Option<T> $it The option in question
|
||||
* @param bool $strict True for strict equality (`===`), false for loose equality (`==`)
|
||||
* @return bool True if the value matches, false if not; `None` always returns false
|
||||
*/
|
||||
public static function is(mixed $value, Option $it, bool $strict = true): bool
|
||||
{
|
||||
return match (true) {
|
||||
self::isNone($it) => false,
|
||||
default => $strict ? $it->value === $value : $it->value == $value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely retrieve the optional value as a nullable value
|
||||
*
|
||||
* @param Option<T> $it The option in question
|
||||
* @return ?T The value for `Some` instances, `null` for `None` instances
|
||||
*/
|
||||
public static function unwrap(Option $it): mixed
|
||||
{
|
||||
return self::isSome($it) ? $it->get() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap into the `Result` for a secondary action, returning the result
|
||||
*
|
||||
* @param callable(Option<T>): mixed $f The function to run (return value is ignored)
|
||||
* @param Option<T> $it The option against which the function should be run
|
||||
* @return Option<T> The same option provided
|
||||
*/
|
||||
public static function tap(callable $f, Option $it): Option
|
||||
{
|
||||
$f($it);
|
||||
return $it;
|
||||
}
|
||||
}
|
||||
|
163
src/Result.php
163
src/Result.php
@ -66,6 +66,84 @@ readonly class Result
|
||||
return $this->errorValue->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this result `OK`?
|
||||
*
|
||||
* @return bool True if the result is `OK`, false if it is `Error`
|
||||
*/
|
||||
public function isOK(): bool
|
||||
{
|
||||
return $this->okValue->isSome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this result `Error`?
|
||||
*
|
||||
* @return bool True if the result is `Error`, false if it is `OK`
|
||||
*/
|
||||
public function isError(): bool
|
||||
{
|
||||
return $this->errorValue->isSome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an `OK` result to another, leaving an `Error` result unmodified
|
||||
*
|
||||
* @template U The type of the mapping function
|
||||
* @param callable(TOK): U $f The mapping function
|
||||
* @return Result<U, TError> A transformed `OK` instance or the original `Error` instance
|
||||
*/
|
||||
public function map(callable $f): self
|
||||
{
|
||||
return $this->isOK() ? self::OK($f($this->getOK())) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an `Error` result to another, leaving an `OK` result unmodified
|
||||
*
|
||||
* @template U The type of the mapping function
|
||||
* @param callable(TError): U $f The mapping function
|
||||
* @return Result<TOK, U> A transformed `Error` instance or the original `OK` instance
|
||||
*/
|
||||
public function mapError(callable $f): self
|
||||
{
|
||||
return $this->isError() ? self::Error($f($this->getError())) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function on an `OK` value (if it exists)
|
||||
*
|
||||
* @param callable(TOK): void $f The function to call
|
||||
*/
|
||||
public function iter(callable $f): void
|
||||
{
|
||||
if ($this->isOK()) {
|
||||
$f($this->getOK());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a `Result`'s `OK` value to an `Option`
|
||||
*
|
||||
* @return Option<TOK> A `Some` option with the OK value if `OK`, `None` if `Error`
|
||||
*/
|
||||
public function toOption(): Option
|
||||
{
|
||||
return $this->isOK() ? Option::Some($this->getOK()) : Option::None();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap into the `Result` for a secondary action, returning the result
|
||||
*
|
||||
* @param callable(Result<TOK, TError>): mixed $f The function to run (return value is ignored)
|
||||
* @return Result<TOK, TError> The same result provided
|
||||
*/
|
||||
public function tap(callable $f): Result
|
||||
{
|
||||
$f($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an `OK` result
|
||||
*
|
||||
@ -93,89 +171,4 @@ readonly class Result
|
||||
}
|
||||
return new self(errorValue: $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given result `OK`?
|
||||
*
|
||||
* @param Result $it The result in question
|
||||
* @return bool True if the result is `OK`, false if it is `Error`
|
||||
*/
|
||||
public static function isOK(Result $it): bool
|
||||
{
|
||||
return Option::isSome($it->okValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given result `Error`?
|
||||
*
|
||||
* @param Result $it The result in question
|
||||
* @return bool True if the result is `Error`, false if it is `OK`
|
||||
*/
|
||||
public static function isError(Result $it): bool
|
||||
{
|
||||
return Option::isSome($it->errorValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an `OK` result to another, leaving an `Error` result unmodified
|
||||
*
|
||||
* @template U The type of the mapping function
|
||||
* @param callable(TOK): U $f The mapping function
|
||||
* @param Result<TOK, TError> $it The result in question
|
||||
* @return Result<U, TError> A transformed `OK` instance, or an `Error` instance with the same value
|
||||
*/
|
||||
public static function map(callable $f, Result $it): self
|
||||
{
|
||||
return self::isOK($it) ? self::OK($f($it->getOK())) : self::Error($it->getError());
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an `Error` result to another, leaving an `OK` result unmodified
|
||||
*
|
||||
* @template U The type of the mapping function
|
||||
* @param callable(TError): U $f The mapping function
|
||||
* @param Result<TOK, TError> $it The result in question
|
||||
* @return Result<TOK, U> A transformed `Error` instance, or an `OK` instance with the same value
|
||||
*/
|
||||
public static function mapError(callable $f, Result $it): self
|
||||
{
|
||||
return self::isError($it) ? self::Error($f($it->getError())) : self::OK($it->getOK());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function on an `OK` value (if it exists)
|
||||
*
|
||||
* @param callable(TOK): void $f The function to call
|
||||
* @param Result<TOK, TError> $it The result in question
|
||||
*/
|
||||
public static function iter(callable $f, Result $it): void
|
||||
{
|
||||
if (self::isOK($it)) {
|
||||
$f($it->getOK());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a `Result`'s `OK` value to an `Option`
|
||||
*
|
||||
* @param Result<TOK, TError> $it The result in question
|
||||
* @return Option<TOK> A `Some` option with the OK value if `OK`, `None` if `Error`
|
||||
*/
|
||||
public static function toOption(Result $it): Option
|
||||
{
|
||||
return Result::isOK($it) ? Option::Some($it->getOK()) : Option::None();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap into the `Result` for a secondary action, returning the result
|
||||
*
|
||||
* @param callable(Result<TOK, TError>): mixed $f The function to run (return value is ignored)
|
||||
* @param Result<TOK, TError> $it The result against which the function should be run
|
||||
* @return Result<TOK, TError> The same result provided
|
||||
*/
|
||||
public static function tap(callable $f, Result $it): Result
|
||||
{
|
||||
$f($it);
|
||||
return $it;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class OptionTest extends TestCase
|
||||
public function testGetSucceedsForSome(): void
|
||||
{
|
||||
$it = Option::Some(9);
|
||||
$this->assertTrue(Option::isSome($it), 'The option should have been "Some"');
|
||||
$this->assertTrue($it->isSome(), 'The option should have been "Some"');
|
||||
$this->assertEquals(9, $it->get(), 'The value was incorrect');
|
||||
}
|
||||
|
||||
@ -34,10 +34,223 @@ class OptionTest extends TestCase
|
||||
Option::None()->get();
|
||||
}
|
||||
|
||||
#[TestDox('IsNone succeeds with None')]
|
||||
public function testIsNoneSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertTrue(Option::None()->isNone(), '"None" should return true');
|
||||
}
|
||||
|
||||
#[TestDox('IsNone succeeds with Some')]
|
||||
public function testIsNoneSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertFalse(Option::Some(8)->isNone(), '"Some" should return false');
|
||||
}
|
||||
|
||||
#[TestDox('IsSome succeeds with None')]
|
||||
public function testIsSomeSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertFalse(Option::None()->isSome(), '"None" should return false');
|
||||
}
|
||||
|
||||
#[TestDox('IsSome succeeds with Some')]
|
||||
public function testIsSomeSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertTrue(Option::Some('boo')->isSome(), '"Some" should return true');
|
||||
}
|
||||
|
||||
#[TestDox('GetOrDefault succeeds with None')]
|
||||
public function testGetOrDefaultSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertEquals('yes', Option::None()->getOrDefault('yes'), 'Value should have been default');
|
||||
}
|
||||
|
||||
#[TestDox('GetOrDefault succeeds with Some')]
|
||||
public function testGetOrDefaultSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertEquals('no', Option::Some('no')->getOrDefault('yes'), 'Value should have been from option');
|
||||
}
|
||||
|
||||
#[TestDox('GetOrCall succeeds with None')]
|
||||
public function testGetOrCallSucceedsWithNone(): void
|
||||
{
|
||||
$value = Option::None()->getOrCall(new class { public function __invoke(): string { return 'called'; } });
|
||||
$this->assertEquals('called', $value, 'The value should have been obtained from the callable');
|
||||
}
|
||||
|
||||
#[TestDox('GetOrCall succeeds with Some')]
|
||||
public function testGetOrCallSucceedsWithSome(): void
|
||||
{
|
||||
$value = Option::Some('passed')->getOrCall(
|
||||
new class { public function __invoke(): string { return 'called'; } });
|
||||
$this->assertEquals('passed', $value, 'The value should have been obtained from the option');
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds with None')]
|
||||
public function testMapSucceedsWithNone(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$none = Option::None();
|
||||
$mapped = $none->map(function () use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return 'hello';
|
||||
});
|
||||
$this->assertTrue($mapped->isNone(), 'The mapped option should be "None"');
|
||||
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
|
||||
$this->assertSame($none, $mapped, 'The same "None" instance should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds with Some')]
|
||||
public function testMapSucceedsWithSome(): void
|
||||
{
|
||||
$mapped = Option::Some('abc ')->map(fn($it) => str_repeat($it, 2));
|
||||
$this->assertTrue($mapped->isSome(), 'The mapped option should be "Some"');
|
||||
$this->assertEquals('abc abc ', $mapped->get(), 'The mapping function was not called correctly');
|
||||
}
|
||||
|
||||
#[TestDox('Map fails with Some when mapping is null')]
|
||||
public function testMapFailsWithSomeWhenMappingIsNull(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Option::Some('oof')->map(fn($it) => null);
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds with None')]
|
||||
public function testIterSucceedsWithNone(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Option::None()->iter(function () use ($target) { $target->called = 'uh oh'; });
|
||||
$this->assertNull($target->called, 'The function should not have been called');
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds with Some')]
|
||||
public function testIterSucceedsWithSome(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Option::Some(33)->iter(function ($it) use ($target) { $target->called = $it; });
|
||||
$this->assertEquals(33, $target->called, 'The function should have been called with the "Some" value');
|
||||
}
|
||||
|
||||
#[TestDox('Filter succeeds with None')]
|
||||
public function testFilterSucceedsWithNone(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$none = Option::None();
|
||||
$filtered = $none->filter(function () use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return true;
|
||||
});
|
||||
$this->assertTrue($filtered->isNone(), 'The filtered option should have been "None"');
|
||||
$this->assertFalse($tattle->called, 'The callable should not have been called');
|
||||
$this->assertSame($none, $filtered, 'The "None" instance returned should have been the one passed');
|
||||
}
|
||||
|
||||
#[TestDox('Filter succeeds with Some when true')]
|
||||
public function testFilterSucceedsWithSomeWhenTrue(): void
|
||||
{
|
||||
$some = Option::Some(12);
|
||||
$filtered = $some->filter(fn($it) => $it % 2 === 0);
|
||||
$this->assertTrue($filtered->isSome(), 'The filtered option should have been "Some"');
|
||||
$this->assertEquals(12, $filtered->get(), 'The filtered option value is incorrect');
|
||||
$this->assertSame($some, $filtered, 'The same "Some" instance should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('Filter succeeds with Some when false')]
|
||||
public function testFilterSucceedsWithSomeWhenFalse(): void
|
||||
{
|
||||
$some = Option::Some(23);
|
||||
$filtered = $some->filter(fn($it) => $it % 2 === 0);
|
||||
$this->assertTrue($filtered->isNone(), 'The filtered option should have been "None"');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with None')]
|
||||
public function testIsSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertFalse(Option::None()->is(null), '"None" should always return false');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when strictly equal')]
|
||||
public function testIsSucceedsWithSomeWhenStrictlyEqual(): void
|
||||
{
|
||||
$this->assertTrue(Option::Some(3)->is(3), '"Some" with strict equality should be true');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when strictly unequal')]
|
||||
public function testIsSucceedsWithSomeWhenStrictlyUnequal(): void
|
||||
{
|
||||
$this->assertFalse(Option::Some('3')->is(3), '"Some" with strict equality should be false');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when loosely equal')]
|
||||
public function testIsSucceedsWithSomeWhenLooselyEqual(): void
|
||||
{
|
||||
$this->assertTrue(Option::Some('3')->is(3, strict: false), '"Some" with loose equality should be true');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when loosely unequal')]
|
||||
public function testIsSucceedsWithSomeWhenLooselyUnequal(): void
|
||||
{
|
||||
$this->assertFalse(Option::Some('3')->is(4, strict: false), '"Some" with loose equality should be false');
|
||||
}
|
||||
|
||||
#[TestDox('Unwrap succeeds with None')]
|
||||
public function testUnwrapSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertNull(Option::None()->unwrap(), '"None" should return null');
|
||||
}
|
||||
|
||||
#[TestDox('Unwrap succeeds with Some')]
|
||||
public function testUnwrapSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertEquals('boy howdy', Option::Some('boy howdy')->unwrap(), '"Some" should return its value');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds with Some')]
|
||||
public function testTapSucceedsWithSome(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Option::Some('testing');
|
||||
$tapped = $original->tap(
|
||||
function (Option $it) use (&$value) { $value = $it->isSome() ? $it->get() : 'none'; });
|
||||
$this->assertEquals('testing', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same option should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds with None')]
|
||||
public function testTapSucceedsWithNone(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Option::None();
|
||||
$tapped = $original->tap(
|
||||
function (Option $it) use (&$value) { $value = $it->isSome() ? $it->get() : 'none'; });
|
||||
$this->assertEquals('none', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same option should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('ToPhpOption succeeds for Some')]
|
||||
public function testToPhpOptionSucceedsForSome(): void
|
||||
{
|
||||
$opt = Option::Some('php')->toPhpOption();
|
||||
$this->assertNotNull($opt, 'The PhpOption should not have been null');
|
||||
$this->assertInstanceOf('PhpOption\Some', $opt, 'The PhpOption should have been "Some"');
|
||||
$this->assertTrue($opt->isDefined(), 'There should have been a value for the PhpOption');
|
||||
$this->assertEquals('php', $opt->get(), 'The value was not correct');
|
||||
}
|
||||
|
||||
#[TestDox('ToPhpOption succeeds for None')]
|
||||
public function testToPhpOptionSucceedsForNone(): void
|
||||
{
|
||||
$opt = Option::None()->toPhpOption();
|
||||
$this->assertNotNull($opt, 'The PhpOption should not have been null');
|
||||
$this->assertInstanceOf('PhpOption\None', $opt, 'The PhpOption should have been "None"');
|
||||
$this->assertFalse($opt->isDefined(), 'There should not have been a value for the PhpOption');
|
||||
}
|
||||
|
||||
public function testSomeSucceedsWithValue(): void
|
||||
{
|
||||
$it = Option::Some('hello');
|
||||
$this->assertTrue(Option::isSome($it), 'The option should have been "Some"');
|
||||
$this->assertTrue($it->isSome(), 'The option should have been "Some"');
|
||||
}
|
||||
|
||||
public function testSomeFailsWithNull(): void
|
||||
@ -49,19 +262,19 @@ class OptionTest extends TestCase
|
||||
public function testNoneSucceeds(): void
|
||||
{
|
||||
$it = Option::None();
|
||||
$this->assertTrue(Option::isNone($it), 'The option should have been "None"');
|
||||
$this->assertTrue($it->isNone(), 'The option should have been "None"');
|
||||
}
|
||||
|
||||
public function testOfSucceedsWithNull(): void
|
||||
{
|
||||
$it = Option::of(null);
|
||||
$this->assertTrue(Option::isNone($it), '"null" should have created a "None" option');
|
||||
$this->assertTrue($it->isNone(), '"null" should have created a "None" option');
|
||||
}
|
||||
|
||||
public function testOfSucceedsWithNonNull(): void
|
||||
{
|
||||
$it = Option::of('test');
|
||||
$this->assertTrue(Option::isSome($it), 'A non-null value should have created a "Some" option');
|
||||
$this->assertTrue($it->isSome(), 'A non-null value should have created a "Some" option');
|
||||
$this->assertEquals('test', $it->get(), 'The value was not assigned correctly');
|
||||
}
|
||||
|
||||
@ -69,7 +282,7 @@ class OptionTest extends TestCase
|
||||
public function testOfSucceedsWithPhpOptionSome(): void
|
||||
{
|
||||
$it = Option::of(Some::create('something'));
|
||||
$this->assertTrue(Option::isSome($it), 'A "Some" PhpOption should have created a "Some" option');
|
||||
$this->assertTrue($it->isSome(), 'A "Some" PhpOption should have created a "Some" option');
|
||||
$this->assertEquals('something', $it->get(), 'The value was not assigned correctly');
|
||||
}
|
||||
|
||||
@ -77,202 +290,6 @@ class OptionTest extends TestCase
|
||||
public function testOfSucceedsWithPhpOptionNone(): void
|
||||
{
|
||||
$it = Option::of(None::create());
|
||||
$this->assertTrue(Option::isNone($it), 'A "None" PhpOption should have created a "None" option');
|
||||
}
|
||||
|
||||
#[TestDox('IsNone succeeds with None')]
|
||||
public function testIsNoneSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertTrue(Option::isNone(Option::None()), '"None" should return true');
|
||||
}
|
||||
|
||||
#[TestDox('IsNone succeeds with Some')]
|
||||
public function testIsNoneSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertFalse(Option::isNone(Option::Some(8)), '"Some" should return false');
|
||||
}
|
||||
|
||||
#[TestDox('IsSome succeeds with None')]
|
||||
public function testIsSomeSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertFalse(Option::isSome(Option::None()), '"None" should return false');
|
||||
}
|
||||
|
||||
#[TestDox('IsSome succeeds with Some')]
|
||||
public function testIsSomeSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertTrue(Option::isSome(Option::Some('boo')), '"Some" should return true');
|
||||
}
|
||||
|
||||
#[TestDox('DefaultValue succeeds with None')]
|
||||
public function testDefaultValueSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertEquals('yes', Option::defaultValue('yes', Option::None()), 'Value should have been default');
|
||||
}
|
||||
|
||||
#[TestDox('DefaultValue succeeds with Some')]
|
||||
public function testDefaultValueSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertEquals('no', Option::defaultValue('yes', Option::Some('no')),
|
||||
'Value should have been from option');
|
||||
}
|
||||
|
||||
#[TestDox('GetOrCall succeeds with None')]
|
||||
public function testGetOrCallSucceedsWithNone(): void
|
||||
{
|
||||
$value = Option::getOrCall(new class { public function __invoke(): string { return 'called'; } },
|
||||
Option::None());
|
||||
$this->assertEquals('called', $value, 'The value should have been obtained from the callable');
|
||||
}
|
||||
|
||||
#[TestDox('GetOrCall succeeds with Some')]
|
||||
public function testGetOrCallSucceedsWithSome(): void
|
||||
{
|
||||
$value = Option::getOrCall(new class { public function __invoke(): string { return 'called'; } },
|
||||
Option::Some('passed'));
|
||||
$this->assertEquals('passed', $value, 'The value should have been obtained from the option');
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds with None')]
|
||||
public function testMapSucceedsWithNone(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$none = Option::None();
|
||||
$mapped = Option::map(function ($ignored) use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return 'hello';
|
||||
}, $none);
|
||||
$this->assertTrue(Option::isNone($mapped), 'The mapped option should be "None"');
|
||||
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
|
||||
$this->assertNotSame($none, $mapped, 'There should have been a new "None" instance returned');
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds with Some')]
|
||||
public function testMapSucceedsWithSome(): void
|
||||
{
|
||||
$mapped = Option::map(fn($it) => str_repeat($it, 2), Option::Some('abc '));
|
||||
$this->assertTrue(Option::isSome($mapped), 'The mapped option should be "Some"');
|
||||
$this->assertEquals('abc abc ', $mapped->get(), 'The mapping function was not called correctly');
|
||||
}
|
||||
|
||||
#[TestDox('Map fails with Some when mapping is null')]
|
||||
public function testMapFailsWithSomeWhenMappingIsNull(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Option::map(fn($it) => null, Option::Some('oof'));
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds with None')]
|
||||
public function testIterSucceedsWithNone(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Option::iter(function ($ignored) use ($target) { $target->called = 'uh oh'; }, Option::None());
|
||||
$this->assertNull($target->called, 'The function should not have been called');
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds with Some')]
|
||||
public function testIterSucceedsWithSome(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Option::iter(function ($it) use ($target) { $target->called = $it; }, Option::Some(33));
|
||||
$this->assertEquals(33, $target->called, 'The function should have been called with the "Some" value');
|
||||
}
|
||||
|
||||
#[TestDox('Filter succeeds with None')]
|
||||
public function testFilterSucceedsWithNone(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$none = Option::None();
|
||||
$filtered = Option::filter(function ($ignored) use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return true;
|
||||
}, $none);
|
||||
$this->assertTrue(Option::isNone($filtered), 'The filtered option should have been "None"');
|
||||
$this->assertFalse($tattle->called, 'The callable should not have been called');
|
||||
}
|
||||
|
||||
#[TestDox('Filter succeeds with Some when true')]
|
||||
public function testFilterSucceedsWithSomeWhenTrue(): void
|
||||
{
|
||||
$some = Option::Some(12);
|
||||
$filtered = Option::filter(fn($it) => $it % 2 === 0, $some);
|
||||
$this->assertTrue(Option::isSome($filtered), 'The filtered option should have been "Some"');
|
||||
$this->assertEquals(12, $filtered->get(), 'The filtered option value is incorrect');
|
||||
$this->assertNotSame($some, $filtered, 'There should have been a new option instance returned');
|
||||
}
|
||||
|
||||
#[TestDox('Filter succeeds with Some when false')]
|
||||
public function testFilterSucceedsWithSomeWhenFalse(): void
|
||||
{
|
||||
$some = Option::Some(23);
|
||||
$filtered = Option::filter(fn($it) => $it % 2 === 0, $some);
|
||||
$this->assertTrue(Option::isNone($filtered), 'The filtered option should have been "None"');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with None')]
|
||||
public function testIsSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertFalse(Option::is(null, Option::None()), '"None" should always return false');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when strictly equal')]
|
||||
public function testIsSucceedsWithSomeWhenStrictlyEqual(): void
|
||||
{
|
||||
$this->assertTrue(Option::is(3, Option::Some(3)), '"Some" with strict equality should be true');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when strictly unequal')]
|
||||
public function testIsSucceedsWithSomeWhenStrictlyUnequal(): void
|
||||
{
|
||||
$this->assertFalse(Option::is(3, Option::Some('3')), '"Some" with strict equality should be false');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when loosely equal')]
|
||||
public function testIsSucceedsWithSomeWhenLooselyEqual(): void
|
||||
{
|
||||
$this->assertTrue(Option::is(3, Option::Some('3'), strict: false), '"Some" with loose equality should be true');
|
||||
}
|
||||
|
||||
#[TestDox('Is succeeds with Some when loosely unequal')]
|
||||
public function testIsSucceedsWithSomeWhenLooselyUnequal(): void
|
||||
{
|
||||
$this->assertFalse(Option::is(4, Option::Some('3'), strict: false),
|
||||
'"Some" with loose equality should be false');
|
||||
}
|
||||
|
||||
#[TestDox('Unwrap succeeds with None')]
|
||||
public function testUnwrapSucceedsWithNone(): void
|
||||
{
|
||||
$this->assertNull(Option::unwrap(Option::None()), '"None" should return null');
|
||||
}
|
||||
|
||||
#[TestDox('Unwrap succeeds with Some')]
|
||||
public function testUnwrapSucceedsWithSome(): void
|
||||
{
|
||||
$this->assertEquals('boy howdy', Option::unwrap(Option::Some('boy howdy')), '"Some" should return its value');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds with Some')]
|
||||
public function testTapSucceedsWithSome(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Option::Some('testing');
|
||||
$tapped = Option::tap(
|
||||
function (Option $it) use (&$value) { $value = Option::isSome($it) ? $it->get() : 'none'; }, $original);
|
||||
$this->assertEquals('testing', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same option should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds with None')]
|
||||
public function testTapSucceedsWithNone(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Option::None();
|
||||
$tapped = Option::tap(
|
||||
function (Option $it) use (&$value) { $value = Option::isSome($it) ? $it->get() : 'none'; }, $original);
|
||||
$this->assertEquals('none', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same option should have been returned');
|
||||
$this->assertTrue($it->isNone(), 'A "None" PhpOption should have created a "None" option');
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Test;
|
||||
|
||||
use BitBadger\InspiredByFSharp\Option;
|
||||
use BitBadger\InspiredByFSharp\Result;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
@ -47,11 +46,156 @@ class ResultTest extends TestCase
|
||||
Result::OK('yeah')->getError();
|
||||
}
|
||||
|
||||
#[TestDox('IsOK succeeds for OK result')]
|
||||
public function testIsOKSucceedsForOKResult(): void
|
||||
{
|
||||
$result = Result::OK('ok');
|
||||
$this->assertTrue($result->isOK(), 'The check for "OK" should have returned true');
|
||||
}
|
||||
|
||||
#[TestDox('IsOK succeeds for Error result')]
|
||||
public function testIsOKSucceedsForErrorResult(): void
|
||||
{
|
||||
$result = Result::Error('error');
|
||||
$this->assertFalse($result->isOK(), 'The check for "OK" should have returned false');
|
||||
}
|
||||
|
||||
#[TestDox('IsError succeeds for Error result')]
|
||||
public function testIsErrorSucceedsForErrorResult(): void
|
||||
{
|
||||
$result = Result::Error('not ok');
|
||||
$this->assertTrue($result->isError(), 'The check for "Error" should have returned true');
|
||||
}
|
||||
|
||||
#[TestDox('IsError succeeds for OK result')]
|
||||
public function testIsErrorSucceedsForOKResult(): void
|
||||
{
|
||||
$result = Result::OK('fine');
|
||||
$this->assertFalse($result->isError(), 'The check for "Error" should have returned false');
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds for OK result')]
|
||||
public function testMapSucceedsForOKResult(): void
|
||||
{
|
||||
$ok = Result::OK('yard');
|
||||
$mapped = $ok->map(fn($it) => strrev($it));
|
||||
$this->assertTrue($mapped->isOK(), 'The mapped result should be "OK"');
|
||||
$this->assertEquals('dray', $mapped->getOK(), 'The mapping function was not called correctly');
|
||||
}
|
||||
|
||||
#[TestDox('Map fails for OK result when mapping is null')]
|
||||
public function testMapFailsForOKResultWhenMappingIsNull(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Result::OK('not null')->map(fn($it) => null);
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds for Error result')]
|
||||
public function testMapSucceedsForErrorResult(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$error = Result::Error('nope');
|
||||
$mapped = $error->map(function () use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return 'hello';
|
||||
});
|
||||
$this->assertTrue($mapped->isError(), 'The mapped result should be "Error"');
|
||||
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
|
||||
$this->assertSame($error, $mapped, 'The same "Error" instance should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('MapError succeeds for OK result')]
|
||||
public function testMapErrorSucceedsForOKResult(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$ok = Result::OK('sure');
|
||||
$mapped = $ok->mapError(function () use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return 'hello';
|
||||
});
|
||||
$this->assertTrue($mapped->isOK(), 'The mapped result should be "OK"');
|
||||
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
|
||||
$this->assertSame($ok, $mapped, 'The same "OK" instance should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('MapError succeeds for Error result')]
|
||||
public function testMapErrorSucceedsForErrorResult(): void
|
||||
{
|
||||
$error = Result::Error('taco');
|
||||
$mapped = $error->mapError(fn($it) => str_repeat('*', strlen($it)));
|
||||
$this->assertTrue($mapped->isError(), 'The mapped result should be "Error"');
|
||||
$this->assertEquals('****', $mapped->getError(), 'The mapping function was not called correctly');
|
||||
}
|
||||
|
||||
#[TestDox('MapError fails for Error result when mapping is null')]
|
||||
public function testMapErrorFailsForErrorResultWhenMappingIsNull(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Result::Error('pizza')->mapError(fn($it) => null);
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds for OK result')]
|
||||
public function testIterSucceedsForOKResult(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Result::OK(77)->iter(function ($it) use ($target) { $target->called = $it; });
|
||||
$this->assertEquals(77, $target->called, 'The function should have been called with the "OK" value');
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds for Error result')]
|
||||
public function testIterSucceedsForErrorResult(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Result::Error('')->iter(function () use ($target) { $target->called = 'uh oh'; });
|
||||
$this->assertNull($target->called, 'The function should not have been called');
|
||||
}
|
||||
|
||||
#[TestDox('ToOption succeeds for OK result')]
|
||||
public function testToOptionSucceedsForOKResult()
|
||||
{
|
||||
$value = Result::OK(99)->toOption();
|
||||
$this->assertTrue($value->isSome(), 'An "OK" result should map to a "Some" option');
|
||||
$this->assertEquals(99, $value->get(), 'The value is not correct');
|
||||
}
|
||||
|
||||
#[TestDox('ToOption succeeds for Error result')]
|
||||
public function testToOptionSucceedsForErrorResult()
|
||||
{
|
||||
$value = Result::Error('file not found')->toOption();
|
||||
$this->assertTrue($value->isNone(), 'An "Error" result should map to a "None" option');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds for OK result')]
|
||||
public function testTapSucceedsForOKResult(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Result::OK('working');
|
||||
$tapped = $original->tap(function (Result $it) use (&$value) {
|
||||
$value = $it->isOK() ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
|
||||
});
|
||||
$this->assertEquals('OK: working', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same result should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds for Error result')]
|
||||
public function testTapSucceedsForErrorResult(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Result::Error('failed');
|
||||
$tapped = $original->tap(function (Result $it) use (&$value) {
|
||||
$value = $it->isOK() ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
|
||||
});
|
||||
$this->assertEquals('Error: failed', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same result should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('OK succeeds for non null result')]
|
||||
public function testOKSucceedsForNonNullResult(): void
|
||||
{
|
||||
$result = Result::OK('something');
|
||||
$this->assertTrue(Result::isOK($result), 'The result should have been "OK"');
|
||||
$this->assertTrue($result->isOK(), 'The result should have been "OK"');
|
||||
$this->assertEquals('something', $result->getOK(), 'The "OK" value was incorrect');
|
||||
}
|
||||
|
||||
@ -66,7 +210,7 @@ class ResultTest extends TestCase
|
||||
public function testErrorSucceedsForNonNullResult(): void
|
||||
{
|
||||
$result = Result::Error('sad trombone');
|
||||
$this->assertTrue(Result::isError($result), 'The result should have been "Error"');
|
||||
$this->assertTrue($result->isError(), 'The result should have been "Error"');
|
||||
$this->assertEquals('sad trombone', $result->getError(), 'The "Error" value was incorrect');
|
||||
}
|
||||
|
||||
@ -76,149 +220,4 @@ class ResultTest extends TestCase
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Result::Error(null);
|
||||
}
|
||||
|
||||
#[TestDox('IsOK succeeds for OK result')]
|
||||
public function testIsOKSucceedsForOKResult(): void
|
||||
{
|
||||
$result = Result::OK('ok');
|
||||
$this->assertTrue(Result::isOK($result), 'The check for "OK" should have returned true');
|
||||
}
|
||||
|
||||
#[TestDox('IsOK succeeds for Error result')]
|
||||
public function testIsOKSucceedsForErrorResult(): void
|
||||
{
|
||||
$result = Result::Error('error');
|
||||
$this->assertFalse(Result::isOK($result), 'The check for "OK" should have returned false');
|
||||
}
|
||||
|
||||
#[TestDox('IsError succeeds for Error result')]
|
||||
public function testIsErrorSucceedsForErrorResult(): void
|
||||
{
|
||||
$result = Result::Error('not ok');
|
||||
$this->assertTrue(Result::isError($result), 'The check for "Error" should have returned true');
|
||||
}
|
||||
|
||||
#[TestDox('IsError succeeds for OK result')]
|
||||
public function testIsErrorSucceedsForOKResult(): void
|
||||
{
|
||||
$result = Result::OK('fine');
|
||||
$this->assertFalse(Result::isError($result), 'The check for "Error" should have returned false');
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds for OK result')]
|
||||
public function testMapSucceedsForOKResult(): void
|
||||
{
|
||||
$ok = Result::OK('yard');
|
||||
$mapped = Result::map(fn($it) => strrev($it), $ok);
|
||||
$this->assertTrue(Result::isOK($mapped), 'The mapped result should be "OK"');
|
||||
$this->assertEquals('dray', $mapped->getOK(), 'The mapping function was not called correctly');
|
||||
}
|
||||
|
||||
#[TestDox('Map fails for OK result when mapping is null')]
|
||||
public function testMapFailsForOKResultWhenMappingIsNull(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Result::map(fn($it) => null, Result::OK('not null'));
|
||||
}
|
||||
|
||||
#[TestDox('Map succeeds for Error result')]
|
||||
public function testMapSucceedsForErrorResult(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$error = Result::Error('nope');
|
||||
$mapped = Result::map(function ($ignored) use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return 'hello';
|
||||
}, $error);
|
||||
$this->assertTrue(Result::isError($mapped), 'The mapped result should be "Error"');
|
||||
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
|
||||
$this->assertNotSame($error, $mapped, 'There should have been a new "Error" instance returned');
|
||||
}
|
||||
|
||||
#[TestDox('MapError succeeds for OK result')]
|
||||
public function testMapErrorSucceedsForOKResult(): void
|
||||
{
|
||||
$tattle = new class { public bool $called = false; };
|
||||
$ok = Result::OK('sure');
|
||||
$mapped = Result::mapError(function ($ignored) use ($tattle)
|
||||
{
|
||||
$tattle->called = true;
|
||||
return 'hello';
|
||||
}, $ok);
|
||||
$this->assertTrue(Result::isOK($mapped), 'The mapped result should be "OK"');
|
||||
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
|
||||
$this->assertNotSame($ok, $mapped, 'There should have been a new "OK" instance returned');
|
||||
}
|
||||
|
||||
#[TestDox('MapError succeeds for Error result')]
|
||||
public function testMapErrorSucceedsForErrorResult(): void
|
||||
{
|
||||
$error = Result::Error('taco');
|
||||
$mapped = Result::mapError(fn($it) => str_repeat('*', strlen($it)), $error);
|
||||
$this->assertTrue(Result::isError($mapped), 'The mapped result should be "Error"');
|
||||
$this->assertEquals('****', $mapped->getError(), 'The mapping function was not called correctly');
|
||||
}
|
||||
|
||||
#[TestDox('MapError fails for Error result when mapping is null')]
|
||||
public function testMapErrorFailsForErrorResultWhenMappingIsNull(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Result::mapError(fn($it) => null, Result::Error('pizza'));
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds for OK result')]
|
||||
public function testIterSucceedsForOKResult(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Result::iter(function ($it) use ($target) { $target->called = $it; }, Result::OK(77));
|
||||
$this->assertEquals(77, $target->called, 'The function should have been called with the "OK" value');
|
||||
}
|
||||
|
||||
#[TestDox('Iter succeeds for Error result')]
|
||||
public function testIterSucceedsForErrorResult(): void
|
||||
{
|
||||
$target = new class { public mixed $called = null; };
|
||||
Result::iter(function ($ignored) use ($target) { $target->called = 'uh oh'; }, Result::Error(''));
|
||||
$this->assertNull($target->called, 'The function should not have been called');
|
||||
}
|
||||
|
||||
#[TestDox('ToOption succeeds for OK result')]
|
||||
public function testToOptionSucceedsForOKResult()
|
||||
{
|
||||
$value = Result::toOption(Result::OK(99));
|
||||
$this->assertTrue(Option::isSome($value), 'An "OK" result should map to a "Some" option');
|
||||
$this->assertEquals(99, $value->get(), 'The value is not correct');
|
||||
}
|
||||
|
||||
#[TestDox('ToOption succeeds for Error result')]
|
||||
public function testToOptionSucceedsForErrorResult()
|
||||
{
|
||||
$value = Result::toOption(Result::Error('file not found'));
|
||||
$this->assertTrue(Option::isNone($value), 'An "Error" result should map to a "None" option');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds for OK result')]
|
||||
public function testTapSucceedsForOKResult(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Result::OK('working');
|
||||
$tapped = Result::tap(function (Result $it) use (&$value) {
|
||||
$value = Result::isOK($it) ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
|
||||
}, $original);
|
||||
$this->assertEquals('OK: working', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same result should have been returned');
|
||||
}
|
||||
|
||||
#[TestDox('Tap succeeds for Error result')]
|
||||
public function testTapSucceedsForErrorResult(): void
|
||||
{
|
||||
$value = '';
|
||||
$original = Result::Error('failed');
|
||||
$tapped = Result::tap(function (Result $it) use (&$value) {
|
||||
$value = Result::isOK($it) ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
|
||||
}, $original);
|
||||
$this->assertEquals('Error: failed', $value, 'The tapped function was not called');
|
||||
$this->assertSame($original, $tapped, 'The same result should have been returned');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user