@typed/fp
Version:
Data Structures and Resources for fp-ts
576 lines • 15.3 kB
JavaScript
/**
* 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