import { Struct, Infer, Result, Context, Describe } from './struct'import { Failure } from './error'
function isIterable<T>(x: unknown): x is Iterable<T> { return isObject(x) && typeof x[Symbol.iterator] === 'function'}
export function isObject(x: unknown): x is object { return typeof x === 'object' && x != null}
export function isPlainObject(x: unknown): x is { [key: string]: any } { if (Object.prototype.toString.call(x) !== '[object Object]') { return false }
const prototype = Object.getPrototypeOf(x) return prototype === null || prototype === Object.prototype}
export function print(value: any): string { if (typeof value === 'symbol') { return value.toString() }
return typeof value === 'string' ? JSON.stringify(value) : `${value}`}
export function shiftIterator<T>(input: Iterator<T>): T | undefined { const { done, value } = input.next() return done ? undefined : value}
export function toFailure<T, S>( result: string | boolean | Partial<Failure>, context: Context, struct: Struct<T, S>, value: any): Failure | undefined { if (result === true) { return } else if (result === false) { result = {} } else if (typeof result === 'string') { result = { message: result } }
const { path, branch } = context const { type } = struct const { refinement, message = `Expected a value of type \`${type}\`${ refinement ? ` with refinement \`${refinement}\`` : '' }, but received: \`${print(value)}\``, } = result
return { value, type, refinement, key: path[path.length - 1], path, branch, ...result, message, }}
export function* toFailures<T, S>( result: Result, context: Context, struct: Struct<T, S>, value: any): IterableIterator<Failure> { if (!isIterable(result)) { result = [result] }
for (const r of result) { const failure = toFailure(r, context, struct, value)
if (failure) { yield failure } }}
export function* run<T, S>( value: unknown, struct: Struct<T, S>, options: { path?: any[] branch?: any[] coerce?: boolean mask?: boolean message?: string } = {}): IterableIterator<[Failure, undefined] | [undefined, T]> { const { path = [], branch = [value], coerce = false, mask = false } = options const ctx: Context = { path, branch }
if (coerce) { value = struct.coercer(value, ctx)
if ( mask && struct.type !== 'type' && isObject(struct.schema) && isObject(value) && !Array.isArray(value) ) { for (const key in value) { if (struct.schema[key] === undefined) { delete value[key] } } } }
let status: 'valid' | 'not_refined' | 'not_valid' = 'valid'
for (const failure of struct.validator(value, ctx)) { failure.explanation = options.message status = 'not_valid' yield [failure, undefined] }
for (let [k, v, s] of struct.entries(value, ctx)) { const ts = run(v, s as Struct, { path: k === undefined ? path : [...path, k], branch: k === undefined ? branch : [...branch, v], coerce, mask, message: options.message, })
for (const t of ts) { if (t[0]) { status = t[0].refinement != null ? 'not_refined' : 'not_valid' yield [t[0], undefined] } else if (coerce) { v = t[1]
if (k === undefined) { value = v } else if (value instanceof Map) { value.set(k, v) } else if (value instanceof Set) { value.add(v) } else if (isObject(value)) { if (v !== undefined || k in value) value[k] = v } } } }
if (status !== 'not_valid') { for (const failure of struct.refiner(value as T, ctx)) { failure.explanation = options.message status = 'not_refined' yield [failure, undefined] } }
if (status === 'valid') { yield [undefined, value as T] }}
export type UnionToIntersection<U> = ( U extends any ? (arg: U) => any : never) extends (arg: infer I) => void ? I : never
export type Assign<T, U> = Simplify<U & Omit<T, keyof U>>
export type EnumSchema<T extends string | number | undefined | null> = { [K in NonNullable<T>]: K}
export type IsMatch<T, G> = T extends G ? (G extends T ? T : never) : never
export type IsExactMatch<T, U> = (<G>() => G extends T ? 1 : 2) extends < G>() => G extends U ? 1 : 2 ? T : never
export type IsRecord<T> = T extends object ? string extends keyof T ? T : never : never
export type IsTuple<T> = T extends [any] ? T : T extends [any, any] ? T : T extends [any, any, any] ? T : T extends [any, any, any, any] ? T : T extends [any, any, any, any, any] ? T : never
export type IsUnion<T, U extends T = T> = ( T extends any ? (U extends T ? false : true) : false) extends false ? never : T
export type ObjectSchema = Record<string, Struct<any, any>>
export type ObjectType<S extends ObjectSchema> = Simplify< Optionalize<{ [K in keyof S]: Infer<S[K]> }>>
export type OmitBy<T, V> = Omit< T, { [K in keyof T]: V extends Extract<T[K], V> ? K : never }[keyof T]>
export type Optionalize<S extends object> = OmitBy<S, undefined> & Partial<PickBy<S, undefined>>
export type PartialObjectSchema<S extends ObjectSchema> = { [K in keyof S]: Struct<Infer<S[K]> | undefined>}
export type PickBy<T, V> = Pick< T, { [K in keyof T]: V extends Extract<T[K], V> ? K : never }[keyof T]>
export type Simplify<T> = T extends any[] | Date ? T : { [K in keyof T]: T[K] } & {}
export type If<B extends Boolean, Then, Else> = B extends true ? Then : Else
export type StructSchema<T> = [T] extends [string | undefined | null] ? [T] extends [IsMatch<T, string | undefined | null>] ? null : [T] extends [IsUnion<T>] ? EnumSchema<T> : T : [T] extends [number | undefined | null] ? [T] extends [IsMatch<T, number | undefined | null>] ? null : [T] extends [IsUnion<T>] ? EnumSchema<T> : T : [T] extends [boolean] ? [T] extends [IsExactMatch<T, boolean>] ? null : T : T extends | bigint | symbol | undefined | null | Function | Date | Error | RegExp | Map<any, any> | WeakMap<any, any> | Set<any> | WeakSet<any> | Promise<any> ? null : T extends Array<infer E> ? T extends IsTuple<T> ? null : Struct<E> : T extends object ? T extends IsRecord<T> ? null : { [K in keyof T]: Describe<T[K]> } : null
export type TupleSchema<T> = { [K in keyof T]: Struct<T[K]> }
export type AnyStruct = Struct<any, any>
export type InferStructTuple< Tuple extends AnyStruct[], Length extends number = Tuple['length']> = Length extends Length ? number extends Length ? Tuple : _InferTuple<Tuple, Length, []> : nevertype _InferTuple< Tuple extends AnyStruct[], Length extends number, Accumulated extends unknown[], Index extends number = Accumulated['length']> = Index extends Length ? Accumulated : _InferTuple<Tuple, Length, [...Accumulated, Infer<Tuple[Index]>]>