diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ae58cdc..22df2407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +v1.8.2 +------ +**Improved error reporting:** + +- Fix bug where empty error branches could be shown in complex either + expressions (fixes #83) + + v1.8.1 ------ - Fix: revert accidentally emitting $ReadOnlyArray types in array decoders diff --git a/src/__tests__/either.test.js b/src/__tests__/either.test.js index a19c100f..ac223616 100644 --- a/src/__tests__/either.test.js +++ b/src/__tests__/either.test.js @@ -7,29 +7,56 @@ import { constant, undefined_ } from '../constants'; import { either, either4, either9 } from '../either'; import { guard } from '../guard'; import { number } from '../number'; -import { string } from '../string'; +import { object } from '../object'; +import { regex, string } from '../string'; import { INPUTS } from './fixtures'; describe('either', () => { - const decoder = guard(either(string, boolean)); + const stringOrBooleanDecoder = guard(either(string, boolean)); const [okay, not_okay] = partition(INPUTS, x => typeof x === 'string' || typeof x === 'boolean'); it('valid', () => { expect(okay.length).not.toBe(0); for (const value of okay) { - expect(decoder(value)).toBe(value); + expect(stringOrBooleanDecoder(value)).toBe(value); } }); it('invalid', () => { expect(not_okay.length).not.toBe(0); for (const value of not_okay) { - expect(() => decoder(value)).toThrow(); + expect(() => stringOrBooleanDecoder(value)).toThrow(); } }); - it('errors nicely', () => { - expect(() => decoder(42)).toThrow('Either:'); + it('errors nicely in trivial eithers', () => { + expect(() => stringOrBooleanDecoder(42)).toThrow('Either:'); + }); + + it('errors nicely in common, simple eithers (ie optional)', () => { + // Either undefined or string + const g1 = guard(either(undefined_, string)); + expect(() => g1(42)).toThrow('Either:'); + expect(() => g1({})).toThrow('Either:'); + + // Either undefined or object + const g2 = guard(either(undefined_, object({ name: string }))); + expect(() => g2(42)).toThrow('Either:'); + expect(() => g2({ name: 42 })).toThrow('Either'); + + const g3 = guard(either(regex(/1/, 'Must contain 1'), regex(/2/, 'Must contain 2'))); + expect(() => g3(42)).toThrow('Either'); + expect(() => g3('foobar')).toThrow('Either'); + }); + + it.skip('errors nicely in complex eithers (with two wildly different branches)', () => { + const g = guard(either(object({ foo: string }), object({ bar: number }))); + expect(() => + g({ + foo: 123, + bar: 'not a number', + }) + ).toThrow('XXX FIXME - this Either: error looks horrendous'); }); }); diff --git a/src/either.js b/src/either.js index d91c928e..4bc2a6c6 100644 --- a/src/either.js +++ b/src/either.js @@ -1,6 +1,7 @@ // @flow strict import { annotate, indent } from 'debrief'; +import { summarize } from 'debrief'; import { Err, Ok } from 'lemons'; import type { Decoder, anything } from './types'; @@ -21,7 +22,16 @@ export function either(d1: Decoder, d2: Decoder): Decoder Ok(value2), err2 => - Err(annotate(blob, ['Either:', itemize(err1.annotation), itemize(err2.annotation)].join('\n'))) + Err( + annotate( + blob, + [ + 'Either:', + itemize(summarize(err1).join('\n')), + itemize(summarize(err2).join('\n')), + ].join('\n') + ) + ) ) ); }