UNPKG

io-ts-extra

Version:

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

153 lines 6.7 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.strict = exports.regexp = exports.instanceOf = exports.sparseType = exports.optional = void 0; const io_ts_1 = require("io-ts"); // eslint-disable-next-line @typescript-eslint/no-duplicate-imports const t = __importStar(require("io-ts")); const function_1 = require("fp-ts/lib/function"); const E = __importStar(require("fp-ts/lib/Either")); /** * unions the passed-in type with `null` and `undefined`. * @see sparseType */ const optional = (rt, name) => { const unionType = io_ts_1.union([rt, io_ts_1.nullType, io_ts_1.undefined], name || rt.name + '?'); return Object.assign(unionType, { optional: true }); }; exports.optional = optional; /** * Can be used much like `t.type` from io-ts, but any property types wrapped with `optional` from * this package need not be supplied. Roughly equivalent to using `t.intersection` with `t.type` and `t.partial`. * @example * const Person = sparseType({ * name: t.string, * age: optional(t.number), * }) * * // no error - `age` is optional * const bob: typeof Person._A = { name: 'bob' } * @param props equivalent to the `props` passed into `t.type` * @returns a type with `props` field, so the result can be introspected similarly to a type built with * `t.type` or `t.partial` - which isn't the case if you manually use `t.intersection([t.type({...}), t.partial({...})])` */ const sparseType = (props, name) => { let someOptional = false; let someRequired = false; const optionalProps = {}; const requiredProps = {}; for (const key of Object.keys(props)) { const val = props[key]; if (val.optional) { someOptional = true; optionalProps[key] = val; } else { someRequired = true; requiredProps[key] = val; } } const computedName = name || getInterfaceTypeName(props); if (someOptional && someRequired) { return Object.assign(io_ts_1.intersection([io_ts_1.type(requiredProps), io_ts_1.partial(optionalProps)], computedName), { props }); } else if (someOptional) { return io_ts_1.partial(props, computedName); } return io_ts_1.type(props, computedName); }; exports.sparseType = sparseType; const getNameFromProps = (props) => Object.keys(props) .map(k => `${k}: ${props[k].name}`) .join(', '); const getInterfaceTypeName = (props) => { return `{ ${getNameFromProps(props)} }`; }; /** * Validates that a value is an instance of a class using the `instanceof` operator * @example * const DateType = instanceOf(Date) * DateType.is(new Date()) // right(Date(...)) * DateType.is('abc') // left(...) */ const instanceOf = (cns) => new t.Type(`InstanceOf<${cns.name || 'anonymous'}>`, (v) => v instanceof cns, (s, c) => (s instanceof cns ? t.success(s) : t.failure(s, c)), t.identity); exports.instanceOf = instanceOf; /** * A type which validates its input as a string, then decodes with `String.prototype.match`, * succeeding with the RegExpMatchArray result if a match is found, and failing if no match is found. * * @example * const AllCaps = regexp(/\b([A-Z]+)\b/) * AllCaps.decode('HELLO') // right([ 'HELLO', index: 0, input: 'HELLO' ]) * AllCaps.decode('hello') // left(...) * AllCaps.decode(123) // left(...) */ exports.regexp = (() => { const RegExpMatchArrayStructure = t.intersection([ t.array(t.string), t.type({ index: t.number, input: t.string, }), ]); return (v) => { const RegExpMatchArrayDecoder = new t.Type(`RegExp<${v.source}>`, RegExpMatchArrayStructure.is, (s, c) => { // note: this implementation used to be much simpler: // return RegExpMatchArrayStructure.validate(s.match(v), c) // but a change to io-ts means that `t.type` won't validate an array, even if // the array does have the properties required by the t.type. const [array, structure] = RegExpMatchArrayStructure.types; const match = s.match(v); return function_1.pipe(match, E.fromNullable(t.failure(s, c, `No match found for regexp ${v}`)), E.mapLeft(e => e.left), E.chain(match => array.validate(match, c)), E.map((match) => ({ index: match.index, input: match.input })), E.chain(struct => structure.validate(struct, c)), E.chain(() => t.success(match))); }, val => val.input); return t.string.pipe(RegExpMatchArrayDecoder); }; })(); /** * Like `t.type`, but fails when any properties not specified in `props` are defined. * * @example * const Person = strict({name: t.string, age: t.number}) * * expectRight(Person.decode({name: 'Alice', age: 30})) * expectLeft(Person.decode({name: 'Bob', age: 30, unexpectedProp: 'abc'})) * expectRight(Person.decode({name: 'Bob', age: 30, unexpectedProp: undefined})) * * @param props dictionary of properties, same as the input to `t.type` * @param name optional type name * * @description * note: * - additional properties explicitly set to `undefined` _are_ permitted. * - internally, `sparseType` is used, so optional properties are supported. */ const strict = (props, name) => { const codec = exports.sparseType(props); return new t.Type(name || `Strict<${codec.name}`, (val) => codec.is(val) && Object.keys(val).every(k => k in props), (val, ctx) => { if (typeof val !== 'object' || !val) { return codec.validate(val, ctx); } const stricterProps = Object.keys(val).reduce((acc, next) => (Object.assign(Object.assign({}, acc), { [next]: props[next] || t.undefined })), props); return exports.sparseType(stricterProps).validate(val, ctx); }, codec.encode); }; exports.strict = strict; //# sourceMappingURL=combinators.js.map