diff --git a/src/Either.php b/src/Either.php index ec394a5..30e0aca 100644 --- a/src/Either.php +++ b/src/Either.php @@ -15,9 +15,21 @@ public function __construct( public mixed $result, public ?Throwable $error = null ) { + if ($result instanceof Throwable) { + throw new InvalidArgumentException('Result cannot be a Throwable.'); + } + if ($result === null && $error === null) { + throw new InvalidArgumentException('Either result or error must be set.'); + } + parent::__construct(new ArrayIterator([$error, $result])); } + public static function create(mixed $result, ?Throwable $error = null): self + { + return new self($result, $error); + } + public function getIterator(): Traversable { return new ArrayIterator([$this->error, $this->result]); diff --git a/tests/Unit/EitherTest.php b/tests/Unit/EitherTest.php new file mode 100644 index 0000000..4ad73f4 --- /dev/null +++ b/tests/Unit/EitherTest.php @@ -0,0 +1,96 @@ +toBeTrue(); +}); + +test('should extend the IteratorIterator class', function () { + $either = new Either('foo'); + + expect($either)->toBeInstanceOf(IteratorIterator::class); +}); + +test('should implement the ArrayAccess interface', function () { + $either = new Either('foo'); + + expect($either)->toBeInstanceOf(ArrayAccess::class); +}); + +test('should have a result property', function () { + $either = new Either('foo'); + + expect($either->result)->toBe('foo'); +}); + +test('should have an error property', function () { + $either = new Either(null, new Exception('foo')); + + expect($either->error)->toBeInstanceOf(Exception::class); +}); + +test('should throw an error if result is an exception', function () { + expect(fn () => new Either(new Exception('foo')))->toThrow(InvalidArgumentException::class, 'Result cannot be a Throwable.'); +}); + +test('should throw an error if both result and error are null', function () { + expect(fn () => new Either(null, null))->toThrow(InvalidArgumentException::class, 'Either result or error must be set.'); +}); + +test('should have the correct methods', function () { + expect(method_exists(Either::class, 'getIterator'))->toBeTrue() + ->and(method_exists(Either::class, 'offsetSet'))->toBeTrue() + ->and(method_exists(Either::class, 'offsetExists'))->toBeTrue() + ->and(method_exists(Either::class, 'offsetUnset'))->toBeTrue() + ->and(method_exists(Either::class, 'offsetGet'))->toBeTrue() + ->and(method_exists(Either::class, 'create'))->toBeTrue(); +}); + +test('can be instantiated with new', function () { + $either = new Either('foo'); + + expect($either)->toBeInstanceOf(Either::class) + ->and($either->result)->toBe('foo') + ->and($either->error)->toBeNull(); + + [$error, $result] = $either; + + expect($error)->toBeNull() + ->and($result)->toBe('foo'); + + $either = new Either(null, new Exception('foo')); + + expect($either)->toBeInstanceOf(Either::class) + ->and($either->result)->toBeNull() + ->and($either->error)->toBeInstanceOf(Exception::class); + + [$error, $result] = $either; + + expect($error)->toBeInstanceOf(Exception::class) + ->and($result)->toBeNull(); +}); + +test('can be instantiated with from', function () { + $either = Either::create('foo'); + + expect($either)->toBeInstanceOf(Either::class) + ->and($either->result)->toBe('foo') + ->and($either->error)->toBeNull(); + + [$error, $result] = $either; + + expect($error)->toBeNull() + ->and($result)->toBe('foo'); + + $either = Either::create(null, new Exception('foo')); + + expect($either)->toBeInstanceOf(Either::class) + ->and($either->result)->toBeNull() + ->and($either->error)->toBeInstanceOf(Exception::class); + + [$error, $result] = $either; + + expect($error)->toBeInstanceOf(Exception::class) + ->and($result)->toBeNull(); +}); diff --git a/tests/Unit/FailTest.php b/tests/Unit/FailTest.php new file mode 100644 index 0000000..a76486b --- /dev/null +++ b/tests/Unit/FailTest.php @@ -0,0 +1,44 @@ +toBeTrue() + ->and(method_exists(Fail::class, 'from'))->toBeTrue(); +}); + +test('creates a Fail instance', function () { + $might = Fail::from(new Exception('foo')); + + expect($might)->toBeInstanceOf(Fail::class); +}); + +test('extends from the Either class', function () { + $might = Fail::from(new Exception('foo')); + + expect($might)->toBeInstanceOf(Either::class) + ->and($might)->toBeInstanceOf(Fail::class) + ->and($might)->not->toBeInstanceOf(Might::class); +}); + +test('has Either properties', function () { + $might = Fail::from(new Exception('foo')); + + expect($might->result)->toBeNull() + ->and($might->error)->toBeInstanceOf(Exception::class); + + [$error, $result] = $might; + + expect($error)->toBeInstanceOf(Exception::class) + ->and($result)->toBeNull(); +}); + +test('can instantiate with new', function () { + $might = new Fail(new Exception('foo')); + + expect($might)->toBeInstanceOf(Fail::class) + ->and($might->result)->toBeNull() + ->and($might->error)->toBeInstanceOf(Exception::class); +}); diff --git a/tests/Unit/MightTest.php b/tests/Unit/MightTest.php new file mode 100644 index 0000000..1c5a43b --- /dev/null +++ b/tests/Unit/MightTest.php @@ -0,0 +1,44 @@ +toBeTrue() + ->and(method_exists(Might::class, 'from'))->toBeTrue(); +}); + +test('creates a Might instance', function () { + $might = Might::from('foo'); + + expect($might)->toBeInstanceOf(Might::class); +}); + +test('extends from the Either class', function () { + $might = Might::from('foo'); + + expect($might)->toBeInstanceOf(Either::class) + ->and($might)->toBeInstanceOf(Might::class) + ->and($might)->not->toBeInstanceOf(Fail::class); +}); + +test('has Either properties', function () { + $might = Might::from('foo'); + + expect($might->result)->toBe('foo') + ->and($might->error)->toBeNull(); + + [$error, $result] = $might; + + expect($error)->toBeNull() + ->and($result)->toBe('foo'); +}); + +test('can instantiate with new', function () { + $might = new Might('foo'); + + expect($might)->toBeInstanceOf(Might::class) + ->and($might->result)->toBe('foo') + ->and($might->error)->toBeNull(); +});