Skip to content

Commit

Permalink
feat: 🎸 make Result more user friendly
Browse files Browse the repository at this point in the history
Result is nicer to use when we stick to the language of succcess and
error, so this commit provides that

BREAKING CHANGE: 🧨 y

✅ Closes: 195
  • Loading branch information
dancrumb committed May 4, 2024
1 parent 49286bc commit 4639122
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 19 deletions.
13 changes: 13 additions & 0 deletions src/Either.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,17 @@ describe('Either', () => {
expect(e.isLeft()).toBe(false);
});
});
describe('.proceedLeft', () => {
test('handles regular functions', () => {
const e = Either.left('test');
expect(e.proceedLeft((x) => Either.left(x + 'ing')).getLeft()).toBe('testing');
});
test('handles async functions', async () => {
const e = Either.left('test');
const proceeded = await e.proceedLeft((x) =>
Promise.resolve(Either.left(x + 'ing'))
);
expect(proceeded.getLeft()).toBe('testing');
});
});
});
28 changes: 10 additions & 18 deletions src/Either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ export class Either<L, R> {
*
* @param lFunc
*/
public proceedLeft<T>(lFunc: (val: L) => Either<T, R>): Either<T, R> {
public proceedLeft<T>(lFunc: (val: L) => Promise<Either<T, R>>): Promise<Either<T, R>>;
public proceedLeft<T>(lFunc: (val: L) => Either<T, R>): Either<T, R>;
public proceedLeft<T>(
lFunc: ((val: L) => Either<T, R>) | ((val: L) => Promise<Either<T, R>>)
): Either<T, R> | Promise<Either<T, R>> {
if (this.left.isPresent()) {
return lFunc(this.left.get());
}
Expand All @@ -144,23 +148,11 @@ export class Either<L, R> {
*
* @param rFunc
*/
public proceedRight<T>(rFunc: (val: R) => Either<L, T>): Either<L, T> {
if (this.right.isPresent()) {
return rFunc(this.right.get());
}

return Either.left<L, T>(this.left.get());
}

/**
* This provides an implementation of {@link proceedRightAsync} that can handle a mapping function
* that returns a `Promise<Either>`.
*
* @param rFunc
*/
public async proceedRightAsync<T>(
rFunc: (val: R) => Promise<Either<L, T>>
): Promise<Either<L, T>> {
public proceedRight<T>(rFunc: (val: R) => Promise<Either<L, T>>): Promise<Either<L, T>>;
public proceedRight<T>(rFunc: (val: R) => Either<L, T>): Either<L, T>;
public proceedRight<T>(
rFunc: ((val: R) => Either<L, T>) | ((val: R) => Promise<Either<L, T>>)
): Either<L, T> | Promise<Either<L, T>> {
if (this.right.isPresent()) {
return rFunc(this.right.get());
}
Expand Down
65 changes: 65 additions & 0 deletions src/Result.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {expect, describe, test} from 'vitest';

import {Result} from '../src/Result';
import {Optional} from '../src/Optional';

describe('Result', () => {
test('maps Optionals correctly (right)', async () => {
const e = Result.error(new Error('Test'));
e.mapRight((d) => Optional.of(d)).apply(
() => {
throw new Error('Wrong function called');
},
(f) => {
expect(f).toBeInstanceOf(Error);
}
);
});
test('maps Optionals correctly (left)', async () => {
const e = Result.success(1);
e.mapLeft((d) => Optional.of(d)).apply(
(f) => {
expect(f).toBe(1);
},
() => {
throw new Error('Wrong function called');
}
);
});
test('maps Optionals correctly (both)', async () => {
expect(
Result.success(1).map(
(d) => Optional.of((d as number) * 2),
(d) => Optional.of(0)
)
).toBe(2);
expect(
Result.error(new Error('Test')).map(
(d) => Optional.of((d as number) * 2),
(d) => Optional.of(0)
)
).toBe(0);
});

describe('.isError', () => {
test('returns true for right values', () => {
const e = Result.error(new Error('Test'));
expect(e.isError()).toBe(true);
});
test('returns false for left values', () => {
const e = Result.success('test');
expect(e.isError()).toBe(false);
});
});

describe('.isSuccess', () => {
test('returns true for left values', () => {
const e = Result.success('test');
expect(e.isSuccess()).toBe(true);
});
test('returns false for right values', () => {
const e = Result.error(new Error('Test'));
expect(e.isSuccess()).toBe(false);
});
});
});
44 changes: 43 additions & 1 deletion src/Result.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
import {Either} from './Either';
import {Optional} from './Optional';

/**
* A Result is a value that represents either success or failure. It's a special case of an Either.
*/
export class Result<T, E extends Error = Error> extends Either<E, T> {}
export class Result<T, E extends Error = Error> extends Either<T, E> {
private static fromEither<ET, EE extends Error = Error>(either: Either<ET, EE>) {
const left: Optional<ET> = Optional.of(either.isLeft() ? either.getLeft() : null);
const right: Optional<EE> = Optional.of(either.isRight() ? either.getRight() : null);

return new Result<ET, EE>(left, right);
}

public static success<NT, NE extends Error = Error>(
value: NT | Optional<NT>
): Result<NT, NE> {
return Result.fromEither(Either.left<NT, NE>(value));
}

public static error<NT, NE extends Error = Error>(value: NE): Result<NT, NE> {
return Result.fromEither(Either.right<NT, NE>(value));
}

public isSuccess() {
return this.isLeft();
}

public isError() {
return this.isRight();
}

public mapSuccess(...args: Parameters<Either<T, E>['mapLeft']>) {
return this.mapLeft(...args);
}

public proceedWithSuccess(...args: Parameters<Either<T, E>['proceedLeft']>) {
return this.proceedLeft(...args);
}

public mapError(...args: Parameters<Either<T, E>['mapRight']>) {
return this.mapRight(...args);
}

public proceedsWithError(...args: Parameters<Either<T, E>['proceedRight']>) {
return this.proceedRight(...args);
}
}

0 comments on commit 4639122

Please sign in to comment.