Repository: gcanti/io-ts-types (ts)

Issue:
add fallback combinator
We've just had the need for some default value as part of a decoding (long story, short; firebase erase empty dictionaries..).
(We have tests)

```typescript
export const withDefault = <T, O>(Type: t.Type<T, O>, v: T): typeof Type =>
  t.union([
    Type,
    new t.Type(`withDefault${Type.name}`, Type.is, (_i, _context) => right(v), Type.encode)
  ])
```

Usage:
```typescript
const Type = withDefault(
  t.dictionary(t.string, ActionPayloadData),
  {}
)

// use Type as usual
```


I'm not sure about the naming (both the function and the Type name) do you have some better one?


Pull request description:
add fallback fixes #49

Patch:
diff --git a/src/fallback.ts b/src/fallback.ts
new file mode 100644
index 0000000..5a93216
--- /dev/null
+++ b/src/fallback.ts
@@ -0,0 +1,17 @@
+import * as t from 'io-ts'
+
+export const fallback = <A, O, I>(type: t.Type<A, O, I>) => (
+  a: A,
+  name = `fallback(${type.name})`
+): t.Type<A, O, I> => {
+  const isFallbackValid = type.is(a)
+  return new t.Type<A, O, I>(
+    name,
+    type.is,
+    (i, context) => {
+      const validation = type.validate(i, context)
+      return validation.isLeft() && isFallbackValid ? t.success(a) : validation
+    },
+    type.encode
+  )
+}
diff --git a/src/index.ts b/src/index.ts
index bb0c63d..d3ec88e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -35,3 +35,6 @@ export { mapOutput } from './mapOutput'
 
 // UUID
 export { uuid } from './string/uuid'
+
+// io-ts
+export { fallback } from './fallback'


Test patch:
diff --git a/test/index.ts b/test/index.ts
index 5abe3cc..c2b261c 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -24,7 +24,8 @@ import {
   lensesFromInterface,
   mapOutput,
   fromNewtypeCurried,
-  uuid
+  uuid,
+  fallback
 } from '../src'
 import { left, right } from 'fp-ts/lib/Either'
 import { none, some } from 'fp-ts/lib/Option'
@@ -327,3 +328,27 @@ it('UUID', () => {
   assert.deepEqual(uuid.decode('6e9c5587-a342-4b63-a901-87b31fa2ffa3'), right('6e9c5587-a342-4b63-a901-87b31fa2ffa3'))
   assert.deepEqual(PathReporter.report(uuid.decode('invalid')), ['Invalid value "invalid" supplied to : UUID'])
 })
+
+it('fallback', () => {
+  const date1 = new Date(123456)
+  const date2 = new Date(1234564)
+
+  const defaultDate1 = fallback(DateFromISOString)(date1)
+
+  assert.deepEqual(defaultDate1.decode(date2.toISOString()), right(date2), 'decode actual valid value') //
+  assert.deepEqual(defaultDate1.decode(date2), right(date1), 'fallback on alternate value on failure') //
+  assert.deepEqual(defaultDate1.decode('date2'), right(date1), 'fallback on alternate value on failure(2)')
+
+  assert.deepEqual(defaultDate1.is(date2), true, 'is on correct value')
+  assert.deepEqual(defaultDate1.is('date2'), false, 'is on invalid value')
+
+  assert.deepEqual(defaultDate1.encode(date1), date1.toISOString(), 'encode')
+
+  const invalidFallback = fallback(t.refinement(t.number, n => n > 0))(-1)
+  assert.deepEqual(invalidFallback.decode(1), right(1), 'decode value with invalid fallback')
+  assert.deepEqual(invalidFallback.decode(-1).isLeft(), true, 'fails decoding invalid value with invalid fallback')
+  assert.deepEqual(invalidFallback.encode(1), 1, 'encodes valid with invalid fallback')
+  assert.deepEqual(invalidFallback.encode(-1), -1, 'encodes invalid with invalid fallback')
+  assert.deepEqual(invalidFallback.is(1), true, 'is on valid with invalid fallback')
+  assert.deepEqual(invalidFallback.is(-1), false, 'is on invalid with invalid fallback')
+})