UNPKG

@typed/fp

Version:

Data Structures and Resources for fp-ts

576 lines 15.3 kB
/** * Decoder is a data structure for representing runtime representations of your types. * @since 0.9.4 */ import { parseISO } from 'date-fns'; import * as App from 'fp-ts/Applicative'; import * as Ap from 'fp-ts/Apply'; import * as Ch from 'fp-ts/Chain'; import * as Ei from 'fp-ts/Either'; import * as F from 'fp-ts/Functor'; import * as N from 'fp-ts/number'; import { not } from 'fp-ts/Predicate'; import * as RA from 'fp-ts/ReadonlyArray'; import { concatW } from 'fp-ts/ReadonlyNonEmptyArray'; import * as S from 'fp-ts/string'; import * as DE from './DecodeError'; import { leaf } from './DecodeError'; import { pipe } from './function'; import { memoize } from './internal'; import * as St from './struct'; import { make } from './struct'; import * as T from './These'; /** * @category Constructor * @since 0.9.4 */ export function fromRefinement(refinement, expected) { return { decode: (i) => (refinement(i) ? T.right(i) : T.left([DE.leaf(i, expected)])), }; } /** * @category Combinator * @since 0.9.4 */ export const compose = (second) => (first) => { const { chain } = T.getChain(DE.getSemigroup()); return { decode: (i) => pipe(i, first.decode, chain(second.decode)), }; }; /** * @category Constructor * @since 0.9.4 */ export const string = fromRefinement((x) => typeof x === 'string', 'string'); /** * @category Constructor * @since 0.9.4 */ export const number = fromRefinement((x) => typeof x === 'number', 'number'); /** * @category Constructor * @since 0.9.4 */ export const boolean = fromRefinement((x) => typeof x === 'boolean', 'boolean'); /** * @category Decoder * @since 0.9.5 */ export const dateFromISOString = { decode: (i) => { const date = parseISO(i); const time = date.getTime(); if (Number.isNaN(time)) { return T.left([leaf(i, `dateFromISOString`)]); } return T.right(date); }, }; /** * @category Instance * @since 0.9.5 */ export const WithRefine = { refine: (refinment, id) => (from) => pipe(from, compose(fromRefinement(refinment, id))), }; /** * @category Combinator * @since 0.9.5 */ export const refine = WithRefine.refine; /** * @category Constructor * @since 0.9.4 */ export const union = (second) => (first) => { const { concat } = DE.getSemigroup(); return { decode: (i) => pipe(i, first.decode, T.mapLeft((errors) => [DE.member(0, errors)]), T.matchW((e1) => pipe(i, second.decode, T.mapLeft((errors) => [DE.member(1, errors)]), T.matchW((e2) => pipe(e1, concat(e2), T.left), T.right, (e2, a) => T.both(pipe(e1, concat(e2)), a))), T.right, (e1, o1) => pipe(i, second.decode, T.mapLeft((errors) => [DE.member(1, errors)]), T.matchW((e2) => T.both(pipe(e1, concat(e2)), o1), T.right, (e2) => T.both(pipe(e1, concat(e2)), o1))))), }; }; /** * @category Refinement * @since 0.9.6 */ export const isDate = (x) => x instanceof Date; /** * @category Constructor * @since 0.9.6 */ export const date = pipe(string, refine(isDate, 'Date'), union(fromRefinement(isDate, 'Date'))); /** * @category Constructor * @since 0.9.6 */ export const sum = (tag) => (members) => { return { decode: (i) => pipe(i, struct(St.make(tag, literal(...Object.keys(members)))).decode, T.matchW(T.left, (a) => members[a[tag]].decode(a), ([first, ...rest], a) => pipe(members[a[tag]].decode(a), T.mapLeft((e) => [first, ...rest, ...e])))), }; }; /** * @category Constructor * @since 0.9.6 */ export const literal = (...literals) => fromRefinement((x) => literals.includes(x), literals.join(' | ')); /** * @category Combinator * @since 0.9.4 */ export const nullable = union(fromRefinement((x) => x === null, 'null')); /** * @category Combinator * @since 0.9.4 */ export const optional = union(fromRefinement((x) => x === undefined, 'undefined')); /** * @category Constructor * @since 0.9.4 */ export const unknownArray = fromRefinement(Array.isArray, 'Array<unknown>'); /** * @category Constructor * @since 0.9.4 */ export const unknownRecord = fromRefinement((x) => !!x && !Array.isArray(x) && typeof x === 'object', 'Record<string, unknown>'); /** * @category Constructor * @since 0.9.4 */ export const fromArray = (member) => { const { concat } = T.getSemigroup(DE.getSemigroup(), RA.getSemigroup()); return { decode: (inputs) => { const array = inputs.map((input, index) => pipe(input, member.decode, T.mapLeft((e) => [DE.index(index, e)]), T.map((o) => [o]))); return array.reduce((acc, x) => pipe(x, concat(acc)), T.right([])); }, }; }; /** * @category Constructor * @since 0.9.4 */ export const array = (member) => pipe(unknownArray, compose(fromArray(member))); /** * @category Constructor * @since 0.9.4 */ export const fromStruct = (properties) => { const { concat } = T.getSemigroup(DE.getSemigroup(), St.getAssignSemigroup()); return { decode: (i) => { const expectedKeys = Object.keys(properties); const remainingKeys = expectedKeys.filter((k) => k in i); const struct = remainingKeys.map((k) => pipe(properties[k].decode(i[k]), T.mapLeft((e) => [DE.key(k, e)]), T.map((o) => make(k, o)))); const result = struct.reduce((acc, x) => pipe(x, concat(acc)), T.right({})); return result; }, }; }; /** * @category Constructor * @since 0.9.4 */ export function missingKeys(properties) { const diff = RA.difference(S.Eq); return { decode: (i) => { const expectedKeys = Object.keys(properties); const actualKeys = Object.keys(i); const missingKeys = pipe(expectedKeys, diff(actualKeys)); const result = RA.isNonEmpty(missingKeys) ? T.both([DE.missingKeys([missingKeys[0], ...missingKeys.slice(1)])], i) : T.right(i); return result; }, }; } /** * @category Constructor * @since 0.9.4 */ export function unexpectedKeys(properties) { const diff = RA.difference(S.Eq); return { decode: (i) => { const expectedKeys = Object.keys(properties); const actualKeys = Object.keys(i); const unexpectedKeys = pipe(actualKeys, diff(expectedKeys)); const result = RA.isNonEmpty(unexpectedKeys) ? T.both([DE.unexpectedKeys([unexpectedKeys[0], ...unexpectedKeys.slice(1)])], i) : T.right(i); return result; }, }; } /** * @category Constructor * @since 0.9.4 */ export function struct(properties) { return pipe(unknownRecord, compose(missingKeys(properties)), compose(unexpectedKeys(properties)), compose(fromStruct(properties))); } /** * @category Constructor * @since 0.9.4 */ export function fromRecord(decoder) { const { concat } = T.getSemigroup(DE.getSemigroup(), St.getAssignSemigroup()); return { decode: (i) => { const results = Object.entries(i).map(([key, value]) => pipe(value, decoder.decode, T.mapLeft((errors) => [DE.key(key, errors)]), T.map((b) => ({ [key]: b })))); return results.reduce((acc, x) => pipe(x, concat(acc)), T.of(Object.create(null))); }, }; } /** * @category Constructor * @since 0.9.6 */ export const record = (codomain) => pipe(unknownRecord, compose(fromRecord(codomain))); /** * @category Constructor * @since 0.9.4 */ export function fromTuple(...components) { const { concat } = T.getSemigroup(DE.getSemigroup(), RA.getSemigroup()); return { decode: (input) => { const tuple = components.map((d, i) => pipe(d.decode(input[i]), T.mapLeft((errors) => [DE.index(i, errors)]), T.map((o) => [o]))); const result = tuple.reduce((acc, x) => pipe(x, concat(acc)), T.right([])); return result; }, }; } /** * @category Constructor * @since 0.9.4 */ export function missingIndexes(...components) { const diff = RA.difference(N.Eq); return { decode: (i) => { const expectedKeys = Object.keys(components).map(parseFloat); const actualKeys = Object.keys(i).map(parseFloat); const missingKeys = pipe(expectedKeys, diff(actualKeys)); const result = RA.isNonEmpty(missingKeys) ? T.both([DE.missingIndexes([missingKeys[0], ...missingKeys.slice(1)])], i) : T.right(i); return result; }, }; } /** * @category Constructor * @since 0.9.4 */ export function unexpectedIndexes(...components) { const diff = RA.difference(S.Eq); return { decode: (i) => { const expectedKeys = Object.keys(components); const actualKeys = Object.keys(i); const unexpectedKeys = pipe(actualKeys, diff(expectedKeys)); const result = RA.isNonEmpty(unexpectedKeys) ? T.both([DE.unexpectedKeys([unexpectedKeys[0], ...unexpectedKeys.slice(1)])], i) : T.right(i); return result; }, }; } /** * @category Constructor * @since 0.9.4 */ export function tuple(...components) { return pipe(unknownArray, compose(missingIndexes(...components)), compose(unexpectedIndexes(...components)), compose(fromTuple(...components))); } /** * @category Combinator * @since 0.9.6 */ export const intersect = (second) => (first) => { const { concat } = T.getSemigroup(DE.getSemigroup(), St.getAssignSemigroup()); return { decode: (i) => concat(second.decode(i))(first.decode(i)), }; }; /** * @category Constructor * @since 0.9.6 */ export const lazy = (id, f) => { const get = memoize((_) => f()); return { decode: (i) => pipe(get().decode(i), T.mapLeft((errors) => [DE.lazy(id, errors)])), }; }; /** * @category URI * @since 0.9.4 */ export const URI = '@typed/fp/Decoder'; /** * @category Constructor * @since 0.9.4 */ export const of = (value) => ({ decode: () => T.right(value), }); /** * @category Combinator * @since 0.9.4 */ export const map = (f) => (decoder) => ({ decode: (i) => pipe(i, decoder.decode, T.map(f)), }); /** * @category Combinator * @since 0.9.4 */ export function condemnWhen(predicate) { return (decoder) => ({ decode: (i) => pipe(i, decoder.decode, T.matchW(T.left, T.right, (errors, a) => { const { left: absolved, right: condemned } = pipe(errors, RA.map(Ei.fromPredicate(predicate)), RA.separate); return RA.isNonEmpty(condemned) ? T.left(condemned) : RA.isNonEmpty(absolved) ? T.both(absolved, a) : T.right(a); })), }); } /** * @category Combinator * @since 0.9.4 */ export const condemn = condemnWhen(() => true); /** * @category Combinator * @since 0.9.4 */ export function absolveWhen(predicate) { return (decoder) => ({ decode: (i) => pipe(i, decoder.decode, T.matchW(T.left, T.right, (errors, a) => { const { left: condemned, right: absolved } = pipe(errors, RA.map(Ei.fromPredicate(not(predicate))), RA.separate); return RA.isNonEmpty(absolved) ? T.right(a) : RA.isNonEmpty(condemned) ? T.both(condemned, a) : T.right(a); })), }); } /** * @category Combinator * @since 0.9.4 */ export const absolve = absolveWhen(() => true); /** * @category Combinator * @since 0.9.4 */ export const condemnUnexpectedKeys = condemnWhen((d) => d._tag === 'UnexpectedKeys'); /** * @category Combinator * @since 0.9.4 */ export const condemmMissingKeys = condemnWhen((d) => d._tag === 'MissingKeys'); /** * @category Combinator * @since 0.9.4 */ export const strict = condemnWhen((d) => d._tag === 'UnexpectedKeys' || d._tag === 'MissingKeys'); /** * @category Instance * @since 0.9.4 */ export const Pointed = { of, }; /** * @category Instance * @since 0.9.4 */ export const Functor = { map, }; /** * @category Combinator * @since 0.9.4 */ export const bindTo = F.bindTo(Functor); /** * @category Combinator * @since 0.9.4 */ export const flap = F.flap(Functor); /** * @category Combinator * @since 0.9.4 */ export const tupled = F.tupled(Functor); /** * @category Constructor * @since 0.9.4 */ export const fromIO = (io) => ({ decode: () => T.right(io()), }); /** * @category Combinator * @since 0.9.4 */ export const chain = (f) => (decoder) => ({ decode: (i) => pipe(i, decoder.decode, T.matchW(T.left, (a) => f(a).decode(i), (errors, a) => pipe(i, f(a).decode, T.mapLeft(concatW(errors))))), }); /** * @category Constructor * @since 0.9.4 */ export const Chain = { map, chain, }; /** * @category Combinator * @since 0.9.4 */ export const ap = Ch.ap(Chain); /** * @category Combinator * @since 0.9.4 */ export const chainFirst = Ch.chainFirst(Chain); /** * @category Combinator * @since 0.9.4 */ export const bind = Ch.bind(Chain); /** * @category Instance * @since 0.9.4 */ export const Apply = { map, ap, }; /** * @category Combinator * @since 0.9.4 */ export const apFirst = Ap.apFirst(Apply); /** * @category Combinator * @since 0.9.4 */ export const apS = Ap.apS(Apply); /** * @category Combinator * @since 0.9.4 */ export const apSecond = Ap.apSecond(Apply); /** * @category Combinator * @since 0.9.4 */ export const apT = Ap.apT(Apply); /** * @category Typeclass Constructor * @since 0.9.4 */ export const getApplySemigroup = Ap.getApplySemigroup(Apply); /** * @category Instance * @since 0.9.4 */ export const Applicative = { of, ...Apply, }; /** * @category Combinator * @since 0.9.4 */ export const getApplicativeMonoid = App.getApplicativeMonoid(Applicative); /** * @category Constructor * @since 0.9.4 */ export const Do = fromIO(() => Object.create(null)); /** * @category Instance * @since 0.9.4 */ export const Monad = { ...Pointed, ...Chain, }; /** * @category Instance * @since 0.9.5 */ export const Schemable = { URI, literal, string, number, boolean, date, nullable, optional, struct: struct, record, array, tuple: tuple, intersect, sum, lazy, branded: ((d) => d), unknownArray, unknownRecord, }; /** * @category Instance * @since 0.9.5 */ export const WithUnion = { union, }; /** * @category Decoder * @since 0.9.5 */ export const jsonParseFromString = { decode: (i) => { try { return T.right(JSON.parse(i)); } catch (e) { return T.left([DE.leaf(i, `Json`)]); } }, }; /** * @category Decoder * @since 0.9.5 */ export const jsonParse = pipe(string, compose(jsonParseFromString)); /** * Throw if not a valid decoder. Absolves optional errors * @category Interpreter * @since 0.9.5 */ export const assert = (decoder) => (i) => pipe(i, decoder.decode, T.absolve, Ei.matchW((errors) => { throw new Error(DE.drawErrors(errors)); }, (o) => o)); /** * Throw if not a valid decoder. Condemns optional errors * @category Interpreter * @since 0.9.5 */ export const assertStrict = (decoder) => (i) => pipe(i, decoder.decode, T.condemn, Ei.matchW((errors) => { throw new Error(DE.drawErrors(errors)); }, (o) => o)); //# sourceMappingURL=Decoder.js.map