Skip to content

Commit

Permalink
feat: add intersection contract
Browse files Browse the repository at this point in the history
  • Loading branch information
bigslycat committed Dec 2, 2020
1 parent e870ce8 commit 0330e84
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 0 deletions.
53 changes: 53 additions & 0 deletions __tests__/contracts/intersection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* @flow */

import { intersection } from '../../src/contracts/intersection';
import { ValidationError } from '../../src/ValidationError';

const createError = (...types: $ReadOnlyArray<string>) => (
name: string,
value: mixed,
) => new ValidationError(name, value, types);

describe('intersection', () => {
describe('Creates new Contract for one or more other contracts or validation functions', () => {
it('Return ValidationError if at least one Contract returns ValidationError', () => {
const validate1 = jest.fn().mockReturnValue({ a: 1, b: 2 });
const validate2 = jest.fn().mockReturnValue({ a: 1, b: 2 });
const validate3 = jest.fn(createError('type3'));

const result = intersection(
validate1,
validate2,
validate3,
)('valueName', { a: 1, b: 2 });

expect(result).toBeInstanceOf(ValidationError);
expect(result.expectedTypes).toEqual(['Intersection']);
expect(result.nested).toEqual([
new ValidationError('valueName', { a: 1, b: 2 }, 'type3'),
]);

expect(validate1).lastCalledWith('valueName', { a: 1, b: 2 });
expect(validate2).lastCalledWith('valueName', { a: 1, b: 2 });
expect(validate3).lastCalledWith('valueName', { a: 1, b: 2 });
});

it('Return value in other cases', () => {
const validate1 = jest.fn().mockReturnValue({ a: 1, b: 2 });
const validate2 = jest.fn().mockReturnValue({ a: 1, b: 2 });
const validate3 = jest.fn().mockReturnValue({ a: 1, b: 2 });

expect(
intersection(
validate1,
validate2,
validate3,
)('valueName', { a: 1, b: 2 }),
).toEqual({ a: 1, b: 2 });

expect(validate1).lastCalledWith('valueName', { a: 1, b: 2 });
expect(validate2).lastCalledWith('valueName', { a: 1, b: 2 });
expect(validate3).lastCalledWith('valueName', { a: 1, b: 2 });
});
});
});
1 change: 1 addition & 0 deletions src/contracts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

export * from './array';
export * from './boolean';
export * from './intersection';
export * from './literal';
export * from './null';
export * from './number';
Expand Down
94 changes: 94 additions & 0 deletions src/contracts/intersection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* @flow */

import { ValidationError } from '../ValidationError';
import * as contract from '../Contract';

export class IntersectionError extends ValidationError {
constructor(
valueName: string,
value: mixed,
errors: $ReadOnlyArray<ValidationError>,
) {
super(valueName, value, 'Intersection', errors);
this.name = 'IntersectionError';
}
}

declare export function intersection<A>(
a: (name: string, value: mixed) => ValidationError | A,
): contract.Contract<A>;

declare export function intersection<A, B>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
): contract.Contract<A & B>;

declare export function intersection<A, B, C>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
c: (name: string, value: mixed) => ValidationError | C,
): contract.Contract<A & B & C>;

declare export function intersection<A, B, C, D>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
c: (name: string, value: mixed) => ValidationError | C,
d: (name: string, value: mixed) => ValidationError | D,
): contract.Contract<A & B & C & D>;

declare export function intersection<A, B, C, D, E>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
c: (name: string, value: mixed) => ValidationError | C,
d: (name: string, value: mixed) => ValidationError | D,
e: (name: string, value: mixed) => ValidationError | E,
): contract.Contract<A & B & C & D & E>;

declare export function intersection<A, B, C, D, E, F>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
c: (name: string, value: mixed) => ValidationError | C,
d: (name: string, value: mixed) => ValidationError | D,
e: (name: string, value: mixed) => ValidationError | E,
f: (name: string, value: mixed) => ValidationError | F,
): contract.Contract<A & B & C & D & E & F>;

declare export function intersection<A, B, C, D, E, F, G>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
c: (name: string, value: mixed) => ValidationError | C,
d: (name: string, value: mixed) => ValidationError | D,
e: (name: string, value: mixed) => ValidationError | E,
f: (name: string, value: mixed) => ValidationError | F,
g: (name: string, value: mixed) => ValidationError | G,
): contract.Contract<A & B & C & D & E & F & G>;

declare export function intersection<A, B, C, D, E, F, G, H>(
a: (name: string, value: mixed) => ValidationError | A,
b: (name: string, value: mixed) => ValidationError | B,
c: (name: string, value: mixed) => ValidationError | C,
d: (name: string, value: mixed) => ValidationError | D,
e: (name: string, value: mixed) => ValidationError | E,
f: (name: string, value: mixed) => ValidationError | F,
g: (name: string, value: mixed) => ValidationError | G,
h: (name: string, value: mixed) => ValidationError | H,
): contract.Contract<A & B & C & D & E & F & G & H>;

export function intersection<T>(
...rules: $ReadOnlyArray<(name: string, value: mixed) => ValidationError | T>
): contract.Contract<T> {
const validators = rules;

const intersectionContract = (name, value: any): ValidationError | T => {
const errors = validators
.map(validate => validate(name, value))
.reduce((scope, error) => {
if (error instanceof ValidationError) scope.push(error);
return scope;
}, []);

return errors.length ? new IntersectionError(name, value, errors) : value;
};

return contract.of(intersectionContract);
}
60 changes: 60 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,66 @@ export declare var str: typeof string;
export declare var isStr: typeof string;
export declare var passStr: typeof string;

export declare function intersection<A>(
a: Validator<A>,
): Contract<A>;

export declare function intersection<A, B>(
a: Validator<A>,
b: Validator<B>,
): Contract<A & B>;

export declare function intersection<A, B, C>(
a: Validator<A>,
b: Validator<B>,
c: Validator<C>,
): Contract<A & B & C>;

export declare function intersection<A, B, C, D>(
a: Validator<A>,
b: Validator<B>,
c: Validator<C>,
d: Validator<D>,
): Contract<A & B & C & D>;

export declare function intersection<A, B, C, D, E>(
a: Validator<A>,
b: Validator<B>,
c: Validator<C>,
d: Validator<D>,
e: Validator<E>,
): Contract<A & B & C & D & E>;

export declare function intersection<A, B, C, D, E, F>(
a: Validator<A>,
b: Validator<B>,
c: Validator<C>,
d: Validator<D>,
e: Validator<E>,
f: Validator<F>,
): Contract<A & B & C & D & E & F>;

export declare function intersection<A, B, C, D, E, F, G>(
a: Validator<A>,
b: Validator<B>,
c: Validator<C>,
d: Validator<D>,
e: Validator<E>,
f: Validator<F>,
g: Validator<G>,
): Contract<A & B & C & D & E & F & G>;

export declare function intersection<A, B, C, D, E, F, G, H>(
a: Validator<A>,
b: Validator<B>,
c: Validator<C>,
d: Validator<D>,
e: Validator<E>,
f: Validator<F>,
g: Validator<G>,
h: Validator<H>,
): Contract<A & B & C & D & E & F & G & H>;

export declare function union<T extends Array<Validator<any> | string | number | boolean>>(
...rules: T
): Contract<
Expand Down

0 comments on commit 0330e84

Please sign in to comment.