From c90949b17e89c2d5875b90406ea983a361703ce2 Mon Sep 17 00:00:00 2001 From: gcanti Date: Sun, 15 Dec 2019 18:48:02 +0100 Subject: [PATCH] fix #397 --- CHANGELOG.md | 5 +++++ package.json | 2 +- src/index.ts | 31 +++++++++++++++++----------- test/intersection.ts | 48 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 905243e9..927df20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ **Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice. +# 2.0.2 + +- **Bug Fix** + - fix #397 (@gcanti) + # 2.0.1 - **Bug Fix** diff --git a/package.json b/package.json index 60aaec01..f5bcafa8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "io-ts", - "version": "2.0.1", + "version": "2.0.2", "description": "TypeScript compatible runtime type system for IO validation", "files": [ "lib", diff --git a/src/index.ts b/src/index.ts index 93b099fe..1f711082 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1191,19 +1191,26 @@ export interface IntersectionC]> > {} const mergeAll = (base: any, us: Array): any => { - let r: any = base - for (let i = 0; i < us.length; i++) { - const u = us[i] + let equal = true + let primitive = true + for (const u of us) { if (u !== base) { - // `u` contains a prismatic value or is the result of a stripping combinator - if (r === base) { - r = Object.assign({}, u) - continue - } - for (const k in u) { - if (u[k] !== base[k] || !r.hasOwnProperty(k)) { - r[k] = u[k] - } + equal = false + } + if (isObject(u)) { + primitive = false + } + } + if (equal) { + return base + } else if (primitive) { + return us[us.length - 1] + } + let r: any = {} + for (const u of us) { + for (const k in u) { + if (u[k] !== base[k] || !r.hasOwnProperty(k)) { + r[k] = u[k] } } } diff --git a/test/intersection.ts b/test/intersection.ts index 9246aa67..014c45eb 100644 --- a/test/intersection.ts +++ b/test/intersection.ts @@ -87,15 +87,32 @@ describe('intersection', () => { assertStrictEqual(T.decode(value), value) }) - it('should play well with stripping combinators', () => { - const A = t.exact(t.type({ a: t.string })) - const B = t.exact(t.type({ b: t.number })) - const T = t.intersection([A, B]) - assertSuccess(T.decode({ a: 'a', b: 1 })) - assertSuccess(T.decode({ a: 'a', b: 1, c: true }), { a: 'a', b: 1 }) - assertFailure(T, { a: 'a' }, [ + it('should play well with exact', () => { + const T1 = t.intersection([t.exact(t.type({ a: t.string })), t.exact(t.type({ b: t.number }))]) + assertSuccess(T1.decode({ a: 'a', b: 1 }), { a: 'a', b: 1 }) + assertSuccess(T1.decode({ a: 'a', b: 1, c: true }), { a: 'a', b: 1 }) + assertFailure(T1, { a: 'a' }, [ 'Invalid value undefined supplied to : ({| a: string |} & {| b: number |})/1: {| b: number |}/b: number' ]) + + const T2 = t.intersection([t.exact(t.type({})), t.partial({ a: t.number })]) + assertSuccess(T2.decode({}), {}) + assertSuccess(T2.decode({ a: 1 }), { a: 1 }) + assertSuccess(T2.decode({ a: undefined }), { a: undefined }) + assertSuccess(T2.decode({ a: 1, b: true }), { a: 1, b: true } as any) + + // #397 + const T3 = t.intersection([t.exact(t.type({})), t.exact(t.partial({ a: t.number }))]) + assertSuccess(T3.decode({}), {}) + assertSuccess(T3.decode({ a: 1 }), { a: 1 }) + assertSuccess(T3.decode({ a: undefined }), { a: undefined }) + assertSuccess(T3.decode({ a: 1, b: true }), { a: 1 }) + + const T4 = t.intersection([t.type({ b: t.string }), t.exact(t.partial({ a: t.number }))]) + assertSuccess(T4.decode({ b: 'b' }), { b: 'b' }) + assertSuccess(T4.decode({ b: 'b', a: 1 }), { b: 'b', a: 1 }) + assertSuccess(T4.decode({ b: 'b', a: undefined }), { b: 'b', a: undefined }) + assertSuccess(T4.decode({ b: 'b', a: 1, c: 2 }), { b: 'b', a: 1, c: 2 } as any) }) }) @@ -149,8 +166,23 @@ describe('intersection', () => { const T = t.intersection([t.string] as any) assert.strictEqual(T.is('a'), true) assert.strictEqual(T.is(1), false) - assertSuccess(T.decode('a')) + assertSuccess(T.decode('a'), 'a') assertFailure(T, 1, ['Invalid value 1 supplied to : (string)/0: string']) assert.strictEqual(T.encode('a'), 'a') }) + + it('should handle primitives', () => { + const T1 = t.intersection([t.string, t.string]) + assert.strictEqual(T1.is('a'), true) + assert.strictEqual(T1.is(1), false) + assertSuccess(T1.decode('a'), 'a') + assertFailure(T1, 1, [ + 'Invalid value 1 supplied to : (string & string)/0: string', + 'Invalid value 1 supplied to : (string & string)/1: string' + ]) + assert.strictEqual(T1.encode('a'), 'a') + const T2 = t.intersection([NumberFromString, NumberFromString]) + assertSuccess(T2.decode('1'), 1) + assert.strictEqual(T2.encode(1), '1') + }) })