import type { UnionToIntersection } from "@principia/prelude/Utils"; import * as A from "../Array"; import type { Either } from "../Either"; import * as FS from "../FreeSemigroup"; import type { Refinement } from "../Function"; import { pipe } from "../Function"; import * as K from "../KleisliDecoder"; import type { Option } from "../Option"; import type { ReadonlyRecord } from "../Record"; import * as R from "../Record"; import type { DecodeError, ErrorInfo } from "./decode-error"; import { error } from "./decode-error"; import * as DE from "./DecodeError"; import type { Decoder, InputOf, TypeOf } from "./model"; import { M } from "./monad"; import { UnknownArray, UnknownRecord } from "./primitives"; export const compose_: (from: Decoder, to: Decoder) => Decoder = (from, to) => ({ decode: K.compose_(M)(from, to).decode, _meta: { name: `(${from._meta.name} >>> ${to._meta.name})` } }); export const compose = (to: Decoder) => (from: Decoder): Decoder => compose_(from, to); export const mapLeftWithInput = (f: (input: I, e: DecodeError) => DecodeError) => ( decoder: Decoder ): Decoder => mapLeftWithInput_(decoder, f); export const mapLeftWithInput_ = ( decoder: Decoder, f: (input: I, e: DecodeError) => DecodeError ): Decoder => ({ decode: K.mapLeftWithInput_(M)(decoder, f).decode, _meta: { name: decoder._meta.name } }); export const wrapInfo = (info?: ErrorInfo) => (decoder: Decoder): Decoder => info ? mapLeftWithInput_(decoder, (_, e) => FS.element(DE.wrap(info, e))) : decoder; export const withMessage = ( message: (input: I, e: DecodeError) => string ): ((decoder: Decoder) => Decoder) => mapLeftWithInput((input, e) => FS.element(DE.wrap({ message: message(input, e) }, e))); export const refine = (refinement: Refinement, name: string, info?: ErrorInfo) => ( from: Decoder ): Decoder => ({ decode: K.refine_(M)(from, refinement, (a) => error(a, name, info)).decode, _meta: { name: name } }); export const parse_ = ( from: Decoder, parser: (a: A) => Either, name?: string ): Decoder => ({ decode: K.parse_(M)(from, parser).decode, _meta: { name: name ?? from._meta.name } }); export const parse = (parser: (a: A) => Either, name?: string) => ( from: Decoder ): Decoder => parse_(from, parser, name); export const nullable: (info?: ErrorInfo) => (or: Decoder) => Decoder = ( info ) => (or) => ({ decode: K.nullable_(M)(or, (u, e) => FS.combine(FS.element(DE.member(0, error(u, "null | undefined", info))), FS.element(DE.member(1, e))) ).decode, _meta: { name: `${or._meta.name} | null | undefined` } }); export const optional = (info?: ErrorInfo) => (or: Decoder): Decoder> => ({ decode: K.optional_(M)(or, (u, e) => FS.combine(FS.element(DE.member(0, error(u, "null | undefined", info))), FS.element(DE.member(1, e))) ).decode, _meta: { name: `${or._meta.name} | null | undefined` } }); export const fromType =

>>( properties: P, info?: ErrorInfo ): Decoder<{ [K in keyof P]: InputOf }, { [K in keyof P]: TypeOf }> => { const name: string = pipe( properties, R.reduceWithIndex([] as string[], (k, b, a) => [...b, `${k}: ${a._meta.name}`]), (as) => `{ ${as.join(", ")} }` ); return pipe( { decode: K.fromType_(M)(properties, (k, e) => FS.element(DE.key(k, DE.required, e))).decode, _meta: { name } }, wrapInfo({ name, ...info }) ); }; export const type =

>>( properties: P, info?: ErrorInfo ): Decoder }> => compose_(UnknownRecord(info) as any, fromType(properties, info)); export const fromPartial =

>>( properties: P, info?: ErrorInfo ): Decoder }>, Partial<{ [K in keyof P]: TypeOf }>> => { const name: string = pipe( properties, R.reduceWithIndex([] as string[], (k, b, a) => [...b, `${k}?: ${a._meta.name}`]), (as) => `{ ${as.join(", ")} }` ); return pipe( { decode: K.fromPartial_(M)(properties, (k, e) => FS.element(DE.key(k, DE.optional, e))).decode, _meta: { name } }, wrapInfo({ name, ...info }) ); }; export const partial = ( properties: { [K in keyof A]: Decoder }, info?: ErrorInfo ): Decoder> => compose_(UnknownRecord(info) as any, fromPartial(properties, info)); export const fromArray = (item: Decoder, info?: ErrorInfo): Decoder, ReadonlyArray> => { const name = `Array<${item._meta.name}>`; return pipe( { decode: K.fromArray_(M)(item, (i, e) => FS.element(DE.index(i, DE.optional, e))).decode, _meta: { name } }, wrapInfo({ name, ...info }) ); }; export const array = (item: Decoder, info?: ErrorInfo): Decoder> => compose_(UnknownArray(info), fromArray(item, info)); export const fromRecord = ( codomain: Decoder, info?: ErrorInfo ): Decoder, ReadonlyRecord> => { const name = `Record`; return pipe( { decode: K.fromRecord_(M)(codomain, (k, e) => FS.element(DE.key(k, DE.optional, e))).decode, _meta: { name } }, wrapInfo({ name, ...info }) ); }; export const record = (codomain: Decoder, info?: ErrorInfo): Decoder> => compose_(UnknownRecord(info), fromRecord(codomain, info)); export const fromTuple = >>( ...components: C ): ((info?: ErrorInfo) => Decoder<[...{ [K in keyof C]: InputOf }], [...{ [K in keyof C]: TypeOf }]>) => ( info ) => { const name: string = pipe( components, A.map((d) => d._meta.name), (as) => `[${as.join(", ")}]` ); return pipe( { decode: K.fromTuple(M)((i, e) => FS.element(DE.index(i, DE.required, e)))(...components).decode, _meta: { name } }, wrapInfo({ name, ...info }) ); }; export const tuple = >(...components: { [K in keyof A]: Decoder }) => ( info?: ErrorInfo ): Decoder => compose_(UnknownArray(info) as any, fromTuple(...components)(info)); export const union = (info?: ErrorInfo) => , ...Array>]>( ...members: MS ): Decoder, TypeOf> => { const name = members.join(" | "); return pipe( { decode: K.union(M)((i, e) => FS.element(DE.member(i, e)))(...members).decode, _meta: { name } } as Decoder, TypeOf>, wrapInfo({ name, ...info }) ); }; export const intersect_ = ( left: Decoder, right: Decoder, name?: string ): Decoder => pipe( { decode: K.intersect_(M)(left, right).decode, _meta: { name: name ?? `${left._meta.name} & ${right._meta.name}` } }, wrapInfo({ name: name ?? `${left._meta.name} & ${right._meta.name}` }) ); export const intersect = (right: Decoder, name?: string) => ( left: Decoder ): Decoder => intersect_(left, right, name); export const intersectAll = < A extends readonly [Decoder, Decoder, ...(readonly Decoder[])] >( decoders: A, name?: string ): Decoder< UnionToIntersection<{ [K in keyof A]: InputOf }[keyof A]>, UnionToIntersection<{ [K in keyof A]: TypeOf }[keyof A]> > => { const [left, right, ...rest] = decoders; const decode = A.reduce_(rest, K.intersect_(M)(left, right), (b, a) => K.intersect_(M)(b, a)).decode; const _name = name ?? A.map_(decoders, (d) => d._meta.name).join(" & "); return pipe({ decode, _meta: { name: name ?? _name } }, wrapInfo({ name: name ?? _name })); }; export const fromSum_ = >>( tag: T, members: MS ): Decoder, TypeOf> => { const name: string = pipe( members, R.reduce([] as string[], (b, a) => [...b, a._meta.name]), (as) => as.join(" | ") ); const decode = K.fromSum_(M)(tag, members, (tag, value, keys) => FS.element( DE.key( tag, DE.required, error(value, keys.length === 0 ? "never" : keys.map((k) => JSON.stringify(k)).join(" | ")) ) ) ).decode; return pipe({ decode, _meta: { name } }, wrapInfo({ name })); }; export const fromSum = (tag: T) => >>( members: MS ): Decoder, TypeOf> => fromSum_(tag, members); export const sum_ = ( tag: T, members: { [K in keyof A]: Decoder }, info?: ErrorInfo ): Decoder => compose_(UnknownRecord(info), fromSum(tag)(members)); export const sum = (tag: T, info?: ErrorInfo) => ( members: { [K in keyof A]: Decoder } ): Decoder => sum_(tag, members, info); export const lazy = (id: string, f: () => Decoder, info?: ErrorInfo): Decoder => pipe( { decode: K.lazy_(M)(id, f, (id, e) => FS.element(DE.lazy(id, e))).decode, _meta: { name: info?.name ?? id } }, wrapInfo({ name: id, ...info }) ); export const map_ = (fa: Decoder, f: (a: A) => B): Decoder => ({ decode: K.map_(M)(fa, f).decode, _meta: { name: fa._meta.name } }); export const map = (f: (a: A) => B) => (fa: Decoder): Decoder => map_(fa, f); export const alt_ = (me: Decoder, that: () => Decoder): Decoder => ({ decode: K.alt_(M)(me, that).decode, _meta: { name: `${me._meta.name} ${that()._meta.name}` } }); export const alt = (that: () => Decoder) => (me: Decoder): Decoder => alt_(me, that); export const id = (): Decoder => ({ decode: K.id(M)().decode, _meta: { name: "" } });