Skip to content

Instantly share code, notes, and snippets.

@sledorze
Created November 30, 2017 08:49
Show Gist options
  • Save sledorze/a4ffa850c1ab71314b4fd0d3d480304e to your computer and use it in GitHub Desktop.
Save sledorze/a4ffa850c1ab71314b4fd0d3d480304e to your computer and use it in GitHub Desktop.
testcheck Generator generation from io-ts definition (pre 0.9.0)
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