Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Remove ambiguity when Ok and Err value types are compatible. #70

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ interface BaseResult<T, E> extends Iterable<T extends Iterable<infer U> ? U : ne
* Returns the contained `Ok` value, if does not exist. Throws an error if it does.
* @param msg the message to throw if Ok value.
*/
expectErr(msg: string): T;
expectErr(msg: string): E;

/**
* Returns the contained `Ok` value.
* Because this function may throw, its use is generally discouraged.
Expand Down Expand Up @@ -97,7 +97,7 @@ export class ErrImpl<E> implements BaseResult<never, E> {

readonly ok!: false;
readonly err!: true;
readonly val!: E;
readonly errVal!: E;

private readonly _stack!: string;

Expand All @@ -116,10 +116,10 @@ export class ErrImpl<E> implements BaseResult<never, E> {

this.ok = false;
this.err = true;
this.val = val;
this.errVal = val;

const stackLines = new Error().stack!.split('\n').slice(2);
if (stackLines && stackLines.length > 0 && stackLines[0].includes('ErrImpl')) {
if (stackLines && stackLines.length > 0 && stackLines[0]!.includes('ErrImpl')) {
stackLines.shift();
}

Expand All @@ -139,15 +139,15 @@ export class ErrImpl<E> implements BaseResult<never, E> {
}

expect(msg: string): never {
throw new Error(`${msg} - Error: ${toString(this.val)}\n${this._stack}`);
throw new Error(`${msg} - Error: ${toString(this.errVal)}\n${this._stack}`);
}

expectErr(_msg: string): E {
return this.val
return this.errVal
}

unwrap(): never {
throw new Error(`Tried to unwrap Error: ${toString(this.val)}\n${this._stack}`);
throw new Error(`Tried to unwrap Error: ${toString(this.errVal)}\n${this._stack}`);
}

map(_mapper: unknown): Err<E> {
Expand All @@ -159,15 +159,15 @@ export class ErrImpl<E> implements BaseResult<never, E> {
}

mapErr<E2>(mapper: (err: E) => E2): Err<E2> {
return new Err(mapper(this.val));
return new Err(mapper(this.errVal));
}

toOption(): Option<never> {
return None;
}

toString(): string {
return `Err(${toString(this.val)})`;
return `Err(${toString(this.errVal)})`;
}

get stack(): string | undefined {
Expand All @@ -187,13 +187,13 @@ export class OkImpl<T> implements BaseResult<T, never> {

readonly ok!: true;
readonly err!: false;
readonly val!: T;
readonly okVal!: T;

/**
* Helper function if you know you have an Ok<T> and T is iterable
*/
[Symbol.iterator](): Iterator<T extends Iterable<infer U> ? U : never> {
const obj = Object(this.val) as Iterable<any>;
const obj = Object(this.okVal) as Iterable<any>;

return Symbol.iterator in obj
? obj[Symbol.iterator]()
Expand All @@ -211,50 +211,50 @@ export class OkImpl<T> implements BaseResult<T, never> {

this.ok = true;
this.err = false;
this.val = val;
this.okVal = val;
}

/**
* @see unwrapOr
* @deprecated in favor of unwrapOr
*/
else(_val: unknown): T {
return this.val;
return this.okVal;
}

unwrapOr(_val: unknown): T {
return this.val;
return this.okVal;
}

expect(_msg: string): T {
return this.val;
return this.okVal;
}

expectErr(msg: string): never {
throw new Error(msg);
}

unwrap(): T {
return this.val;
return this.okVal;
}

map<T2>(mapper: (val: T) => T2): Ok<T2> {
return new Ok(mapper(this.val));
return new Ok(mapper(this.okVal));
}

andThen<T2>(mapper: (val: T) => Ok<T2>): Ok<T2>;
andThen<E2>(mapper: (val: T) => Err<E2>): Result<T, E2>;
andThen<T2, E2>(mapper: (val: T) => Result<T2, E2>): Result<T2, E2>;
andThen<T2, E2>(mapper: (val: T) => Result<T2, E2>): Result<T2, E2> {
return mapper(this.val);
return mapper(this.okVal);
}

mapErr(_mapper: unknown): Ok<T> {
return this;
}

toOption(): Option<T> {
return Some(this.val);
return Some(this.okVal);
}

/**
Expand All @@ -267,11 +267,11 @@ export class OkImpl<T> implements BaseResult<T, never> {
* (this is the `into_ok()` in rust)
*/
safeUnwrap(): T {
return this.val;
return this.okVal;
}

toString(): string {
return `Ok(${toString(this.val)})`;
return `Ok(${toString(this.okVal)})`;
}
}

Expand Down Expand Up @@ -302,7 +302,7 @@ export namespace Result {
const okResult = [];
for (let result of results) {
if (result.ok) {
okResult.push(result.val);
okResult.push(result.okVal);
} else {
return result as Err<ResultErrTypes<T>[number]>;
}
Expand All @@ -325,7 +325,7 @@ export namespace Result {
if (result.ok) {
return result as Ok<ResultOkTypes<T>[number]>;
} else {
errResult.push(result.val);
errResult.push(result.errVal);
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/rxjs-operators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export function elseMap<T, E, E2>(mapper: (val: E) => E2): OperatorFunction<Resu
return source.pipe(
map((result) => {
if (result.err) {
return mapper(result.val);
return mapper(result.errVal);
} else {
return result.val;
return result.okVal;
}
}),
);
Expand All @@ -47,7 +47,7 @@ export function elseMapTo<T, E, E2>(value: E2): OperatorFunction<Result<T, E>, T
if (result.err) {
return value;
} else {
return result.val;
return result.okVal;
}
}),
);
Expand All @@ -67,7 +67,7 @@ export function resultSwitchMap<T, E, T2, E2>(
return source.pipe(
switchMap((result) => {
if (result.ok) {
return mapper(result.val);
return mapper(result.okVal);
} else {
return of(result);
}
Expand Down Expand Up @@ -96,7 +96,7 @@ export function resultMergeMap<T, E, T2, E2>(
return source.pipe(
mergeMap((result) => {
if (result.ok) {
return mapper(result.val);
return mapper(result.okVal);
} else {
return of(result);
}
Expand All @@ -116,7 +116,7 @@ export function filterResultOk<T, E>(): OperatorFunction<Result<T, E>, T> {
return (source) => {
return source.pipe(
filter((result): result is Ok<T> => result.ok),
map((result) => result.val),
map((result) => result.okVal),
);
};
}
Expand All @@ -125,7 +125,7 @@ export function filterResultErr<T, E>(): OperatorFunction<Result<T, E>, E> {
return (source) => {
return source.pipe(
filter((result): result is Err<E> => result.err),
map((result) => result.val),
map((result) => result.errVal),
);
};
}
Expand All @@ -135,7 +135,7 @@ export function tapResultErr<T, E>(tapFn: (err: E) => void): MonoTypeOperatorFun
return source.pipe(
tap((r) => {
if (!r.ok) {
tapFn(r.val);
tapFn(r.errVal);
}
}),
);
Expand All @@ -147,7 +147,7 @@ export function tapResultOk<T, E>(tapFn: (val: T) => void): MonoTypeOperatorFunc
return source.pipe(
tap((r) => {
if (r.ok) {
tapFn(r.val);
tapFn(r.okVal);
}
}),
);
Expand Down
6 changes: 3 additions & 3 deletions test/err.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ test('ok, err, and val', () => {
expect(err.ok).toBe(false);
assert<typeof err.ok>(false);

expect(err.val).toBe(32);
eq<typeof err.val, number>(true);
expect(err.errVal).toBe(32);
eq<typeof err.errVal, number>(true);
});

test('static EMPTY', () => {
expect(Err.EMPTY).toBeInstanceOf(Err);
expect(Err.EMPTY.val).toBe(undefined);
expect(Err.EMPTY.errVal).toBe(undefined);
eq<typeof Err.EMPTY, Err<void>>(true);
});

Expand Down
6 changes: 3 additions & 3 deletions test/ok.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ test('ok, err, and val', () => {
expect(err.ok).toBe(true);
assert<typeof err.ok>(true);

expect(err.val).toBe(32);
eq<typeof err.val, number>(true);
expect(err.okVal).toBe(32);
eq<typeof err.okVal, number>(true);
});

test('static EMPTY', () => {
expect(Ok.EMPTY).toBeInstanceOf(Ok);
expect(Ok.EMPTY.val).toBe(undefined);
expect(Ok.EMPTY.okVal).toBe(undefined);
eq<typeof Ok.EMPTY, Ok<void>>(true);
});

Expand Down
5 changes: 2 additions & 3 deletions test/result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ test('Result.all', () => {
eq<typeof all1, Result<[number, boolean], never>>(true);

const all3 = Result.all(err0, err1);
expect(all3).toMatchResult(Err(err0.val));
expect(all3).toMatchResult(Err(err0.errVal));
eq<typeof all3, Result<[never, never], symbol | Error>>(true);

const all4 = Result.all(...([] as Result<string, number>[]));
Expand Down Expand Up @@ -143,7 +143,7 @@ test('Result.any', () => {
eq<typeof any1, Result<number | boolean, [never, never]>>(true);

const any3 = Result.any(err0, err1);
expect(any3).toMatchResult(Err([err0.val, err1.val]));
expect(any3).toMatchResult(Err([err0.errVal, err1.errVal]));
eq<typeof any3, Result<never, [symbol, Error]>>(true);

const any4 = Result.any(...([] as Result<string, number>[]));
Expand Down Expand Up @@ -189,7 +189,6 @@ test('Result.wrapAsync', async () => {

const c = await Result.wrapAsync<number, string>(() => {
throw 'thrown before promise';
return Promise.resolve(3);
});

expect(c).toMatchResult(Err('thrown before promise'));
Expand Down
36 changes: 26 additions & 10 deletions test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,25 @@ expect.extend({
let pass = true;
try {
expect(received.ok).toBe(result.ok);

if (received.val !== result.val) {
expect(received.val).toMatchObject(result.val);
if (received.ok && result.ok) {
if (received.okVal !== result.okVal) {
expect(received.okVal).toMatchObject(result.okVal);
}
} else if (received.err && result.err) {
if (received.errVal !== result.errVal) {
expect(received.errVal).toMatchObject(result.errVal);
}
} else {
pass = false;
}
} catch (e) {
pass = false;
}

const type = received.ok ? 'Ok' : 'Err';
const expectedType = received.ok ? 'Ok' : 'Err';
const val = JSON.stringify(received.val);
const expectedVal = JSON.stringify(result.val);
const val = received.ok ? JSON.stringify(received.okVal) : JSON.stringify(received.errVal);
const expectedVal = result.ok ? JSON.stringify(result.okVal): JSON.stringify(result.errVal);

return {
message: () => `expected ${type}(${val}) ${pass ? '' : 'not '}to equal ${expectedType}(${expectedVal})`,
Expand All @@ -51,18 +58,27 @@ expect.extend({
obs.subscribe((val) => (received = val)).unsubscribe();

expect(received?.ok).toBe(result.ok);

if (received?.val !== result.val) {
expect(received?.val).toMatchObject(result.val);
if (received && received.ok && result.ok) {
if (received.okVal !== result.okVal) {
expect(received.okVal).toMatchObject(result.okVal);
}
} else if (received && received.err && result.err) {
if (received.errVal !== result.errVal) {
expect(received.errVal).toMatchObject(result.errVal);
}
} else {
pass = false;
}


} catch (e) {
pass = false;
}

const type = received?.ok ? 'Ok' : 'Err';
const expectedType = received?.ok ? 'Ok' : 'Err';
const val = JSON.stringify(received?.val);
const expectedVal = JSON.stringify(result.val);
const val = received?.ok ? JSON.stringify(received.okVal) : JSON.stringify(received?.errVal);
const expectedVal = result.ok ? JSON.stringify(result.okVal): JSON.stringify(result.errVal);

return {
message: () => `expected ${type}(${val}) ${pass ? '' : 'not '}to equal ${expectedType}(${expectedVal})`,
Expand Down