@unsplash/sum-types-io-ts
Version:
io-ts bindings for @unsplash/sum-types.
70 lines (69 loc) • 5.5 kB
JavaScript
import * as Sum from "@unsplash/sum-types";
import { constant, flow, pipe } from "fp-ts/function";
import * as t from "io-ts";
import * as A from "fp-ts/Array";
import * as O from "fp-ts/Option";
import * as E from "fp-ts/Either";
import * as Map from "fp-ts/Map";
import * as R from "fp-ts/Record";
import * as Str from "fp-ts/string";
import { mapFst } from "fp-ts/Tuple";
import { eqStrict } from "fp-ts/Eq";
const unknownSerialize = (x) => {
return O.tryCatch(() => Sum.serialize(x));
};
const unsafeGetTags = Object.keys;
const union1 = (xs, name = "union1") => xs[1] === undefined
? new t.UnionType(name, xs[0].is, xs[0].validate, xs[0].encode, xs)
: t.union(xs, name);
const foldToUnion = (f) => (cs, name = "Sum union") => pipe(cs, Object.entries, A.map(([k, v]) => f([k, v])), ([x, ...ys]) => union1([x, ...ys], name));
export const nullaryFrom = (to) => (from) => new t.Type(`nullaryFrom(${from.name})`, t.null.is, (i, c) => pipe(from.validate(i, c), E.map(constant(null))), constant(to));
export const nullaryFromEmpty = nullaryFrom(undefined)(t.union([t.undefined, t.null, t.type({})]));
export const getSerializedCodec = () => (cs, name = "Serialized Sum") => foldToUnion(flow(mapFst(t.literal), xs => t.tuple(xs)))(cs, name);
export const getCodecFromSerialized = (sum) => (cs, name = "Sum") => {
const sc = getSerializedCodec()(cs, name);
return new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(sc.is)), flow(sc.validate, E.map(Sum.deserialize(sum))), flow(Sum.serialize, sc.encode));
};
export const getCodec = (sum) => (cs, name = "Sum") => {
const sc = getSerializedCodec()(cs, name);
return new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(sc.is)), (i, c) => pipe(i, unknownSerialize, O.matchW(() => t.failure(i, c), E.right), E.chain(sc.decode), E.map(Sum.deserialize(sum))), flow(Sum.serialize, sc.encode, Sum.deserialize(sum)));
};
export const getCodecFromMappedNullaryTag = (sum) => (from, to) => (tags, name = "Sum Mapped Tag") => {
const isKnownTag = (x) => tags.includes(x);
return new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(y => isKnownTag(y[0]) && y[1] === null)), (x, ctx) => pipe(x, from, O.match(() => t.failure(x, ctx), k => t.success(Sum.deserialize(sum)([k, null])))), flow(Sum.serialize, x => x[0], to));
};
export class MappedType extends t.Type {
Map;
_tag = "@unsplash/sum-types-io-ts/MappedType";
constructor(name, is, validate, encode, Map) {
super(name, is, validate, encode);
this.Map = Map;
}
}
export const getCodecFromPrimitiveMappedNullaryTag = (sum) => (tos, name = "Sum Primitive Mapped Tag") => {
const froms = pipe(tos, R.reduceWithIndex(Str.Ord)(new globalThis.Map(), (k, xs, v) => Map.upsertAt(eqStrict)(v, k)(xs)));
const codec = getCodecFromMappedNullaryTag(sum)(x => Map.lookup(eqStrict)(x)(froms), x => tos[x])(unsafeGetTags(tos), name);
return new MappedType(codec.name, codec.is, codec.validate, codec.encode, tos);
};
export const getCodecFromNullaryTag = (sum) => (tags, name = "Sum Tag") => getCodecFromPrimitiveMappedNullaryTag(sum)(pipe(tags, A.reduce({}, (xs, y) => R.upsertAt(y, y)(xs))), name);
const getExternallyTaggedMemberCodec = (sum) => (k) => (vc, name = "Sum") => new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(y => y[0] === k && vc.is(y[1]))), (i, ctx) => pipe(typeof i === "object" && i !== null && k in i
? E.right(i)
: t.failure(i, ctx, `Missing key "${k}"`), E.chain(x => t.strict({ [k]: vc }).validate(x, ctx)), E.map(x => Sum.deserialize(sum)([k, x[k]]))), flow(Sum.serialize, ([k, v]) => ({ [k]: vc.encode(v) })));
export const getExternallyTaggedCodec = (sum) => (cs, name = "Sum") => foldToUnion(([tag, codec]) => getExternallyTaggedMemberCodec(sum)(tag)(codec, name))(cs, name);
const getAdjacentlyTaggedMemberCodec = (tagKey) => (valueKey) => (sum) => (tag) => (vc, name = "Sum") => new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(y => y[0] === tag && vc.is(y[1]))), (i, ctx) => pipe(t
.strict({ [tagKey]: t.literal(tag), [valueKey]: vc })
.validate(i, ctx), E.map(x => Sum.deserialize(sum)([
tag,
x[valueKey],
]))), flow(Sum.serialize, ([k, v]) => ({
[tagKey]: k,
[valueKey]: vc.encode(v),
})));
export const getAdjacentlyTaggedCodec = (tagKey) => (valueKey) => (sum) => (cs, name = "Sum") => foldToUnion(([tag, codec]) => getAdjacentlyTaggedMemberCodec(tagKey)(valueKey)(sum)(tag)(codec, name))(cs, name);
const getUntaggedMemberCodec = (sum) => (k) => (c, name = "Sum") => new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(y => y[0] === k && c.is(y[1]))), (i, ctx) => pipe(c.validate(i, ctx), E.map(v => Sum.deserialize(sum)([k, v]))), flow(Sum.serialize, x => x[1], c.encode));
export const getUntaggedCodec = (sum) => (cs, name = "Sum") => foldToUnion(([tag, codec]) => getUntaggedMemberCodec(sum)(tag)(codec, name))(cs, name);
const getInternallyTaggedMemberCodec = (tagKey) => (sum) => (tag) => (vc, name = "Sum") => new t.Type(name, (x) => pipe(unknownSerialize(x), O.exists(y => y[0] === tag && vc.is(y[1]))), (i, ctx) => pipe(t.type({ [tagKey]: t.literal(tag) }).validate(i, ctx), E.chain(x => vc.validate(x, ctx)), E.map(x => Sum.deserialize(sum)([tag, x]))), flow(Sum.serialize, ([k, v]) => ({
[tagKey]: k,
...vc.encode(v),
})));
export const getInternallyTaggedCodec = (tagKey) => (sum) => (cs, name = "Sum") => foldToUnion(([tag, codec]) => getInternallyTaggedMemberCodec(tagKey)(sum)(tag)(codec, name))(cs, name);