forked from runtypes/runtypes
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
36 changed files
with
1,303 additions
and
898 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,56 @@ | ||
import { AsyncContract, Number } from '.'; | ||
import { ValidationError } from './errors'; | ||
|
||
describe('AsyncContract', () => { | ||
describe('when function does not return a promise', () => { | ||
it('throws a validation error', () => { | ||
const contractedFunction = AsyncContract(Number).enforce(() => 7 as any); | ||
expect(contractedFunction).toThrow(ValidationError); | ||
it('throws a validation error', async () => { | ||
const contractedFunction = AsyncContract([], Number).enforce(() => 7 as any); | ||
await expect(contractedFunction()).rejects.toMatchInlineSnapshot( | ||
`[ValidationError: Expected function to return a promise, but instead got 7]`, | ||
); | ||
}); | ||
}); | ||
describe('when a function does return a promise, but for the wrong type', () => { | ||
it('throws a validation error asynchronously', async () => { | ||
const contractedFunction = AsyncContract(Number).enforce(() => Promise.resolve('hi' as any)); | ||
try { | ||
await contractedFunction(); | ||
fail(); | ||
} catch (e) { | ||
expect(e).toBeInstanceOf(ValidationError); | ||
} | ||
const contractedFunction = AsyncContract([], Number).enforce(() => | ||
Promise.resolve('hi' as any), | ||
); | ||
await expect(contractedFunction()).rejects.toMatchInlineSnapshot( | ||
`[ValidationError: Expected number, but was string]`, | ||
); | ||
}); | ||
}); | ||
describe('when a function does return a promise', () => { | ||
describe('when a function does return a promise, for the correct type', () => { | ||
it('should validate successfully', async () => { | ||
const contractedFunction = AsyncContract(Number).enforce(() => Promise.resolve(7)); | ||
const contractedFunction = AsyncContract([], Number).enforce(() => Promise.resolve(7)); | ||
await expect(contractedFunction()).resolves.toBe(7); | ||
}); | ||
}); | ||
describe('when not enough arguments are provided', () => { | ||
it('throws a validation error', () => { | ||
const contractedFunction = AsyncContract(Number, Number).enforce(n => Promise.resolve(n + 1)); | ||
expect(contractedFunction).toThrow(ValidationError); | ||
it('throws a validation error', async () => { | ||
const contractedFunction = AsyncContract([Number], Number).enforce(n => | ||
Promise.resolve(n + 1), | ||
); | ||
await expect((contractedFunction as any)()).rejects.toMatchInlineSnapshot( | ||
`[ValidationError: Expected 1 arguments but only received 0]`, | ||
); | ||
}); | ||
}); | ||
describe('when arguments are of the wrong type', () => { | ||
it('throws a validation error', async () => { | ||
const contractedFunction = AsyncContract([Number], Number).enforce(n => | ||
Promise.resolve(n + 1), | ||
); | ||
await expect(contractedFunction('whatever' as any)).rejects.toMatchInlineSnapshot( | ||
`[ValidationError: Expected number, but was string]`, | ||
); | ||
}); | ||
}); | ||
describe('when arguments are valid', () => { | ||
it('throws a validation error', async () => { | ||
const contractedFunction = AsyncContract([Number], Number).enforce(n => | ||
Promise.resolve(n + 1), | ||
); | ||
await expect(contractedFunction(41)).resolves.toEqual(42); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,165 +1,50 @@ | ||
import { Runtype } from './index'; | ||
import { ValidationError } from './errors'; | ||
import { RuntypeBase } from './runtype'; | ||
|
||
export interface AsyncContract0<Z> { | ||
enforce(f: () => Promise<Z>): () => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract1<A, Z> { | ||
enforce(f: (a: A) => Promise<Z>): (a: A) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract2<A, B, Z> { | ||
enforce(f: (a: A, b: B) => Promise<Z>): (a: A, b: B) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract3<A, B, C, Z> { | ||
enforce(f: (a: A, b: B, c: C) => Promise<Z>): (a: A, b: B, c: C) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract4<A, B, C, D, Z> { | ||
enforce(f: (a: A, b: B, c: C, d: D) => Promise<Z>): (a: A, b: B, c: C, d: D) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract5<A, B, C, D, E, Z> { | ||
enforce( | ||
f: (a: A, b: B, c: C, d: D, e: E) => Promise<Z>, | ||
): (a: A, b: B, c: C, d: D, e: E) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract6<A, B, C, D, E, F, Z> { | ||
enforce( | ||
f: (a: A, b: B, c: C, d: D, e: E, f: F) => Promise<Z>, | ||
): (a: A, b: B, c: C, d: D, e: E, f: F) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract7<A, B, C, D, E, F, G, Z> { | ||
enforce( | ||
f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => Promise<Z>, | ||
): (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract8<A, B, C, D, E, F, G, H, Z> { | ||
enforce( | ||
f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => Promise<Z>, | ||
): (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract9<A, B, C, D, E, F, G, H, I, Z> { | ||
enforce( | ||
f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => Promise<Z>, | ||
): (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => Promise<Z>; | ||
} | ||
|
||
export interface AsyncContract10<A, B, C, D, E, F, G, H, I, J, Z> { | ||
enforce( | ||
f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => Promise<Z>, | ||
): (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => Promise<Z>; | ||
export interface AsyncContract<A extends any[], Z> { | ||
enforce(f: (...a: A) => Promise<Z>): (...a: A) => Promise<Z>; | ||
} | ||
|
||
/** | ||
* Create a function contract. | ||
*/ | ||
export function AsyncContract<Z>(Z: Runtype<Z>): AsyncContract0<Z>; | ||
export function AsyncContract<A, Z>(A: Runtype<A>, Z: Runtype<Z>): AsyncContract1<A, Z>; | ||
export function AsyncContract<A, B, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
Z: Runtype<Z>, | ||
): AsyncContract2<A, B, Z>; | ||
export function AsyncContract<A, B, C, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
Z: Runtype<Z>, | ||
): AsyncContract3<A, B, C, Z>; | ||
export function AsyncContract<A, B, C, D, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
Z: Runtype<Z>, | ||
): AsyncContract4<A, B, C, D, Z>; | ||
export function AsyncContract<A, B, C, D, E, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
E: Runtype<E>, | ||
Z: Runtype<Z>, | ||
): AsyncContract5<A, B, C, D, E, Z>; | ||
export function AsyncContract<A, B, C, D, E, F, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
E: Runtype<E>, | ||
F: Runtype<F>, | ||
Z: Runtype<Z>, | ||
): AsyncContract6<A, B, C, D, E, F, Z>; | ||
export function AsyncContract<A, B, C, D, E, F, G, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
E: Runtype<E>, | ||
F: Runtype<F>, | ||
G: Runtype<G>, | ||
Z: Runtype<Z>, | ||
): AsyncContract7<A, B, C, D, E, F, G, Z>; | ||
export function AsyncContract<A, B, C, D, E, F, G, H, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
E: Runtype<E>, | ||
F: Runtype<F>, | ||
G: Runtype<G>, | ||
H: Runtype<H>, | ||
Z: Runtype<Z>, | ||
): AsyncContract8<A, B, C, D, E, F, G, H, Z>; | ||
export function AsyncContract<A, B, C, D, E, F, G, H, I, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
E: Runtype<E>, | ||
F: Runtype<F>, | ||
G: Runtype<G>, | ||
H: Runtype<H>, | ||
I: Runtype<I>, | ||
Z: Runtype<Z>, | ||
): AsyncContract9<A, B, C, D, E, F, G, H, I, Z>; | ||
export function AsyncContract<A, B, C, D, E, F, G, H, I, J, Z>( | ||
A: Runtype<A>, | ||
B: Runtype<B>, | ||
C: Runtype<C>, | ||
D: Runtype<D>, | ||
E: Runtype<E>, | ||
F: Runtype<F>, | ||
G: Runtype<G>, | ||
H: Runtype<H>, | ||
I: Runtype<I>, | ||
J: Runtype<J>, | ||
Z: Runtype<Z>, | ||
): AsyncContract10<A, B, C, D, E, F, G, H, I, J, Z>; | ||
export function AsyncContract(...runtypes: Runtype[]) { | ||
const lastIndex = runtypes.length - 1; | ||
const argTypes = runtypes.slice(0, lastIndex); | ||
const returnType = runtypes[lastIndex]; | ||
export function AsyncContract<A extends [any, ...any[]] | [], Z>( | ||
argTypes: { [key in keyof A]: key extends 'length' ? A['length'] : RuntypeBase<A[key]> }, | ||
returnType: RuntypeBase<Z>, | ||
): AsyncContract<A, Z> { | ||
return { | ||
enforce: (f: (...args: any[]) => any) => (...args: any[]) => { | ||
if (args.length < argTypes.length) | ||
throw new ValidationError( | ||
`Expected ${argTypes.length} arguments but only received ${args.length}`, | ||
if (args.length < argTypes.length) { | ||
return Promise.reject( | ||
new ValidationError( | ||
`Expected ${argTypes.length} arguments but only received ${args.length}`, | ||
), | ||
); | ||
for (let i = 0; i < argTypes.length; i++) argTypes[i].check(args[i]); | ||
} | ||
for (let i = 0; i < argTypes.length; i++) { | ||
const result = argTypes[i].validate(args[i]); | ||
if (result.success) { | ||
argTypes[i] = result.value; | ||
} else { | ||
return Promise.reject(new ValidationError(result.message, result.key)); | ||
} | ||
} | ||
const returnedPromise = f(...args); | ||
if (!(returnedPromise instanceof Promise)) | ||
throw new ValidationError( | ||
`Expected function to return a promise, but instead got ${returnedPromise}`, | ||
if (!(returnedPromise instanceof Promise)) { | ||
return Promise.reject( | ||
new ValidationError( | ||
`Expected function to return a promise, but instead got ${returnedPromise}`, | ||
), | ||
); | ||
return returnedPromise.then(returnType.check); | ||
} | ||
return returnedPromise.then(value => { | ||
const result = returnType.validate(value); | ||
if (result.success) { | ||
return result.value; | ||
} else { | ||
throw new ValidationError(result.message, result.key); | ||
} | ||
}); | ||
}, | ||
}; | ||
} |
Oops, something went wrong.