import { toFailures, shiftIterator, StructSchema, run } from './utils'import { StructError, Failure } from './error'
export class Struct<T = unknown, S = unknown> { readonly TYPE!: T type: string schema: S coercer: (value: unknown, context: Context) => unknown validator: (value: unknown, context: Context) => Iterable<Failure> refiner: (value: T, context: Context) => Iterable<Failure> entries: ( value: unknown, context: Context ) => Iterable<[string | number, unknown, Struct<any> | Struct<never>]>
constructor(props: { type: string schema: S coercer?: Coercer validator?: Validator refiner?: Refiner<T> entries?: Struct<T, S>['entries'] }) { const { type, schema, validator, refiner, coercer = (value: unknown) => value, entries = function* () {}, } = props
this.type = type this.schema = schema this.entries = entries this.coercer = coercer
if (validator) { this.validator = (value, context) => { const result = validator(value, context) return toFailures(result, context, this, value) } } else { this.validator = () => [] }
if (refiner) { this.refiner = (value, context) => { const result = refiner(value, context) return toFailures(result, context, this, value) } } else { this.refiner = () => [] } }
assert(value: unknown, message?: string): asserts value is T { return assert(value, this, message) }
create(value: unknown, message?: string): T { return create(value, this, message) }
is(value: unknown): value is T { return is(value, this) }
mask(value: unknown, message?: string): T { return mask(value, this, message) }
validate( value: unknown, options: { coerce?: boolean message?: string } = {} ): [StructError, undefined] | [undefined, T] { return validate(value, this, options) }}
export function assert<T, S>( value: unknown, struct: Struct<T, S>, message?: string): asserts value is T { const result = validate(value, struct, { message })
if (result[0]) { throw result[0] }}
export function create<T, S>( value: unknown, struct: Struct<T, S>, message?: string): T { const result = validate(value, struct, { coerce: true, message })
if (result[0]) { throw result[0] } else { return result[1] }}
export function mask<T, S>( value: unknown, struct: Struct<T, S>, message?: string): T { const result = validate(value, struct, { coerce: true, mask: true, message })
if (result[0]) { throw result[0] } else { return result[1] }}
export function is<T, S>(value: unknown, struct: Struct<T, S>): value is T { const result = validate(value, struct) return !result[0]}
export function validate<T, S>( value: unknown, struct: Struct<T, S>, options: { coerce?: boolean mask?: boolean message?: string } = {}): [StructError, undefined] | [undefined, T] { const tuples = run(value, struct, options) const tuple = shiftIterator(tuples)!
if (tuple[0]) { const error = new StructError(tuple[0], function* () { for (const t of tuples) { if (t[0]) { yield t[0] } } })
return [error, undefined] } else { const v = tuple[1] return [undefined, v] }}
export type Context = { branch: Array<any> path: Array<any>}
export type Infer<T extends Struct<any, any>> = T['TYPE']
export type Describe<T> = Struct<T, StructSchema<T>>
export type Result = | boolean | string | Partial<Failure> | Iterable<boolean | string | Partial<Failure>>
export type Coercer<T = unknown> = (value: T, context: Context) => unknown
export type Validator = (value: unknown, context: Context) => Result
export type Refiner<T> = (value: T, context: Context) => Result