/**
* An instance of `Schemable` for `fast-check` arbitraries that emit valid values
*
* @since 0.0.2
* @internal
*/
import * as fc from 'fast-check'
import { identity, pipe } from 'fp-ts/function'
import { Refinement } from 'fp-ts/Refinement'
import * as RA from 'fp-ts/ReadonlyArray'
import * as RR from 'fp-ts/ReadonlyRecord'
import * as RTup from 'fp-ts/ReadonlyTuple'
import * as S from 'io-ts/Schemable'
/**
* @since 0.0.2
* @category Model
*/
export interface Arbitrary extends fc.Arbitrary {}
// -------------------------------------------------------------------------------------
// constructors
// -------------------------------------------------------------------------------------
/**
* @since 0.0.2
* @category Constructors
*/
export const literal = <
A extends readonly [L, ...ReadonlyArray],
L extends S.Literal = S.Literal
>(
...values: A
): Arbitrary => fc.oneof(...values.map(v => fc.constant(v)))
// -------------------------------------------------------------------------------------
// primitives
// -------------------------------------------------------------------------------------
/**
* @since 0.0.2
* @category Primitives
*/
export const string: Arbitrary = fc.oneof(
fc.string(),
fc.asciiString(),
fc.fullUnicodeString(),
fc.hexaString(),
fc.lorem()
)
/**
* @since 0.0.2
* @category Primitives
*/
export const number: Arbitrary = fc.oneof(fc.float(), fc.double(), fc.integer())
/**
* @since 0.0.2
* @category Primitives
*/
export const boolean: Arbitrary = fc.boolean()
/**
* @since 0.0.2
* @category Primitives
*/
export const UnknownArray: Arbitrary> = fc.array(fc.anything())
/**
* @since 0.0.2
* @category Primitives
*/
export const UnknownRecord: Arbitrary> = fc.dictionary(
string,
fc.anything()
)
// -------------------------------------------------------------------------------------
// combinators
// -------------------------------------------------------------------------------------
/**
* @since 0.0.2
* @category Combinators
*/
export const refine =
(refinement: Refinement) =>
(from: Arbitrary): Arbitrary =>
from.filter(refinement)
/**
* @since 0.0.2
* @category Combinators
*/
export const nullable = (or: Arbitrary): Arbitrary =>
fc.oneof(fc.constant(null), or)
/**
* @since 0.0.2
* @category Combinators
*/
export const struct = (properties: {
[K in keyof A]: Arbitrary
}): Arbitrary => fc.record(properties)
/**
* @since 0.0.2
* @category Combinators
*/
export const partial = (properties: { [K in keyof A]: Arbitrary }): Arbitrary<
Partial
> => {
const keys = fc.oneof(...Object.keys(properties).map(p => fc.constant(p)))
return keys.chain(key => {
const p: { [K in keyof A]: Arbitrary } = { ...properties }
delete p[key as keyof A]
return fc.record(p)
})
}
/**
* @since 0.0.2
* @category Combinators
*/
export const record = (codomain: Arbitrary): Arbitrary> =>
fc.dictionary(string, codomain)
/**
* @since 0.0.2
* @category Combinators
*/
export const array = (item: Arbitrary): Arbitrary> => fc.array(item)
/**
* @since 0.0.2
* @category Combinators
*/
export const tuple = >(
...components: { [K in keyof A]: Arbitrary }
): Arbitrary => {
if (components.length === 0) {
return fc.constant(RA.zero() as A)
}
return fc.tuple(...components) as unknown as Arbitrary
}
/**
* @since 0.0.2
* @internal
*/
export const typeOf = (x: unknown): string => (x === null ? 'null' : typeof x)
/**
* @since 0.0.2
* @internal
*/
const intersect_ = (a: A, b: B): A & B => {
if (a !== undefined && b !== undefined) {
const tx = typeOf(a)
const ty = typeOf(b)
if (tx === 'object' || ty === 'object') {
return Object.assign({}, a, b)
}
}
return undefined as A & B
}
/**
* @since 0.0.2
* @category Combinators
*/
export const intersect =
(right: Arbitrary) =>
(left: Arbitrary): Arbitrary =>
fc.tuple(left, right).map(([a, b]) => intersect_(a, b))
/**
* @since 0.0.2
* @category Combinators
*/
export const sum =
(
_tag: T // eslint-disable-line @typescript-eslint/no-unused-vars
) =>
(members: { [K in keyof A]: Arbitrary> }): Arbitrary<
A[keyof A]
> =>
fc.oneof(...pipe(members, RR.toReadonlyArray, RA.map(RTup.snd)))
/**
* @since 0.0.2
* @category Combinators
*/
export const lazy = (f: () => Arbitrary): Arbitrary => {
const get = S.memoize>(f)
return fc.constant(null).chain(() => get())
}
/**
* @since 0.0.2
* @category Combintors
*/
export const readonly: (arb: Arbitrary) => Arbitrary> = identity
/**
* @since 0.0.2
* @category Combinators
*/
export const union = , ...Array>]>(
...members: A
): Arbitrary => fc.oneof(...members) as unknown as Arbitrary
// -------------------------------------------------------------------------------------
// instances
// -------------------------------------------------------------------------------------
/**
* @since 0.0.2
* @category Instances
*/
export const URI = 'Arbitrary'
export type URI = typeof URI
declare module 'fp-ts/lib/HKT' {
interface URItoKind {
readonly Arbitrary: Arbitrary
}
}
/**
* @since 0.0.2
* @category Instances
*/
export const Schemable: S.Schemable1 &
S.WithUnknownContainers1 &
S.WithUnion1 &
S.WithRefine1 = {
URI,
literal,
string,
number,
boolean,
nullable,
type: struct,
struct,
partial,
record,
array,
tuple: tuple as S.Schemable1['tuple'],
intersect,
sum,
lazy: (_, f) => lazy(f),
readonly,
UnknownArray,
UnknownRecord,
union: union as S.WithUnion1['union'],
refine: refine as S.WithRefine1['refine'],
}