Skip to content

Commit

Permalink
Fix function types (#13)
Browse files Browse the repository at this point in the history
* Fix guard types

* Fix fn types

* Fix fn types

* Update option type

* Add explicit types to some, none

* Remove get tests

* Fix promise types

* Remove random consolelog
  • Loading branch information
bkiac authored Nov 15, 2023
1 parent 0737def commit d65d57b
Show file tree
Hide file tree
Showing 19 changed files with 366 additions and 303 deletions.
76 changes: 63 additions & 13 deletions src/helpers/fn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,60 @@ describe.concurrent("fn", () => {

describe("types", () => {
it("returns correct type with function returning Ok | Err", () => {
const f = (_arg: number) => {
const wrapped = fn((_arg: number) => {
if (Math.random() > 0.5) {
return Ok(1)
}
return Err("error")
}
const wrapped = fn(f)
})
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<number, string>>()
})

it("returns correct type with function returning Ok", () => {
const f = (_arg: number) => Ok(1)
const wrapped = fn(f)
const wrapped = fn((_arg: number) => Ok(1))
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<number, unknown>>()
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<number, never>>()
})

it("returns correct type with function returning Err", () => {
const f = (_arg: number) => Err(1)
const wrapped = fn(f)
const wrapped = fn((_arg: number) => Err(1))
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<unknown, number>>()
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<never, number>>()
})

it("returns correct type with function returning Result", () => {
const f = (_arg: number) => tryFn(() => 1)
const wrapped = fn(f)
const wrapped = fn((_arg: number) => tryFn(() => 1))
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<number, StdError>>()
})

it("works with generics", () => {
const wrapped = fn(<A, B>(a: A, b: B) => {
if (Math.random() > 0.5) {
return Ok(a)
}
return Err(b)
})
expectTypeOf(wrapped).toEqualTypeOf<<A, B>(a: A, b: B) => Result<A, B>>()
})

it("works with short-circuit return", () => {
const foo = (): Result<number, string> => {
if (Math.random() > 0.5) {
return Ok(42)
}
return Err("error")
}
const wrapped = fn(() => {
const r = foo()
if (r.err) {
return r
}
return Ok(true)
})
expectTypeOf(wrapped).returns.toEqualTypeOf<Result<boolean, string>>()
})
})
})

Expand Down Expand Up @@ -90,14 +113,14 @@ describe.concurrent("asyncFn", () => {
const f = async (_arg: number) => Ok(1)
const wrapped = asyncFn(f)
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<PromiseResult<number, unknown>>()
expectTypeOf(wrapped).returns.toEqualTypeOf<PromiseResult<number, never>>()
})

it("returns correct type with function returning Promise<Err>", () => {
const f = async (_arg: number) => Err(1)
const wrapped = asyncFn(f)
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<PromiseResult<unknown, number>>()
expectTypeOf(wrapped).returns.toEqualTypeOf<PromiseResult<never, number>>()
})

it("returns correct type with function returning PromiseResult", () => {
Expand All @@ -118,5 +141,32 @@ describe.concurrent("asyncFn", () => {
expectTypeOf(wrapped).parameter(0).toBeNumber()
expectTypeOf(wrapped).returns.toEqualTypeOf<PromiseResult<number, StdError>>()
})

it("works with generics", () => {
const wrapped = asyncFn(async <A, B>(a: A, b: B) => {
if (Math.random() > 0.5) {
return Ok(a)
}
return Err(b)
})
expectTypeOf(wrapped).toEqualTypeOf<<A, B>(a: A, b: B) => PromiseResult<A, B>>()
})

it("works with short-circuit return", () => {
const foo = asyncFn(async () => {
if (Math.random() > 0.5) {
return Ok(42)
}
return Err("error")
})
const wrapped = asyncFn(async () => {
const r = await foo()
if (r.err) {
return r
}
return Ok(true)
})
expectTypeOf(wrapped).returns.toEqualTypeOf<PromiseResult<boolean, string>>()
})
})
})
50 changes: 33 additions & 17 deletions src/helpers/fn.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import type {
ResultValueType,
ResultErrorType,
AsyncResultValueType,
AsyncResultErrorType,
} from "../util"
import type {Result} from "../result/interface"
import type {Result} from "../result/result"
import {PromiseResult} from "../result/promise"
import type {Err, Ok} from "../internal"

export function fn<T extends (...args: any[]) => Result<any, any>>(
f: T,
): (...args: Parameters<T>) => Result<ResultValueType<T>, ResultErrorType<T>> {
export function fn<A extends any[], T>(f: (...args: A) => Ok<T>): (...args: A) => Result<T, never>
export function fn<A extends any[], E>(f: (...args: A) => Err<E>): (...args: A) => Result<never, E>
export function fn<A extends any[], T, E>(
f: (...args: A) => Ok<T> | Err<E>,
): (...args: A) => Result<T, E>
export function fn<A extends any[], T, E>(
f: (...args: A) => Result<T, E>,
): (...args: A) => Result<T, E>
export function fn<A extends any[], T, E>(
f: (...args: A) => Result<T, E>,
): (...args: A) => Result<T, E> {
return f
}

export function asyncFn<
T extends
| ((...args: any[]) => PromiseResult<any, any>)
| ((...args: any[]) => Promise<Result<any, any>>),
>(f: T) {
return function (...args: Parameters<T>) {
return new PromiseResult<AsyncResultValueType<T>, AsyncResultErrorType<T>>(f(...args))
export function asyncFn<A extends any[], T>(
f: (...args: A) => Promise<Ok<T>>,
): (...args: A) => PromiseResult<T, never>
export function asyncFn<A extends any[], E>(
f: (...args: A) => Promise<Err<E>>,
): (...args: A) => PromiseResult<never, E>
export function asyncFn<A extends any[], T, E>(
f: (...args: A) => Promise<Ok<T> | Err<E>>,
): (...args: A) => PromiseResult<T, E>
export function asyncFn<A extends any[], T, E>(
f: (...args: A) => Promise<Result<T, E>>,
): (...args: A) => PromiseResult<T, E>
export function asyncFn<A extends any[], T, E>(
f: (...args: A) => PromiseResult<T, E>,
): (...args: A) => PromiseResult<T, E>
export function asyncFn<A extends any[], T, E>(
f: (...args: A) => Promise<Ok<T> | Err<E> | Result<T, E>> | PromiseResult<T, E>,
): (...args: A) => PromiseResult<T, E> {
return function (...args: A) {
return new PromiseResult<T, E>(f(...args))
}
}
2 changes: 1 addition & 1 deletion src/helpers/group.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {tryAsyncFnWith, tryFnWith, tryPromiseWith} from "./try"
import type {ErrorHandler} from "../error/result_error"
import type {Result} from "../result/interface"
import type {Result} from "../result/result"
import type {PromiseResult} from "../result/promise"

type Fn = (...args: any[]) => any
Expand Down
77 changes: 75 additions & 2 deletions src/helpers/guard.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import {expect, it, describe} from "vitest"
import {ResultError, guard, guardAsync, guardAsyncWith, guardWith} from "../internal"
import {expect, it, describe, expectTypeOf} from "vitest"
import {
ResultError,
StdError,
guard,
guardAsync,
guardAsyncWith,
guardWith,
Result,
PromiseResult,
} from "../internal"

class MyError extends ResultError {
readonly tag = "MyError"
Expand All @@ -24,6 +33,24 @@ describe.concurrent("guard", () => {
expect(result.ok).toEqual(false)
expect(result.unwrapErr().origin).toEqual(error)
})

describe("types", () => {
it("works with a function", () => {
const f = (value: number) => value
const guarded = guard(f)
expectTypeOf(guarded).toEqualTypeOf<
(value: number) => Result<number, StdError<unknown>>
>()
})

it("works with a generic function", () => {
const f = <A, B>(a: A, _b: B) => a
const guarded = guard(f)
expectTypeOf(guarded).toEqualTypeOf<
<A, B>(a: A, b: B) => Result<A, StdError<unknown>>
>()
})
})
})

describe.concurrent("guardWith", () => {
Expand All @@ -46,6 +73,20 @@ describe.concurrent("guardWith", () => {
expect(result.ok).toEqual(false)
expect(result.unwrapErr()).toEqual(myError)
})

describe("types", () => {
it("works with a function", () => {
const f = (value: number) => value
const guarded = guardWith(f, () => new MyError())
expectTypeOf(guarded).toEqualTypeOf<(value: number) => Result<number, MyError>>()
})

it("works with a generic function", () => {
const f = <A, B>(a: A, _b: B) => a
const guarded = guardWith(f, () => new MyError())
expectTypeOf(guarded).toEqualTypeOf<<A, B>(a: A, b: B) => Result<A, MyError>>()
})
})
})

describe.concurrent("guardAsync", () => {
Expand All @@ -67,6 +108,24 @@ describe.concurrent("guardAsync", () => {
expect(result.ok).toEqual(false)
expect(result.unwrapErr().origin).toEqual(error)
})

describe("types", () => {
it("works with a function", () => {
const f = async (value: number) => value
const guarded = guardAsync(f)
expectTypeOf(guarded).toEqualTypeOf<
(value: number) => PromiseResult<number, StdError<unknown>>
>()
})

it("works with a generic function", () => {
const f = async <A, B>(a: A, _b: B) => a
const guarded = guardAsync(f)
expectTypeOf(guarded).toEqualTypeOf<
<A, B>(a: A, b: B) => PromiseResult<A, StdError<unknown>>
>()
})
})
})

describe.concurrent("guardAsyncWith", () => {
Expand All @@ -89,4 +148,18 @@ describe.concurrent("guardAsyncWith", () => {
expect(result.ok).toEqual(false)
expect(result.unwrapErr()).toEqual(myError)
})

describe("types", () => {
it("works with a function", () => {
const f = async (value: number) => value
const guarded = guardAsyncWith(f, () => new MyError())
expectTypeOf(guarded).toEqualTypeOf<(value: number) => PromiseResult<number, MyError>>()
})

it("works with a generic function", () => {
const f = async <A, B>(a: A, _b: B) => a
const guarded = guardAsyncWith(f, () => new MyError())
expectTypeOf(guarded).toEqualTypeOf<<A, B>(a: A, b: B) => PromiseResult<A, MyError>>()
})
})
})
35 changes: 19 additions & 16 deletions src/helpers/guard.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import type {ErrorHandler, ResultError} from "../error/result_error"
import {type ErrorHandler, type ResultError} from "../error/result_error"
import {tryAsyncFn, tryAsyncFnWith, tryFn, tryFnWith} from "./try"

type Fn = (...args: any[]) => any
type AsyncFn = (...args: any[]) => Promise<any>

export function guard<T extends Fn>(f: T) {
return function (...args: Parameters<T>) {
return tryFn<ReturnType<T>>(() => f(...args))
export function guard<A extends any[], T>(f: (...args: A) => T) {
return function (...args: A) {
return tryFn<T>(() => f(...args))
}
}

export function guardWith<T extends Fn, E extends ResultError>(f: T, h: ErrorHandler<E>) {
return function (...args: Parameters<T>) {
return tryFnWith<ReturnType<T>, E>(() => f(...args), h)
export function guardWith<A extends any[], T, E extends ResultError<Error | null>>(
f: (...args: A) => T,
h: ErrorHandler<E>,
) {
return function (...args: A) {
return tryFnWith<T, E>(() => f(...args), h)
}
}

export function guardAsync<T extends AsyncFn>(f: T) {
return function (...args: Parameters<T>) {
return tryAsyncFn<Awaited<ReturnType<T>>>(() => f(...args))
export function guardAsync<A extends any[], T>(f: (...args: A) => Promise<T>) {
return function (...args: A) {
return tryAsyncFn<T>(() => f(...args))
}
}

export function guardAsyncWith<T extends AsyncFn, E extends ResultError>(f: T, h: ErrorHandler<E>) {
return function (...args: Parameters<T>) {
return tryAsyncFnWith<Awaited<ReturnType<T>>, E>(() => f(...args), h)
export function guardAsyncWith<A extends any[], T, E extends ResultError<Error | null>>(
f: (...args: A) => Promise<T>,
h: ErrorHandler<E>,
) {
return function (...args: A) {
return tryAsyncFnWith<T, E>(() => f(...args), h)
}
}
Loading

0 comments on commit d65d57b

Please sign in to comment.