Skip to content

Commit

Permalink
#develop custom result implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
bennobuilder committed Aug 18, 2024
1 parent 4e3d11a commit 8138857
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 15 deletions.
6 changes: 6 additions & 0 deletions .changeset/chilly-walls-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'feature-fetch': patch
'@blgc/utils': patch
---

Custom and very barebone Result implementation to not bloat bundle
3 changes: 1 addition & 2 deletions packages/feature-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
"homepage": "https://builder.group/?source=package-json",
"dependencies": {
"@blgc/types": "workspace:*",
"@blgc/utils": "workspace:*",
"ts-results-es": "4.2.0"
"@blgc/utils": "workspace:*"
},
"devDependencies": {
"@blgc/config": "workspace:*",
Expand Down
3 changes: 2 additions & 1 deletion packages/feature-fetch/src/create-fetch-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
import { unwrapErr } from '@blgc/utils';

import { createFetchClient } from './create-fetch-client';

Expand Down Expand Up @@ -54,6 +55,6 @@ describe('createFetchClient function', () => {
const result = await client._baseFetch('/test', 'GET', {});

expect(result.isErr()).toBe(true);
expect(result.unwrapErr()).toBeInstanceOf(Error);
expect(unwrapErr(result)).toBeInstanceOf(Error);
});
});
2 changes: 1 addition & 1 deletion packages/feature-fetch/src/create-fetch-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Ok } from 'ts-results-es';
import { Err, Ok } from '@blgc/utils';

import { FetchError } from './exceptions';
import {
Expand Down
10 changes: 9 additions & 1 deletion packages/feature-fetch/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ export * from './features';
export * from './helper';
export * from './types';

export * from 'ts-results-es';
export {
Err,
Ok,
unwrapErr,
unwrapOk,
type TErrResult,
type TOkResult,
type TResult
} from '@blgc/utils';
4 changes: 2 additions & 2 deletions packages/feature-fetch/src/types/fetch-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Result } from 'ts-results-es';
import { type TResult } from '@blgc/utils';

import type { FetchError, NetworkError, RequestError } from '../exceptions';
import type { FetchHeaders } from '../helper';
Expand Down Expand Up @@ -124,7 +124,7 @@ export type TFetchResponse<
GSuccessResponseBody,
GErrorResponseBody,
GParseAs extends TParseAs
> = Result<
> = TResult<
TFetchResponseSuccess<GSuccessResponseBody, GParseAs>,
TFetchResponseError<GErrorResponseBody>
>;
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './multiply-vec2';
export * from './not-empty';
export * from './pick-properties';
export * from './random-hex';
export * from './result';
export * from './rgb-to-hex';
export * from './rgb-to-rgba';
export * from './rgba-to-rgb';
Expand Down
82 changes: 82 additions & 0 deletions packages/utils/src/result.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, expect, it } from 'vitest';

import { Err, Ok, unwrapErr, unwrapOk, type TResult } from './result';

describe('Result implementation', () => {
it('should create an Ok result correctly', () => {
const result = Ok(42);
expect(result._type).toBe('Ok');
expect(result.value).toBe(42);
expect(result.unwrap()).toBe(42);
expect(result.isOk()).toBe(true);
expect(result.isErr()).toBe(false);
});

it('should create an Err result correctly', () => {
const result = Err('Some error');
expect(result._type).toBe('Err');
expect(result.error).toBe('Some error');
expect(() => result.unwrap()).toThrowError();
expect(result.isOk()).toBe(false);
expect(result.isErr()).toBe(true);
});

it('should handle unwrap correctly for Ok result', () => {
const result = Ok('Success');
expect(result.unwrap()).toBe('Success');
});

it('should throw error on unwrap for Err result', () => {
const result = Err('Error occurred');
expect(() => result.unwrap()).toThrow('Error occurred');
});

it('should distinguish between Ok and Err using type guards', () => {
const okResult: TResult<number, string> = Ok(99);
const errResult: TResult<number, string> = Err('Failure');

if (okResult.isOk()) {
expect(okResult.value).toBe(99);
} else {
throw new Error('Expected okResult to be Ok');
}

if (errResult.isErr()) {
expect(errResult.error).toBe('Failure');
} else {
throw new Error('Expected errResult to be Err');
}
});

it('should handle multiple Ok and Err instances', () => {
const okResult1 = Ok(1);
const okResult2 = Ok(2);
const errResult1 = Err('First error');
const errResult2 = Err('Second error');

expect(okResult1.isOk()).toBe(true);
expect(okResult2.isOk()).toBe(true);
expect(errResult1.isErr()).toBe(true);
expect(errResult2.isErr()).toBe(true);
});

it('should handle unwrapOk correctly for Ok result', () => {
const result = Ok('Success');
expect(unwrapOk(result)).toBe('Success');
});

it('should throw error on unwrapErr for Ok result', () => {
const result = Ok('No error');
expect(() => unwrapErr(result)).toThrow('Expected an Err result');
});

it('should handle unwrapErr correctly for Err result', () => {
const result = Err('Error occurred');
expect(unwrapErr(result)).toBe('Error occurred');
});

it('should throw error on unwrapOk for Err result', () => {
const result = Err('Error occurred');
expect(() => unwrapOk(result)).toThrow('Expected an Ok result');
});
});
77 changes: 77 additions & 0 deletions packages/utils/src/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export type TResultError = string | Error;

export interface TOkResult<T, E extends TResultError> {
_type: 'Ok';
value: T;
unwrap: () => T;
isOk: () => this is TOkResult<T, E>;
isErr: () => this is TErrResult<T, E>;
}

export interface TErrResult<T, E extends TResultError> {
_type: 'Err';
error: E;
unwrap: () => T;
isOk: () => this is TOkResult<T, E>;
isErr: () => this is TErrResult<T, E>;
}

export type TResult<T, E extends TResultError> = TOkResult<T, E> | TErrResult<T, E>;

// Factory function for creating an Ok result
export function Ok<T, E extends TResultError>(value: T): TOkResult<T, E> {
return {
_type: 'Ok',
value,
unwrap() {
return value;
},
// @ts-expect-error -- Assignable
isOk() {
return true;
},
// @ts-expect-error -- Assignable
isErr() {
return false;
}
};
}

// Factory function for creating an Err result
export function Err<T, E extends TResultError>(error: E): TErrResult<T, E> {
return {
_type: 'Err',
error,
unwrap() {
if (error instanceof Error) {
throw error;
} else {
throw new Error(error);
}
},
// @ts-expect-error -- Assignable
isOk() {
return false;
},
// @ts-expect-error -- Assignable
isErr() {
return true;
}
};
}

// Extracts value from an Ok result or throws an error if it's an Err
export function unwrapOk<T, E extends TResultError>(result: TResult<T, E>): T {
if (result.isOk()) {
return result.value;
}
throw new Error('Expected an Ok result');
}

// Extracts error from an Err result or throws an error if it's an Ok
export function unwrapErr<T, E extends TResultError>(result: TResult<T, E>): E {
if (result.isErr()) {
return result.error;
}
throw new Error('Expected an Err result');
}
8 changes: 0 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8138857

Please sign in to comment.