Skip to content

Instantly share code, notes, and snippets.

@sledorze
Last active December 1, 2017 11:42
Show Gist options
  • Save sledorze/57c45b27aa57d2971227ba5ac5f41edf to your computer and use it in GitHub Desktop.
Save sledorze/57c45b27aa57d2971227ba5ac5f41edf to your computer and use it in GitHub Desktop.
Adapted version of io-ts (95 branch) with testcheck integration
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