diff --git a/src/result.test.ts b/src/result.test.ts index fbb67fe..43a80b5 100644 --- a/src/result.test.ts +++ b/src/result.test.ts @@ -1,4 +1,4 @@ -import { expect, test, describe, beforeEach } from 'vitest'; +import { expect, test, describe, expectTypeOf } from 'vitest'; import { Result } from './result'; describe('Result', () => { @@ -43,4 +43,38 @@ describe('Result', () => { expect(result.getError()).toBe(error); }); }); + + describe('Type narrowing', () => { + const createResult = (valid: boolean): Result => { + return valid ? Result.ok(10) : Result.error(new Error('message')); + }; + + test('isOk() narrows both branches', () => { + for (const valid of [true, false]) { + const result = createResult(valid); + + if (result.isOk()) { + expectTypeOf(result.getValue()).toEqualTypeOf(); + expect(result.getValue()).toBe(10); + } else { + expectTypeOf(result.getError()).toEqualTypeOf(); + expect(result.getError()).toBeInstanceOf(Error); + } + } + }); + + test('isError() narrows both branches', () => { + for (const valid of [true, false]) { + const result = createResult(valid); + + if (result.isError()) { + expectTypeOf(result.getError()).toEqualTypeOf(); + expect(result.getError()).toBeInstanceOf(Error); + } else { + expectTypeOf(result.getValue()).toEqualTypeOf(); + expect(result.getValue()).toBe(10); + } + } + }); + }); }); diff --git a/src/result.ts b/src/result.ts index 9f21b92..6b28c68 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,4 +1,16 @@ -export class Result { +interface Ok extends ResultImpl { + getValue(): T; + getError(): undefined; +} + +interface Err extends ResultImpl { + getValue(): undefined; + getError(): Error; +} + +export type Result = Ok | Err; + +class ResultImpl { private readonly ok: boolean; private readonly value?: T | undefined; private readonly error?: Error | undefined; @@ -10,18 +22,18 @@ export class Result { } static ok(value?: T): Result { - return new Result(true, value); + return new ResultImpl(true, value) as Result; } static error(error: Error): Result { - return new Result(false, undefined, error); + return new ResultImpl(false, undefined, error) as Result; } - isOk(): boolean { + isOk(): this is Ok { return this.ok; } - isError(): boolean { + isError(): this is Err { return !this.ok; } @@ -33,3 +45,5 @@ export class Result { return this.error; } } + +export const Result = ResultImpl;