pragmatic-fp-ts
Version:
Opinionated functional programming library with easy use in mind
131 lines (130 loc) • 5.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.from = void 0;
const all_1 = require("./all");
const Either_1 = require("./Either");
const getValueOr_1 = require("./getValueOr");
const isDataObject_1 = require("./isDataObject");
const isFunction_1 = require("./isFunction");
const isNil_1 = require("./isNil");
const isNumber_1 = require("./isNumber");
const isString_1 = require("./isString");
const map_1 = require("./map");
const values_1 = require("./values");
const nameProp = "__name";
const typeName = (x) => Array.isArray(x) ? "array" : x === null ? "null object" : typeof x;
const typeError = (validator, x) => {
const expectedType = (0, isString_1.isString)(validator) ? validator : getName(validator);
const receivedType = typeName(x);
return new Error(`expected ${expectedType}, got ${receivedType} ${JSON.stringify(x)}`);
};
const parseData = (x, decode) => {
const parse = (0, isFunction_1.isFunction)(decode)
? decode
: decode === true
? JSON.parse
: undefined;
return (0, isString_1.isString)(x) && parse ? parse(x) : x;
};
const Validator = (validate, { decode } = { decode: false }) => assignName(getName(validate), (x) => {
const xx = decode ? parseData(x, decode) : x;
return validate(xx) ? (0, Either_1.right)(xx) : (0, Either_1.left)(typeError(validate, x));
});
const assignName = (value, x) => {
Object.defineProperty(x, nameProp, { value });
return x;
};
const getName = (x) => x[nameProp];
const joinArrayReasons = (coll) => {
const badValues = coll
.reduce((msgs, m, idx) => {
var _a;
if (m.isLeft())
msgs.push(`${idx}: ${(_a = m.getReason()) === null || _a === void 0 ? void 0 : _a.message}`);
return msgs;
}, [])
.join(", ");
return `[${badValues}]`;
};
const liftArray = (coll) => (0, all_1.all)(Either_1.isRight, coll) ? (0, Either_1.right)(coll.map(liftValue)) : (0, Either_1.left)(new Error(joinArrayReasons(coll)));
const liftValue = (v) => Array.isArray(v) ? liftArray(v) : (0, isDataObject_1.isDataObject)(v) ? liftRecord(v) : (0, getValueOr_1.getValueOr)(undefined, v);
const joinRecordReasons = (coll) => {
const badFields = coll
.reduce((msgs, [key, value]) => {
var _a;
if ((0, Either_1.isLeft)(value))
msgs.push(`${key}: ${(_a = value.getReason()) === null || _a === void 0 ? void 0 : _a.message}`);
return msgs;
}, [])
.join(", ");
return `{${badFields}}`;
};
const liftRecord = (data) => (0, all_1.all)(Either_1.isRight, (0, values_1.values)(data))
? (0, Either_1.right)((0, map_1.map)(liftValue, data))
: (0, Either_1.left)(new Error(joinRecordReasons(Object.entries(data))));
const array = (validate) => {
const contentType = `Array<${getName(validate)}>`;
return assignName(contentType, (x, { decode } = { decode: false }) => {
const xx = decode ? parseData(x, decode) : x;
return Array.isArray(xx) ? liftValue(xx.map(validate)) : (0, Either_1.left)(typeError(contentType, x));
});
};
const record = (schema) => {
const recordDataType = Object.entries(schema)
.map(([k, v]) => `${k}: ${getName(v)}`)
.join(", ");
const recordType = `Record<{${recordDataType}}>`;
return assignName(recordType, (x, { decode } = { decode: false }) => {
const validateRecord = (y) => Object.fromEntries(Object.entries(schema).map(([k, decode]) => [k, decode(y[k])]));
const xx = decode ? parseData(x, decode) : x;
return (0, isDataObject_1.isDataObject)(xx) ? liftValue(validateRecord(xx)) : (0, Either_1.left)(typeError(recordType, x));
});
};
const dictionary = (validate) => {
const dictType = `Dictionary<${getName(validate)}>`;
return assignName(dictType, (x, { decode } = { decode: false }) => {
const decodeDict = (o) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, validate(v)]));
const xx = decode ? parseData(x, decode) : x;
return (0, isDataObject_1.isDataObject)(xx) ? liftValue(decodeDict(xx)) : (0, Either_1.left)(typeError(dictType, x));
});
};
const from = (name, validate) => Validator(assignName(name, validate));
exports.from = from;
const any = (0, exports.from)("any", (_) => true);
const unknown = (0, exports.from)("unknown", (_) => true);
const string = (0, exports.from)("string", isString_1.isString);
const number = (0, exports.from)("number", isNumber_1.isNumber);
const null_ = (0, exports.from)("null", (x) => x === null);
const undefined_ = (0, exports.from)("undefined", (x) => x === undefined);
const bool = (0, exports.from)("boolean", (x) => x === true || x === false);
const nil = (0, exports.from)("nil", isNil_1.isNil);
const dateString = (0, exports.from)("ISO date string", (x) => (0, isString_1.isString)(x) && String(new Date(x)) !== "Invalid Date");
const enum_ = (allowed, name = `one of [${allowed.join(",")}]`) => {
const lookup = new Set(allowed);
return (0, exports.from)(name !== null && name !== void 0 ? name : `one of [${allowed.join(",")}]`, (x) => lookup.has(x));
};
const oneOf = (...args) => {
const typeNames = args.map(getName).join(" | ");
return (0, exports.from)(`[${typeNames}]`, (x) => args.some((test) => test(x).isRight()));
};
const optional = (type_) => oneOf(type_, undefined_);
const nullable = (type_) => oneOf(type_, null_);
exports.default = {
from: exports.from,
any,
array,
boolean: bool,
dateString: dateString,
dictionary,
enum: enum_,
nil,
number,
null: null_,
nullable,
oneOf,
optional,
record,
string,
undefined: undefined_,
unknown,
};