diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9bf62958..e8a37c2d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
- **Experimental**
- `Decoder`
- add support for non-`string` tag values to `sum`, closes #481 (@gcanti)
+ - `intersection` should accumulate all errors (@gcanti)
# 2.2.5
diff --git a/src/Decoder.ts b/src/Decoder.ts
index ed478d7d0..f92eb4fbf 100644
--- a/src/Decoder.ts
+++ b/src/Decoder.ts
@@ -1,7 +1,7 @@
/**
* @since 2.2.0
*/
-import { Either, isLeft, isRight, left, mapLeft, right } from 'fp-ts/lib/Either'
+import * as E from 'fp-ts/lib/Either'
import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
import { pipe } from 'fp-ts/lib/pipeable'
import { Forest, Tree } from 'fp-ts/lib/Tree'
@@ -14,6 +14,8 @@ import { Alt1 } from 'fp-ts/lib/Alt'
// model
// -------------------------------------------------------------------------------------
+import Either = E.Either
+
/**
* @category model
* @since 2.2.0
@@ -59,7 +61,7 @@ export function tree(value: A, forest: Forest = empty): Tree {
* @since 2.2.0
*/
export function success(a: A): Either {
- return right(a)
+ return E.right(a)
}
/**
@@ -67,7 +69,7 @@ export function success(a: A): Either {
* @since 2.2.0
*/
export function failure(message: string): Either {
- return left([tree(message)])
+ return E.left([tree(message)])
}
/**
@@ -170,7 +172,7 @@ export function withExpected(
decode: (u) =>
pipe(
decoder.decode(u),
- mapLeft((nea) => expected(u, nea))
+ E.mapLeft((nea) => expected(u, nea))
)
}
}
@@ -187,7 +189,7 @@ export function refinement(
return {
decode: (u) => {
const e = from.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
}
const a = e.right
@@ -204,11 +206,11 @@ export function parse(from: Decoder, parser: (a: A) => Either {
const e = from.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
}
const pe = parser(e.right)
- if (isLeft(pe)) {
+ if (E.isLeft(pe)) {
return failure(pe.left)
}
return pe
@@ -232,7 +234,7 @@ export function type(properties: { [K in keyof A]: Decoder }): Decoder<
return {
decode: (u) => {
const e = UnknownRecord.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
} else {
const r = e.right
@@ -240,13 +242,13 @@ export function type(properties: { [K in keyof A]: Decoder }): Decoder<
const errors: Array> = []
for (const k in properties) {
const e = properties[k].decode(r[k])
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
errors.push(tree(`required property ${JSON.stringify(k)}`, e.left))
} else {
a[k] = e.right
}
}
- return isNotEmpty(errors) ? left(errors) : success(a as A)
+ return isNotEmpty(errors) ? E.left(errors) : success(a as A)
}
}
}
@@ -260,7 +262,7 @@ export function partial(properties: { [K in keyof A]: Decoder }): Decod
return {
decode: (u) => {
const e = UnknownRecord.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
} else {
const r = e.right
@@ -275,7 +277,7 @@ export function partial(properties: { [K in keyof A]: Decoder }): Decod
a[k] = undefined
} else {
const e = properties[k].decode(rk)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
errors.push(tree(`optional property ${JSON.stringify(k)}`, e.left))
} else {
a[k] = e.right
@@ -283,7 +285,7 @@ export function partial(properties: { [K in keyof A]: Decoder }): Decod
}
}
}
- return isNotEmpty(errors) ? left(errors) : success(a)
+ return isNotEmpty(errors) ? E.left(errors) : success(a)
}
}
}
@@ -297,7 +299,7 @@ export function record(codomain: Decoder): Decoder> {
return {
decode: (u) => {
const e = UnknownRecord.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
} else {
const r = e.right
@@ -305,13 +307,13 @@ export function record(codomain: Decoder): Decoder> {
const errors: Array> = []
for (const k in r) {
const e = codomain.decode(r[k])
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
errors.push(tree(`key ${JSON.stringify(k)}`, e.left))
} else {
a[k] = e.right
}
}
- return isNotEmpty(errors) ? left(errors) : success(a)
+ return isNotEmpty(errors) ? E.left(errors) : success(a)
}
}
}
@@ -325,7 +327,7 @@ export function array(items: Decoder): Decoder> {
return {
decode: (u) => {
const e = UnknownArray.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
} else {
const us = e.right
@@ -334,13 +336,13 @@ export function array(items: Decoder): Decoder> {
const errors: Array> = []
for (let i = 0; i < len; i++) {
const e = items.decode(us[i])
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
errors.push(tree(`item ${i}`, e.left))
} else {
a[i] = e.right
}
}
- return isNotEmpty(errors) ? left(errors) : success(a)
+ return isNotEmpty(errors) ? E.left(errors) : success(a)
}
}
}
@@ -354,7 +356,7 @@ export function tuple>(...components: { [K in k
return {
decode: (u) => {
const e = UnknownArray.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
}
const us = e.right
@@ -362,13 +364,13 @@ export function tuple>(...components: { [K in k
const errors: Array> = []
for (let i = 0; i < components.length; i++) {
const e = components[i].decode(us[i])
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
errors.push(tree(`component ${i}`, e.left))
} else {
a.push(e.right)
}
}
- return isNotEmpty(errors) ? left(errors) : success(a as any)
+ return isNotEmpty(errors) ? E.left(errors) : success(a as any)
}
}
}
@@ -399,11 +401,11 @@ export function intersection(left: Decoder, right: Decoder): Decoder
return {
decode: (u) => {
const ea = left.decode(u)
- if (isLeft(ea)) {
- return ea
- }
const eb = right.decode(u)
- if (isLeft(eb)) {
+ if (E.isLeft(ea)) {
+ return E.isLeft(eb) ? E.left(ea.left.concat(eb.left) as DecodeError) : ea
+ }
+ if (E.isLeft(eb)) {
return eb
}
return success(intersect(ea.right, eb.right))
@@ -421,7 +423,7 @@ export function lazy(id: string, f: () => Decoder): Decoder {
decode: (u) =>
pipe(
get().decode(u),
- mapLeft((nea) => [tree(id, nea)])
+ E.mapLeft((nea) => [tree(id, nea)])
)
}
}
@@ -440,14 +442,14 @@ export function sum(tag: T): (members: { [K in keyof A]: De
return {
decode: (u) => {
const e = UnknownRecord.decode(u)
- if (isLeft(e)) {
+ if (E.isLeft(e)) {
return e
}
const v = e.right[tag] as keyof A
if (v in members) {
return members[v].decode(u)
}
- return left([
+ return E.left([
tree(`required property ${JSON.stringify(tag)}`, [
tree(`cannot decode ${JSON.stringify(v)}, should be ${expected}`)
])
@@ -471,19 +473,19 @@ export function union>(
return {
decode: (u) => {
const e = members[0].decode(u)
- if (isRight(e)) {
+ if (E.isRight(e)) {
return e
} else {
const errors: DecodeError = [tree(`member 0`, e.left)]
for (let i = 1; i < len; i++) {
const e = members[i].decode(u)
- if (isRight(e)) {
+ if (E.isRight(e)) {
return e
} else {
errors.push(tree(`member ${i}`, e.left))
}
}
- return left(errors)
+ return E.left(errors)
}
}
}
@@ -502,7 +504,7 @@ export const map: (f: (a: A) => B) => (fa: Decoder) => Decoder = (f)
const map_: (fa: Decoder, f: (a: A) => B) => Decoder = (fa, f) => ({
decode: (u) => {
const e = fa.decode(u)
- return isLeft(e) ? e : right(f(e.right))
+ return E.isLeft(e) ? e : E.right(f(e.right))
}
})
@@ -515,7 +517,7 @@ export const alt: (that: () => Decoder) => (fa: Decoder) => Decoder
const alt_: (fx: Decoder, fy: () => Decoder) => Decoder = (fx, fy) => ({
decode: (u) => {
const e = fx.decode(u)
- return isLeft(e) ? fy().decode(u) : e
+ return E.isLeft(e) ? fy().decode(u) : e
}
})
diff --git a/test/Codec.ts b/test/Codec.ts
index c9c30e14e..7b3809e56 100644
--- a/test/Codec.ts
+++ b/test/Codec.ts
@@ -501,18 +501,6 @@ describe('Codec', () => {
const codec = C.intersection(Int, Positive)
assert.deepStrictEqual(codec.decode(1), right(1))
})
-
- it('should reject an invalid input', () => {
- const codec = C.intersection(C.type({ a: C.string }), C.type({ b: C.number }))
- assert.deepStrictEqual(
- codec.decode({ a: 'a' }),
- left([D.tree('required property "b"', [D.tree('cannot decode undefined, should be number')])])
- )
- assert.deepStrictEqual(
- codec.decode({ b: 1 }),
- left([D.tree('required property "a"', [D.tree('cannot decode undefined, should be string')])])
- )
- })
})
describe('encode', () => {
diff --git a/test/Decoder.ts b/test/Decoder.ts
index a04636b3b..81f762e74 100644
--- a/test/Decoder.ts
+++ b/test/Decoder.ts
@@ -70,6 +70,27 @@ describe('Decoder', () => {
})
})
+ describe('intersection', () => {
+ it('should accumulate all errors', () => {
+ const decoder = D.intersection(D.type({ a: D.string }), D.type({ b: D.number }))
+ assert.deepStrictEqual(
+ decoder.decode({}),
+ E.left([
+ D.tree('required property "a"', [D.tree('cannot decode undefined, should be string')]),
+ D.tree('required property "b"', [D.tree('cannot decode undefined, should be number')])
+ ])
+ )
+ assert.deepStrictEqual(
+ decoder.decode({ b: 1 }),
+ E.left([D.tree('required property "a"', [D.tree('cannot decode undefined, should be string')])])
+ )
+ assert.deepStrictEqual(
+ decoder.decode({ a: 'a' }),
+ E.left([D.tree('required property "b"', [D.tree('cannot decode undefined, should be number')])])
+ )
+ })
+ })
+
describe('intersect', () => {
it('should concat strings', () => {
assert.deepStrictEqual(D.intersect('a', 'b'), 'b')