Last active
December 1, 2017 11:42
-
-
Save sledorze/57c45b27aa57d2971227ba5ac5f41edf to your computer and use it in GitHub Desktop.
Adapted version of io-ts (95 branch) with testcheck integration
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 "./index"; | |
/** | |
* 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<Q> = { | |
[key: string]: TType<Q>; | |
}; | |
export interface TTypeArrayType<Q> extends t.ArrayType<TType<Q>, Q> {} | |
export interface TTypeDictionaryType<D extends t.StringType, Q> | |
extends t.DictionaryType<D, TType<Q>, Q> {} | |
export type TTypes<Q> = TType<Q>[]; | |
export interface ITTypes<Q> extends TTypes<Q> {} | |
export interface RefinedTType<Q> | |
extends t.RefinementType<t.TypeOf<any>, t.TypeOf<any>, Q> {} | |
export interface RecursiveTType<Q> extends t.RecursiveType<TType<Q>, Q> {} | |
export type TObjTypes<Q> = TObjType<Q>[]; | |
export interface ITObjTypes<Q> extends TObjTypes<Q> {} | |
export interface TObjTypeDictionaryType<D extends t.StringType, Q> | |
extends t.DictionaryType<D, TType<Q>, Q> {} | |
export interface RefinedTObjType<Q> | |
extends t.RefinementType<TObjType<Q>, TObjType<Q>, Q> {} | |
export interface RecursiveTObjType<Q> extends t.RecursiveType<TObjType<Q>, Q> {} | |
export type TObjType<Q> = | |
| t.InterfaceType<ObjectOfTTypes<Q>, Q> | |
| t.UnionType<ITObjTypes<Q>, Q> | |
| t.IntersectionType<ITObjTypes<Q>, Q> // only intersection of interfaces supported | |
| t.PartialType<ObjectOfTTypes<Q>, Q> | |
| TObjTypeDictionaryType<t.StringType, Q> | |
| RecursiveTObjType<Q> | |
| RefinedTObjType<Q>; | |
export type TType<Q> = | |
| t.InterfaceType<ObjectOfTTypes<Q>, Q> | |
| t.UnionType<ITTypes<Q>, Q> | |
| t.IntersectionType<ITObjTypes<Q>, Q> // only intersection of interfaces supported | |
| t.PartialType<ObjectOfTTypes<Q>, Q> | |
| TTypeDictionaryType<t.StringType, Q> | |
| RecursiveTType<Q> | |
| RefinedTType<Q> | |
| t.LiteralType<any> | |
| TTypeArrayType<Q> | |
| t.StringType | |
| t.NumberType | |
| t.BooleanType; | |
const genObjectFromProps = <T extends TType<any>>(x: { | |
[k: string]: T & TType<any>; | |
}) => (f: (x: T & TType<any>) => 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<any>>( | |
typeInfo: T & TType<any>, | |
options?: tc.SizeOptions | |
): tc.Generator<t.TypeOf<T>> => { | |
options = options || { maxSize: 4 }; | |
const propsGenerator = <T extends TType<any>>(props: { | |
[k: string]: T & TType<any>; | |
}): tc.Generator<t.TypeOf<T>> => genObjectFromProps(props)(toGenRec); | |
const partialPropsGenerator = <T extends TType<any>>(props: { | |
[k: string]: T & TType<any>; | |
}): tc.Generator<t.TypeOf<T>> => | |
genObjectFromProps(props)(x => | |
tc.gen.oneOf([toGenRec(x), tc.gen.undefined]) | |
); | |
const recursiveType = <T extends TType<Q>, Q>(x: t.RecursiveType<T, Q>) => { | |
let generator: () => tc.Generator<t.TypeOf<T>> = () => { | |
let v = cache.get(x); | |
if (v === undefined) { | |
v = toGenRec(x.type as TType<Q>); | |
cache.set(x, v); | |
} | |
const w = v; | |
generator = () => w; | |
return v; | |
}; | |
return tc.gen.undefined.then(_ => generator()); | |
}; | |
const recursiveObjType = <T extends TObjType<Q>, Q>( | |
x: t.RecursiveType<T, Q> | |
) => { | |
let generator: () => tc.Generator<t.TypeOf<T>> = () => { | |
let v = cache.get(x); | |
if (v === undefined) { | |
v = toGenObj(x.type as TObjType<Q>); | |
cache.set(x, v); | |
} | |
const w = v; | |
generator = () => w; | |
return v; | |
}; | |
return tc.gen.undefined.then(_ => generator()); | |
}; | |
const toGenObj = <T extends TObjType<any>>( | |
typeInfo: T & TObjType<any> | |
): 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<any>>( | |
typeInfo: T & TType<any> | |
): 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); | |
}; | |
const schema = t.interface({ | |
a: t.string, | |
b: t.union([ | |
t.partial({ | |
c: t.string, | |
d: t.literal("eee") | |
}), | |
t.boolean | |
]), | |
ee: t.array(t.string), | |
e: t.intersection([ | |
// t.array(t.string), // generates an error | |
t.interface({ | |
f: t.array(t.string), | |
w: t.array(t.string) | |
}), | |
t.interface({ | |
g: t.union([t.literal("toto"), t.literal("tata")]) | |
}) | |
]) | |
}); | |
export const schemaGen = toGen(schema); // issue.. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment