UNPKG

@unsplash/sum-types-io-ts

Version:
70 lines (69 loc) 5.5 kB
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);