UNPKG

io-ts-extra

Version:

Adds pattern matching, optional properties, and several other helpers and types, to io-ts.

154 lines 5.96 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.collect = exports.matcher = exports.match = void 0; const Either = __importStar(require("fp-ts/lib/Either")); const t = __importStar(require("io-ts")); const util_1 = require("./util"); const shorthand_1 = require("./shorthand"); const maybeMatchObject = (obj, cases) => { for (const [type, map] of cases) { const decoded = type.decode(obj); if (decoded._tag === 'Right') { return Either.right(map(decoded.right)); } } return Either.left({ noMatchFoundFor: obj, types: cases.map(c => c[0]) }); }; const matchObject = (obj, cases) => { const either = maybeMatchObject(obj, cases); if (either._tag === 'Right') { return either.right; } util_1.RichError.throw({ noMatchFoundFor: obj, types: cases.map(c => c[0]) }); }; const patternMatcher = (cases, obj) => ({ case: (type, ...fns) => { const codec = shorthand_1.codecFromShorthand(type); const refined = fns.length > 1 ? t.refinement(codec, fns[0]) : codec; return patternMatcher(cases.concat([[refined, fns[fns.length - 1]]]), obj); }, default: (map) => patternMatcher(cases.concat([[t.any, map]]), obj), get: () => matchObject(obj, cases), }); /** * Match an object against a number of cases. Loosely based on Scala's pattern matching. * * @example * // get a value which could be a string or a number: * const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10 * const stringified = match(value) * .case(String, s => `the message is ${s}`) * .case(7, () => 'exactly seven') * .case(Number, n => `the number is ${n}`) * .get() * * @description * Under the hood, io-ts is used for validation. The first argument can be a "shorthand" for a type, * but you can also pass in io-ts codecs directly for more complex types: * * @example * // get a value which could be a string or a number: * const value = Math.random() < 0.5 ? 'foo' : 123 * const stringified = match(value) * .case(t.number, n => `the number is ${n}`) * .case(t.string, s => `the message is ${s}`) * .get() * * @description * you can use a predicate function or `t.refinement` for the equivalent of scala's `case x: Int if x > 2`: * * @example * // value which could be a string, or a real number in [0, 10): * const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10 * const stringified = match(value) * .case(Number, n => n > 2, n => `big number: ${n}`) * .case(Number, n => `small number: ${n}`) * .default(x => `not a number: ${x}`) * .get() * * @example * // value which could be a string, or a real number in [0, 10): * const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10 * const stringified = match(value) * .case(t.refinement(t.number, n => n > 2), n => `big number: ${n}`) * .case(t.number, n => `small number: ${n}`) * .default(x => `not a number: ${x}`) * .get() * * @description * * note: when using predicates or `t.refinement`, the type being refined is not considered exhaustively matched, * so you'll usually need to add a non-refined option, or you can also use `.default` as a fallback * case (the equivalent of `.case(t.any, ...)`) * * @param obj the object to be pattern-matched */ const match = (obj) => patternMatcher([], obj); exports.match = match; /** * Like @see match but no object is passed in when constructing the case statements. * Instead `.get` is a function into which a value should be passed. * * @example * const Email = t.type({sender: t.string, subject: t.string, body: t.string}) * const SMS = t.type({from: t.string, content: t.string}) * const Message = t.union([Email, SMS]) * type Message = typeof Message._A * * const content = matcher<MessageType>() * .case(SMS, s => s.content) * .case(Email, e => e.subject + '\n\n' + e.body) * .get({from: '123', content: 'hello'}) * * expect(content).toEqual('hello') * * @description * The function returned by `.get` is stateless and has no `this` context, * you can store it in a variable and pass it around: * * @example * const getContent = matcher<Message>() * .case(SMS, s => s.content) * .case(Email, e => e.subject + '\n\n' + e.body) * .get * * const allMessages: Message[] = getAllMessages(); * const contents = allMessages.map(getContent); */ const matcher = () => matcherRecursive([]); exports.matcher = matcher; const matcherRecursive = (cases) => ({ case: (type, ...fns) => { const codec = shorthand_1.codecFromShorthand(type); const refined = fns.length > 1 ? t.refinement(codec, fns[0]) : codec; return matcherRecursive(cases.concat([[refined, fns[fns.length - 1]]])); }, default: (map) => matcherRecursive(cases.concat([[t.any, map]])), get: (obj) => matchObject(obj, cases), tryGet: (obj) => maybeMatchObject(obj, cases), }); const collect = (items, partialFunc) => items .map(partialFunc) .filter((o) => o._tag === 'Right') .map(o => o.right); exports.collect = collect; //# sourceMappingURL=match.js.map