Skip to content

Commit 7b91b37

Browse files
committed
feat: add Task.prototype.validate and wrapTaskCreator
1 parent 4b4fe1b commit 7b91b37

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

src/Task/Task.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-misused-promises, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-use-before-define */
2-
import { constant, identity, range } from "../util"
2+
import { constant, identity, range, Validation } from "../util"
33

44
export type Reject<E> = (error: E) => void
55
export type Resolve<S> = (result: S) => void
@@ -574,6 +574,20 @@ export const tapChain = <E, S, S2>(
574574
task: Task<E, S>,
575575
): Task<E, S> => chain(result => fn(result).forward(result), task)
576576

577+
/**
578+
* Run a function on a successful value which can fail the task or modify the type.
579+
* @param fn A function will return a Validation on the value.
580+
* @param task The task to tap on succcess.
581+
*/
582+
export const validate = <E, S, E2, S2>(
583+
fn: (value: S) => Validation<E2, S2>,
584+
task: Task<E, S>,
585+
): Task<E | E2, S2> =>
586+
chain((value: S) => {
587+
const result = fn(value)
588+
return result.success ? of(result.value) : fail(result.error)
589+
}, task)
590+
577591
/**
578592
* Given a task, map the failure error to a Task.
579593
* @alias recoverWith
@@ -890,6 +904,12 @@ export class Task<E, S> implements PromiseLike<S> {
890904
return tapChain(fn, this)
891905
}
892906

907+
public validate<E2, S2>(
908+
fn: (value: S) => Validation<E2, S2>,
909+
): Task<E | E2, S2> {
910+
return validate(fn, this)
911+
}
912+
893913
public mapError<E2>(fn: (error: E) => E2): Task<E2, S> {
894914
return mapError(fn, this)
895915
}

src/Task/__tests__/validate.spec.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { failedValidation, successfulValidation, Validation } from "../../util"
2+
import { fail, succeed } from "../Task"
3+
import { ERROR_RESULT } from "./util"
4+
5+
enum GT4Brand {
6+
_ = "",
7+
}
8+
type GT4 = GT4Brand & number
9+
10+
const isGT4 = (value: number): value is GT4 => value > 4
11+
12+
const isGT4Validator = (value: number): Validation<string, GT4> => {
13+
if (isGT4(value)) {
14+
return successfulValidation<GT4>(value)
15+
}
16+
17+
return failedValidation(`${value.toString()} <= 4`)
18+
}
19+
20+
describe("validate", () => {
21+
test("should call validate successfully", () => {
22+
const resolve = jest.fn()
23+
const reject = jest.fn()
24+
25+
succeed(5)
26+
.validate(isGT4Validator)
27+
.map((val: GT4) => val)
28+
.fork(reject, resolve)
29+
30+
expect(reject).not.toBeCalled()
31+
expect(resolve).toBeCalledWith(5)
32+
})
33+
34+
test("should call validate when failing", () => {
35+
const resolve = jest.fn()
36+
const reject = jest.fn()
37+
38+
succeed(3)
39+
.validate(isGT4Validator)
40+
.mapError((err: string) => err)
41+
.fork(reject, resolve)
42+
43+
expect(reject).toBeCalledWith("3 <= 4")
44+
expect(resolve).not.toBeCalled()
45+
})
46+
47+
test("should call not validate when given a failure", () => {
48+
const resolve = jest.fn()
49+
const reject = jest.fn()
50+
const validate = jest.fn()
51+
52+
fail(ERROR_RESULT).validate(validate).fork(reject, resolve)
53+
54+
expect(validate).not.toBeCalled()
55+
expect(reject).toBeCalledWith(ERROR_RESULT)
56+
})
57+
})

src/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,12 @@ import Task, { ExternalTask, LoopBreak, LoopContinue } from "./Task/index"
44

55
export * from "./util"
66
export { RemoteData, Task, ExternalTask, LoopContinue, LoopBreak, Subscription }
7+
8+
/**
9+
* Given a function that returns a task, return a new function that
10+
* returns a promise instead.
11+
* @param fn A function which returns a promise
12+
*/
13+
export const wrapTaskCreator = <S, Args extends unknown[]>(
14+
fn: (...args: Args) => Task<unknown, S>,
15+
) => (...args: Args): Promise<S> => fn(...args).toPromise()

src/util.ts

+14
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,17 @@ Array.prototype.chain_ = function <T, U>(
4949
): U {
5050
return fn(this)
5151
}
52+
53+
export type Validation<E, S> =
54+
| { success: true; value: S }
55+
| { success: false; error: E }
56+
57+
export const successfulValidation = <S>(value: S): Validation<never, S> => ({
58+
success: true,
59+
value,
60+
})
61+
62+
export const failedValidation = <E>(error: E): Validation<E, never> => ({
63+
success: false,
64+
error,
65+
})

0 commit comments

Comments
 (0)