Skip to content

Commit

Permalink
feat: add Task.prototype.validate and wrapTaskCreator
Browse files Browse the repository at this point in the history
  • Loading branch information
tdreyno committed Jan 8, 2021
1 parent 4b4fe1b commit 7b91b37
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 1 deletion.
22 changes: 21 additions & 1 deletion src/Task/Task.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-misused-promises, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-use-before-define */
import { constant, identity, range } from "../util"
import { constant, identity, range, Validation } from "../util"

export type Reject<E> = (error: E) => void
export type Resolve<S> = (result: S) => void
Expand Down Expand Up @@ -574,6 +574,20 @@ export const tapChain = <E, S, S2>(
task: Task<E, S>,
): Task<E, S> => chain(result => fn(result).forward(result), task)

/**
* Run a function on a successful value which can fail the task or modify the type.
* @param fn A function will return a Validation on the value.
* @param task The task to tap on succcess.
*/
export const validate = <E, S, E2, S2>(
fn: (value: S) => Validation<E2, S2>,
task: Task<E, S>,
): Task<E | E2, S2> =>
chain((value: S) => {
const result = fn(value)
return result.success ? of(result.value) : fail(result.error)
}, task)

/**
* Given a task, map the failure error to a Task.
* @alias recoverWith
Expand Down Expand Up @@ -890,6 +904,12 @@ export class Task<E, S> implements PromiseLike<S> {
return tapChain(fn, this)
}

public validate<E2, S2>(
fn: (value: S) => Validation<E2, S2>,
): Task<E | E2, S2> {
return validate(fn, this)
}

public mapError<E2>(fn: (error: E) => E2): Task<E2, S> {
return mapError(fn, this)
}
Expand Down
57 changes: 57 additions & 0 deletions src/Task/__tests__/validate.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { failedValidation, successfulValidation, Validation } from "../../util"
import { fail, succeed } from "../Task"
import { ERROR_RESULT } from "./util"

enum GT4Brand {
_ = "",
}
type GT4 = GT4Brand & number

const isGT4 = (value: number): value is GT4 => value > 4

const isGT4Validator = (value: number): Validation<string, GT4> => {
if (isGT4(value)) {
return successfulValidation<GT4>(value)
}

return failedValidation(`${value.toString()} <= 4`)
}

describe("validate", () => {
test("should call validate successfully", () => {
const resolve = jest.fn()
const reject = jest.fn()

succeed(5)
.validate(isGT4Validator)
.map((val: GT4) => val)
.fork(reject, resolve)

expect(reject).not.toBeCalled()
expect(resolve).toBeCalledWith(5)
})

test("should call validate when failing", () => {
const resolve = jest.fn()
const reject = jest.fn()

succeed(3)
.validate(isGT4Validator)
.mapError((err: string) => err)
.fork(reject, resolve)

expect(reject).toBeCalledWith("3 <= 4")
expect(resolve).not.toBeCalled()
})

test("should call not validate when given a failure", () => {
const resolve = jest.fn()
const reject = jest.fn()
const validate = jest.fn()

fail(ERROR_RESULT).validate(validate).fork(reject, resolve)

expect(validate).not.toBeCalled()
expect(reject).toBeCalledWith(ERROR_RESULT)
})
})
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ import Task, { ExternalTask, LoopBreak, LoopContinue } from "./Task/index"

export * from "./util"
export { RemoteData, Task, ExternalTask, LoopContinue, LoopBreak, Subscription }

/**
* Given a function that returns a task, return a new function that
* returns a promise instead.
* @param fn A function which returns a promise
*/
export const wrapTaskCreator = <S, Args extends unknown[]>(
fn: (...args: Args) => Task<unknown, S>,
) => (...args: Args): Promise<S> => fn(...args).toPromise()
14 changes: 14 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ Array.prototype.chain_ = function <T, U>(
): U {
return fn(this)
}

export type Validation<E, S> =
| { success: true; value: S }
| { success: false; error: E }

export const successfulValidation = <S>(value: S): Validation<never, S> => ({
success: true,
value,
})

export const failedValidation = <E>(error: E): Validation<E, never> => ({
success: false,
error,
})

0 comments on commit 7b91b37

Please sign in to comment.