/** * 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'], }