Repository: gcanti/fp-ts
Pull request #455 (2018-05-23 09:19:55)

Handle undefined errors in callback of TaskEither taskify
When wrapping a function from elasticsearch client the callback is called with `undefined` as first argument preventing taskify to return a right and instead returns left

gcanti: Thanks @dmechas. There's another bug though

```ts
export declare function taskify<A, L, R>(f: (a: A, cb: (e: L, r: R) => void) => void): (a: A) => TaskEither<L, R>
```

should be

```ts
export declare function taskify<A, L, R>(
  f: (a: A, cb: (e: L | null | undefined, r?: R) => void) => void
): (a: A) => TaskEither<L, R>
```

otherwise you get

```ts
const api1 = (path: string, callback: (err: Error | null | undefined, result?: string) => void): void => {
  callback(undefined, 'ok')
}

// const f1: (a: string) => TaskEither<Error | null | undefined, string | undefined>
const f1 = taskify(api1)
```

instead of 

```ts
const f1: (a: string) => TaskEither<Error, string>
```

Patch:
diff --git a/docs/ReaderTaskEither.md b/docs/ReaderTaskEither.md
index 1670d8b1..46cc8e65 100644
--- a/docs/ReaderTaskEither.md
+++ b/docs/ReaderTaskEither.md
@@ -113,6 +113,18 @@ _Signature_
 <B>(f: (a: A) => ReaderTaskEither<E, L, B>): ReaderTaskEither<E, L, B>
 ```
 
+### fold
+
+_method_
+
+_since 1.6.0_
+
+_Signature_
+
+```ts
+<R>(left: (l: L) => R, right: (a: A) => R): Reader<E, Task<R>>
+```
+
 ### map
 
 _method_
@@ -257,6 +269,21 @@ _Signature_
 <E, L, A>(l: L): ReaderTaskEither<E, L, A>
 ```
 
+### fromPredicate
+
+_function_
+
+_since 1.6.0_
+
+_Signature_
+
+```ts
+<E, L, A>(
+  predicate: Predicate<A>,
+  whenFalse: (a: A) => L
+): ((a: A) => ReaderTaskEither<E, L, A>)
+```
+
 ### fromReader
 
 _function_
diff --git a/docs/TaskEither.md b/docs/TaskEither.md
index 2d5a7000..e00e7d4e 100644
--- a/docs/TaskEither.md
+++ b/docs/TaskEither.md
@@ -245,6 +245,18 @@ _Signature_
 <L, A>(l: L): TaskEither<L, A>
 ```
 
+### fromPredicate
+
+_function_
+
+_since 1.6.0_
+
+_Signature_
+
+```ts
+<L, A>(predicate: Predicate<A>, whenFalse: (a: A) => L): ((a: A) => TaskEither<L, A>)
+```
+
 ### left
 
 _function_
diff --git a/src/ReaderTaskEither.ts b/src/ReaderTaskEither.ts
index 9efc8aa8..65b92b24 100644
--- a/src/ReaderTaskEither.ts
+++ b/src/ReaderTaskEither.ts
@@ -1,7 +1,7 @@
 import { Alt3 } from './Alt'
 import { Bifunctor3 } from './Bifunctor'
 import { Either } from './Either'
-import { constant, constIdentity } from './function'
+import { constant, constIdentity, Predicate } from './function'
 import { IO } from './IO'
 import { IOEither } from './IOEither'
 import { Monad3 } from './Monad'
@@ -76,6 +76,12 @@ export class ReaderTaskEither<E, L, A> {
   chain<B>(f: (a: A) => ReaderTaskEither<E, L, B>): ReaderTaskEither<E, L, B> {
     return new ReaderTaskEither(readerTTaskEither.chain(a => f(a).value, this.value))
   }
+  /**
+   * @since 1.6.0
+   */
+  fold<R>(left: (l: L) => R, right: (a: A) => R): Reader<E, Task<R>> {
+    return new Reader(e => this.value(e).fold(left, right))
+  }
   /**
    * @since 1.6.0
    */
@@ -228,6 +234,18 @@ export const fromIOEither = <E, L, A>(fa: IOEither<L, A>): ReaderTaskEither<E, L
   return fromTaskEither(taskEither.fromIOEither(fa))
 }
 
+/**
+ * @function
+ * @since 1.6.0
+ */
+export const fromPredicate = <E, L, A>(
+  predicate: Predicate<A>,
+  whenFalse: (a: A) => L
+): ((a: A) => ReaderTaskEither<E, L, A>) => {
+  const f = taskEither.fromPredicate(predicate, whenFalse)
+  return a => fromTaskEither(f(a))
+}
+
 /**
  * @function
  * @since 1.6.0
diff --git a/src/TaskEither.ts b/src/TaskEither.ts
index 8285d578..ac2efe80 100644
--- a/src/TaskEither.ts
+++ b/src/TaskEither.ts
@@ -1,11 +1,11 @@
 import { Alt2 } from './Alt'
 import { Bifunctor2 } from './Bifunctor'
-import { Either, left as eitherLeft, right as eitherRight } from './Either'
+import { Either, fromPredicate as eitherFromPredicate, left as eitherLeft, right as eitherRight } from './Either'
 import * as eitherT from './EitherT'
 import { IO } from './IO'
 import { Monad2 } from './Monad'
 import { Task, fromIO as taskFromIO, task, tryCatch as taskTryCatch } from './Task'
-import { Lazy, constIdentity, constant } from './function'
+import { Lazy, constIdentity, constant, Predicate } from './function'
 import { IOEither } from './IOEither'
 
 declare module './HKT' {
@@ -165,6 +165,15 @@ export const fromIOEither = <L, A>(fa: IOEither<L, A>): TaskEither<L, A> => {
   return new TaskEither(taskFromIO(fa.value))
 }
 
+/**
+ * @function
+ * @since 1.6.0
+ */
+export const fromPredicate = <L, A>(predicate: Predicate<A>, whenFalse: (a: A) => L): ((a: A) => TaskEither<L, A>) => {
+  const f = eitherFromPredicate(predicate, whenFalse)
+  return a => fromEither(f(a))
+}
+
 /**
  * @function
  * @since 1.0.0
@@ -174,17 +183,21 @@ export const tryCatch = <L, A>(f: Lazy<Promise<A>>, onrejected: (reason: {}) =>
 }
 
 /** Convert a node style callback function to one returning a `TaskEither` */
-export function taskify<L, R>(f: (cb: (e: L, r: R) => void) => void): () => TaskEither<L, R>
-export function taskify<A, L, R>(f: (a: A, cb: (e: L, r: R) => void) => void): (a: A) => TaskEither<L, R>
-export function taskify<A, B, L, R>(f: (a: A, b: B, cb: (e: L, r: R) => void) => void): (a: A, b: B) => TaskEither<L, R>
+export function taskify<L, R>(f: (cb: (e: L | null | undefined, r: R) => void) => void): () => TaskEither<L, R>
+export function taskify<A, L, R>(
+  f: (a: A, cb: (e: L | null | undefined, r: R) => void) => void
+): (a: A) => TaskEither<L, R>
+export function taskify<A, B, L, R>(
+  f: (a: A, b: B, cb: (e: L | null | undefined, r: R) => void) => void
+): (a: A, b: B) => TaskEither<L, R>
 export function taskify<A, B, C, L, R>(
-  f: (a: A, b: B, c: C, cb: (e: L, r: R) => void) => void
+  f: (a: A, b: B, c: C, cb: (e: L | null | undefined, r: R) => void) => void
 ): (a: A, b: B, c: C) => TaskEither<L, R>
 export function taskify<A, B, C, D, L, R>(
-  f: (a: A, b: B, c: C, d: D, cb: (e: L, r: R) => void) => void
+  f: (a: A, b: B, c: C, d: D, cb: (e: L | null | undefined, r: R) => void) => void
 ): (a: A, b: B, c: C, d: D) => TaskEither<L, R>
 export function taskify<A, B, C, D, E, L, R>(
-  f: (a: A, b: B, c: C, d: D, e: E, cb: (e: L, r: R) => void) => void
+  f: (a: A, b: B, c: C, d: D, e: E, cb: (e: L | null | undefined, r: R) => void) => void
 ): (a: A, b: B, c: C, d: D, e: E) => TaskEither<L, R>
 /**
  * Convert a node style callback function to one returning a `TaskEither`
@@ -219,7 +232,7 @@ export function taskify<L, R>(f: Function): () => TaskEither<L, R> {
       new Task(
         () =>
           new Promise(resolve => {
-            args.push((e: L, r: R) => (e !== null ? resolve(eitherLeft<L, R>(e)) : resolve(eitherRight<L, R>(r))))
+            args.push((e: L, r: R) => (e != null ? resolve(eitherLeft<L, R>(e)) : resolve(eitherRight<L, R>(r))))
             f.apply(null, args)
           })
       )


Test patch:
diff --git a/test/ReaderTaskEither.ts b/test/ReaderTaskEither.ts
index 06aab9ce..22a4521f 100644
--- a/test/ReaderTaskEither.ts
+++ b/test/ReaderTaskEither.ts
@@ -13,7 +13,8 @@ import {
   ReaderTaskEither,
   fromLeft,
   fromIO,
-  fromIOEither
+  fromIOEither,
+  fromPredicate
 } from '../src/ReaderTaskEither'
 import { left as eitherLeft, right as eitherRight, either } from '../src/Either'
 import { left as taskEitherLeft, taskEither } from '../src/TaskEither'
@@ -73,6 +74,17 @@ describe('ReaderTaskEither', () => {
     })
   })
 
+  it('fold', () => {
+    const f = (s: string): boolean => s.length > 2
+    const g = (n: number): boolean => n > 2
+    const rte1 = readerTaskEither.of<{}, string, number>(1).fold(f, g)
+    const rte2 = fromLeft<{}, string, number>('foo').fold(f, g)
+    return Promise.all([rte1.run({}).run(), rte2.run({}).run()]).then(([b1, b2]) => {
+      assert.strictEqual(b1, false)
+      assert.strictEqual(b2, true)
+    })
+  })
+
   it('of', () => {
     return readerTaskEither
       .of(1)
@@ -233,4 +245,16 @@ describe('ReaderTaskEither', () => {
       assert.deepEqual(e4, e5)
     })
   })
+
+  it('fromPredicate', () => {
+    const predicate = (n: number) => n >= 2
+    const handleError = (n: number) => `Invalid number ${n}`
+    const gt2 = fromPredicate(predicate, handleError)
+    const x1 = gt2(3)
+    const x2 = gt2(1)
+    return Promise.all([x1.run({}), x2.run({})]).then(([e1, e2]) => {
+      assert.deepEqual(e1, eitherRight(3))
+      assert.deepEqual(e2, eitherLeft('Invalid number 1'))
+    })
+  })
 })
diff --git a/test/TaskEither.ts b/test/TaskEither.ts
index d37e8e07..42d4b6bc 100644
--- a/test/TaskEither.ts
+++ b/test/TaskEither.ts
@@ -11,7 +11,8 @@ import {
   taskEither,
   taskify,
   tryCatch,
-  fromIOEither
+  fromIOEither,
+  fromPredicate
 } from '../src/TaskEither'
 import { IOEither } from '../src/IOEither'
 
@@ -140,16 +141,22 @@ describe('TaskEither', () => {
   })
 
   it('taskify', () => {
-    const api1 = (path: string, callback: (err: Error | null, result?: string) => void): void => {
+    const api1 = (path: string, callback: (err: Error | null | undefined, result?: string) => void): void => {
       callback(null, 'ok')
     }
-    const api2 = (path: string, callback: (err: Error | null, result?: string) => void): void => {
+    const api2 = (path: string, callback: (err: Error | null | undefined, result?: string) => void): void => {
+      callback(undefined, 'ok')
+    }
+    const api3 = (path: string, callback: (err: Error | null | undefined, result?: string) => void): void => {
       callback(new Error('ko'))
     }
-    return Promise.all([taskify(api1)('foo').run(), taskify(api2)('foo').run()]).then(([e1, e2]) => {
-      assert.deepEqual(e1, eitherRight('ok'))
-      assert.deepEqual(e2, eitherLeft(new Error('ko')))
-    })
+    return Promise.all([taskify(api1)('foo').run(), taskify(api2)('foo').run(), taskify(api3)('foo').run()]).then(
+      ([e1, e2, e3]) => {
+        assert.deepEqual(e1, eitherRight('ok'))
+        assert.deepEqual(e2, eitherRight('ok'))
+        assert.deepEqual(e3, eitherLeft(new Error('ko')))
+      }
+    )
   })
 
   it('alt', () => {
@@ -192,4 +199,16 @@ describe('TaskEither', () => {
         assert.deepEqual(log, ['a', 'b'])
       })
   })
+
+  it('fromPredicate', () => {
+    const predicate = (n: number) => n >= 2
+    const handleError = (n: number) => `Invalid number ${n}`
+    const gt2 = fromPredicate(predicate, handleError)
+    const x1 = gt2(3)
+    const x2 = gt2(1)
+    return Promise.all([x1.run(), x2.run()]).then(([e1, e2]) => {
+      assert.deepEqual(e1, eitherRight(3))
+      assert.deepEqual(e2, eitherLeft('Invalid number 1'))
+    })
+  })
 })