Skip to content

Commit

Permalink
feat: Improve either error messages
Browse files Browse the repository at this point in the history
Fix #9
  • Loading branch information
charlespascoe committed Jan 20, 2018
1 parent 2df6590 commit 3a10582
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 18 deletions.
58 changes: 42 additions & 16 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { keysOf, tryCatch, increaseIndent } from './utils';
import { keysOf, tryCatch } from './utils';
import {
ArrayIndexPathNode,
error,
Expand All @@ -9,6 +9,7 @@ import {
success,
ValidationError,
ValidationResult,
EitherValidationError
} from './validation-result';
export * from './validation-result';

Expand Down Expand Up @@ -341,29 +342,54 @@ export function eachValue<T>(assertion: (arg: any) => ValidationResult<T>, next?
}


export function either<A,B>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>): (arg: any) => ValidationResult<A | B>;
export function either<A,B,C>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>): (arg: any) => ValidationResult<A | B | C>;
export function either<A,B,C,D>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>): (arg: any) => ValidationResult<A | B | C | D>;
export function either<A,B,C,D,E>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>, assertion5: (arg: any) => ValidationResult<E>): (arg: any) => ValidationResult<A | B | C | D | E>;
export function either<A,B,C,D,E,F>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>, assertion5: (arg: any) => ValidationResult<E>, assertion6: (arg: any) => ValidationResult<F>): (arg: any) => ValidationResult<A | B | C | D | E | F>;
export function either<A,B,C,D,E,F,G>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>, assertion5: (arg: any) => ValidationResult<E>, assertion6: (arg: any) => ValidationResult<F>, assertion7: (arg: any) => ValidationResult<G>): (arg: any) => ValidationResult<A | B | C | D | E | F | G>;
export function either<A,B,C,D,E,F,G,H>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>, assertion5: (arg: any) => ValidationResult<E>, assertion6: (arg: any) => ValidationResult<F>, assertion7: (arg: any) => ValidationResult<G>, assertion8: (arg: any) => ValidationResult<H>): (arg: any) => ValidationResult<A | B | C | D | E | F | G | H>;
export function either<A,B,C,D,E,F,G,H,I>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>, assertion5: (arg: any) => ValidationResult<E>, assertion6: (arg: any) => ValidationResult<F>, assertion7: (arg: any) => ValidationResult<G>, assertion8: (arg: any) => ValidationResult<H>, assertion9: (arg: any) => ValidationResult<I>): (arg: any) => ValidationResult<A | B | C | D | E | F | G | H | I>;
export function either<A,B,C,D,E,F,G,H,I,J>(assertion1: (arg: any) => ValidationResult<A>, assertion2: (arg: any) => ValidationResult<B>, assertion3: (arg: any) => ValidationResult<C>, assertion4: (arg: any) => ValidationResult<D>, assertion5: (arg: any) => ValidationResult<E>, assertion6: (arg: any) => ValidationResult<F>, assertion7: (arg: any) => ValidationResult<G>, assertion8: (arg: any) => ValidationResult<H>, assertion9: (arg: any) => ValidationResult<I>, assertion10: (arg: any) => ValidationResult<J>): (arg: any) => ValidationResult<A | B | C | D | E | F | G | H | I | J>;
export function either(...assertions: Array<(arg: any) => any>): (arg: any) => any {
export interface IEitherOption<T> {
description: string;
assertion: (arg: any) => ValidationResult<T>;
}


export function is<T>(description: string, assertion: (arg: any) => ValidationResult<T>): IEitherOption<T> {
return {description, assertion};
}


// These overloads are necessary for type safety
export function either<A,B>(option1: IEitherOption<A>, option2: IEitherOption<B>): (arg: any) => ValidationResult<A|B>;
export function either<A,B,C>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>): (arg: any) => ValidationResult<A|B|C>;
export function either<A,B,C,D>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>): (arg: any) => ValidationResult<A|B|C|D>;
export function either<A,B,C,D,E>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>): (arg: any) => ValidationResult<A|B|C|D|E>;
export function either<A,B,C,D,E,F>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>): (arg: any) => ValidationResult<A|B|C|D|E|F>;
export function either<A,B,C,D,E,F,G>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>): (arg: any) => ValidationResult<A|B|C|D|E|F|G>;
export function either<A,B,C,D,E,F,G,H>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H>;
export function either<A,B,C,D,E,F,G,H,I>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I>;
export function either<A,B,C,D,E,F,G,H,I,J>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J>;
export function either<A,B,C,D,E,F,G,H,I,J,K>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N,O>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>, option15: IEitherOption<O>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N|O>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>, option15: IEitherOption<O>, option16: IEitherOption<P>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>, option15: IEitherOption<O>, option16: IEitherOption<P>, option17: IEitherOption<Q>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>, option15: IEitherOption<O>, option16: IEitherOption<P>, option17: IEitherOption<Q>, option18: IEitherOption<R>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>, option15: IEitherOption<O>, option16: IEitherOption<P>, option17: IEitherOption<Q>, option18: IEitherOption<R>, option19: IEitherOption<S>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S>;
export function either<A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T>(option1: IEitherOption<A>, option2: IEitherOption<B>, option3: IEitherOption<C>, option4: IEitherOption<D>, option5: IEitherOption<E>, option6: IEitherOption<F>, option7: IEitherOption<G>, option8: IEitherOption<H>, option9: IEitherOption<I>, option10: IEitherOption<J>, option11: IEitherOption<K>, option12: IEitherOption<L>, option13: IEitherOption<M>, option14: IEitherOption<N>, option15: IEitherOption<O>, option16: IEitherOption<P>, option17: IEitherOption<Q>, option18: IEitherOption<R>, option19: IEitherOption<S>, option20: IEitherOption<T>): (arg: any) => ValidationResult<A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T>;
export function either(...options: Array<IEitherOption<any>>): (arg: any) => any {
return (arg: any) => {
let errors: ValidationError[] = [];
const eitherError = new EitherValidationError();

for (const assertion of assertions) {
const result = assertion(arg);
for (const option of options) {
const result = tryCatch(
() => option.assertion(arg),
(err) => errorFromException(err)
);

if (result.success) {
return result;
}

errors = errors.concat(result.errors);
eitherError.errors[option.description] = result.errors;
}

return error('NO_MATCH', 'No match found - the following assertions failed:\n' + errors.map(error => increaseIndent(error.toString(), 2)).join('\n'));
return new ErrorResult(eitherError);
};
}
5 changes: 5 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ export function increaseIndent(text: string, indent: number): string {
const indentPadding = repeat(' ', indent);
return indentPadding + text.split('\n').join('\n' + indentPadding);
}


export function pluralise(count: number, singular: string, plural: string): string {
return `${count} ${count === 1 ? singular : plural}`;
}
28 changes: 26 additions & 2 deletions lib/validation-result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { increaseIndent } from './utils';
import { increaseIndent, keysOf, pluralise } from './utils';


export type ValidationResult<T> = SuccessResult<T> | ErrorResult;
Expand Down Expand Up @@ -56,6 +56,30 @@ export class ValidationError {
}


export class EitherValidationError extends ValidationError {
public readonly errors: {[description: string]: ValidationError[]} = {};

constructor() {
super('NO_MATCH', 'No match found');
}

public toString(root: string = '$'): string {
return `${this.pathString(root)}: ${this.message} - the following assertions failed:\n` +
keysOf(this.errors)
.map(desc => {
const errors = this.errors[desc];

return increaseIndent(
`Not ${desc}, due to ${pluralise(errors.length, 'validation error', 'validation errors')}:\n` +
errors.map(error => increaseIndent(error.toString(), 4)).join('\n'),
4
);
})
.join('\n');
}
}


export class ErrorResult {
public readonly success: false = false;

Expand All @@ -78,7 +102,7 @@ export class ErrorResult {
}

public toString(root: string = '$'): string {
return `${this.errors.length} validation error${this.errors.length === 1 ? '' : 's'}:\n${this.errors.map(error => increaseIndent(error.toString(root), 2)).join('\n')}`;
return `${this.errors.length} validation error${this.errors.length === 1 ? '' : 's'}:\n${this.errors.map(error => increaseIndent(error.toString(root), 4)).join('\n')}`;
}

public static isErrorResult(arg: any): arg is ErrorResult {
Expand Down

0 comments on commit 3a10582

Please sign in to comment.