Created
November 30, 2017 08:49
-
-
Save sledorze/a4ffa850c1ab71314b4fd0d3d480304e to your computer and use it in GitHub Desktop.
testcheck Generator generation from io-ts definition (pre 0.9.0)
This file contains hidden or 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
import * as tc from 'testcheck' | |
import * as t from 'io-ts' | |
/** | |
* This file provides an API to derive a testcheck Generator from an io-ts definition. | |
* | |
* Supported combinators: | |
* - interface | |
* - string | |
* - number | |
* - boolean | |
* - literal | |
* - union | |
* - intersection (of recursive unions/intersections of interfaces/partials) | |
* - partial | |
* - array | |
* - recursion | |
* | |
* Experimental combinators: | |
* - refinement | |
* - dictionary | |
* | |
*/ | |
export type ObjectOfTTypes = { | |
[key: string]: TType | |
} | |
export interface TTypeArrayType<X extends TType> extends t.ArrayType<X> {} | |
export interface TTypeDictionaryType<D extends t.StringType> extends t.DictionaryType<D, TType> {} | |
export type TTypes = TType[] | |
export interface ITTypes extends TTypes {} | |
export interface RefinedTType extends t.RefinementType<t.TypeOf<TType>> {} | |
export interface RecursiveTType extends t.RecursiveType<t.TypeOf<TType>> {} | |
export type TObjTypes = TObjType[] | |
export interface ITObjTypes extends TObjTypes {} | |
export interface TObjTypeDictionaryType<D extends t.StringType> | |
extends t.DictionaryType<D, TObjType> {} | |
export interface RefinedTObjType extends t.RefinementType<t.TypeOf<TObjType>> {} | |
export interface RecursiveTObjType extends t.RecursiveType<t.TypeOf<TObjType>> {} | |
export type TObjType = | |
| t.InterfaceType<ObjectOfTTypes> | |
| t.UnionType<ITObjTypes, any> | |
| t.IntersectionType<ITObjTypes, any> // only intersection of interfaces supported | |
| t.PartialType<ObjectOfTTypes> | |
| TObjTypeDictionaryType<t.StringType> | |
| RecursiveTObjType | |
| RefinedTObjType | |
export type TType = | |
| t.InterfaceType<ObjectOfTTypes> | |
| t.UnionType<ITTypes, any> | |
| t.IntersectionType<ITObjTypes, any> // only intersection of interfaces supported | |
| t.PartialType<ObjectOfTTypes> | |
| TTypeDictionaryType<t.StringType> | |
| RecursiveTType | |
| RefinedTType | |
| t.LiteralType<any> | |
| TTypeArrayType<any> | |
| t.StringType | |
| t.NumberType | |
| t.BooleanType | |
const genObjectFromProps = <T extends TType>(x: { [k: string]: T & TType }) => ( | |
f: (x: T & TType) => tc.Generator<t.TypeOf<T>> | |
) => { | |
const resGen = {} as { [key: string]: tc.Generator<t.TypeOf<T>> } | |
for (const p in x) { | |
resGen[p] = f(x[p]) | |
} | |
return tc.gen.object(resGen) | |
} | |
export const toGen = <T extends TType>( | |
typeInfo: T & TType, | |
options?: tc.SizeOptions | |
): tc.Generator<t.TypeOf<T>> => { | |
options = options || { maxSize: 4 } | |
const propsGenerator = <T extends TType>(props: { | |
[k: string]: T & TType | |
}): tc.Generator<t.TypeOf<T>> => genObjectFromProps(props)(toGenRec) | |
const partialPropsGenerator = <T extends TType>(props: { | |
[k: string]: T & TType | |
}): tc.Generator<t.TypeOf<T>> => | |
genObjectFromProps(props)(x => tc.gen.oneOf([toGenRec(x), tc.gen.undefined])) | |
const recursiveType = <T extends t.Type<any, any>>(x: t.RecursiveType<T>) => { | |
let generator: () => tc.Generator<t.TypeOf<T>> | undefined = () => { | |
let v = cache.get(x) | |
if (v === undefined) { | |
v = toGenRec(x.type as TType) | |
cache.set(x, v) | |
} | |
generator = () => v | |
return v | |
} | |
return tc.gen.undefined.then(_ => generator()) | |
} | |
const recursiveObjType = <T extends t.Type<any, any>>(x: t.RecursiveType<T>) => { | |
let generator: () => tc.Generator<t.TypeOf<T>> | undefined = () => { | |
let v = cache.get(x) | |
if (v === undefined) { | |
v = toGenObj(x.type as TObjType) | |
cache.set(x, v) | |
} | |
generator = () => v | |
return v | |
} | |
return tc.gen.undefined.then(_ => generator()) | |
} | |
const toGenObj = <T extends TObjType>(typeInfo: T & TObjType): tc.Generator<t.TypeOf<T>> => { | |
switch (typeInfo._tag) { | |
case 'InterfaceType': | |
return propsGenerator(typeInfo.props) | |
case 'UnionType': | |
return tc.gen.oneOf(typeInfo.types.map(toGenObj)) | |
case 'IntersectionType': | |
return typeInfo.types.reduce( | |
(accGen, type) => accGen.then(x => toGenObj(type).then(y => ({ ...x, ...y }))), | |
tc.gen.object({}) | |
) | |
case 'PartialType': | |
return partialPropsGenerator(typeInfo.props) | |
case 'RecursiveType': | |
return recursiveObjType(typeInfo) | |
case 'RefinementType': | |
switch (typeInfo.name) { | |
default: | |
return toGenObj(typeInfo.type).suchThat(typeInfo.predicate) // fallback | |
} | |
case 'DictionaryType': | |
return tc.gen.object(toGenRec(typeInfo.codomain), options) | |
} | |
} | |
const cache = new WeakMap<t.Type<any, any>, tc.Generator<any>>() | |
const toGenRec = <T extends TType>(typeInfo: T & TType): tc.Generator<t.TypeOf<T>> => { | |
switch (typeInfo._tag) { | |
case 'InterfaceType': | |
return propsGenerator(typeInfo.props) | |
case 'StringType': | |
return tc.gen.string | |
case 'NumberType': | |
return tc.gen.number | |
case 'LiteralType': | |
return tc.gen.oneOf([typeInfo.value]) | |
case 'BooleanType': | |
return tc.gen.boolean | |
case 'UnionType': | |
return tc.gen.oneOf(typeInfo.types.map(toGenRec)) | |
case 'IntersectionType': | |
return toGenObj(typeInfo) | |
case 'ArrayType': | |
return tc.gen.array(toGenRec(typeInfo.type), options) | |
case 'PartialType': | |
return partialPropsGenerator(typeInfo.props) | |
case 'RecursiveType': | |
return recursiveType(typeInfo) | |
case 'RefinementType': | |
switch (typeInfo.name) { | |
case 'PositiveInteger': | |
return tc.gen.posInt // Extends through configuration (pass a dictionary?) | |
default: | |
return toGenRec(typeInfo.type).suchThat(typeInfo.predicate) // fallback! | |
} | |
case 'DictionaryType': | |
return tc.gen.object(toGenRec(typeInfo.codomain), options) | |
} | |
} | |
return toGenRec(typeInfo) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment