UNPKG

@prismatic-io/spectral

Version:

Utility library for building Prismatic connectors and code-native integrations

308 lines (307 loc) 13.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.evaluate = exports.dateIsEqual = exports.dateIsBefore = exports.dateIsAfter = exports.evaluatesNotEmpty = exports.evaluatesEmpty = exports.evaluatesNull = exports.evaluatesFalse = exports.evaluatesTrue = exports.isDeepEqual = exports.isEqual = exports.parseDate = exports.contains = exports.parseValue = exports.validate = void 0; const types_1 = require("./types"); const date_fns_1 = require("date-fns"); const isEqualWith_1 = __importDefault(require("lodash/isEqualWith")); const isEqual_1 = __importDefault(require("lodash/isEqual")); const util_1 = __importDefault(require("../util")); const validate = (expression) => { if (!Array.isArray(expression)) { return [false, `Invalid expression syntax: '${expression}'`]; } const [operator] = expression; if (operator in types_1.BooleanOperator) { if (operator === types_1.BooleanOperator.and || operator === types_1.BooleanOperator.or) { const [, ...predicates] = expression; return predicates.reduce((previous, current) => { const [valid, error] = (0, exports.validate)(current); if (!valid) { return [valid, [previous[1], error].filter(Boolean).join(", ")]; } return previous; }, [true]); } return [false, `Invalid expression syntax: '${expression}'`]; } else if (operator in types_1.UnaryOperator || operator in types_1.BinaryOperator) { return [true]; } return [false, `Invalid expression syntax: '${expression}'`]; }; exports.validate = validate; /** Convert stringified objects/values back to their native value, all other * values just pass through unaltered. */ const parseValue = (value) => { try { return typeof value === "string" ? JSON.parse(value) : value; } catch (_a) { return value; } }; exports.parseValue = parseValue; const contains = (container, containee) => { if (typeof container === "string") { // Substring check. // NOTE: JS is real fast and loose with types here, happily returning true // for things like "123".includes(1), but we have to lie to TS. return container.includes(`${containee}`); } if (typeof container === "object" && container !== null) { if (Array.isArray(container)) { // Array member check. return container.includes(containee) || container.includes((0, exports.parseValue)(containee)); } // Object attribute check (set membership). return Object.prototype.hasOwnProperty.call(container, `${containee}`); } throw new Error("Invalid arguments set to 'contains'."); }; exports.contains = contains; const parseDate = (value) => { if (value instanceof Date && (0, date_fns_1.isValid)(value)) { return value; } if (typeof value === "number") { return new Date(value); } if (typeof value === "string") { const dateFormats = ["yyyy", "yyyy-MM-dd"]; for (const format of dateFormats) { const parsed = (0, date_fns_1.parse)(value, format, 0); if ((0, date_fns_1.isValid)(parsed)) { return parsed; } } const isoParsed = (0, date_fns_1.parseISO)(value); if ((0, date_fns_1.isValid)(isoParsed)) { return isoParsed; } } throw new Error("Invalid argument type"); }; exports.parseDate = parseDate; const isEqual = (left, right) => left == right || (0, isEqualWith_1.default)(left, right, (objectA, objectB) => { if (typeof objectA === "object" || typeof objectB === "object") { /** * `undefined` will fall back to the default isEqual behavior. * @see -> (https://lodash.com/docs/4.17.15#isEqualWith) */ return undefined; } return objectA == objectB; }); exports.isEqual = isEqual; const isDeepEqual = (left, right) => { return (0, isEqual_1.default)(left, right); }; exports.isDeepEqual = isDeepEqual; const evaluatesTrue = (value) => { return typeof value === "string" ? ["t", "true", "y", "yes"].includes(value) : value; }; exports.evaluatesTrue = evaluatesTrue; const evaluatesFalse = (value) => { return typeof value === "string" ? ["f", "false", "n", "no"].includes(value) : value; }; exports.evaluatesFalse = evaluatesFalse; const evaluatesNull = (value) => { const nullValues = [undefined, null, 0, Number.NaN, false, ""]; return nullValues.includes(value); }; exports.evaluatesNull = evaluatesNull; const evaluatesEmpty = (value) => { if (Array.isArray(value)) { return value.length === 0; } if (typeof value === "string") { return value.length === 0; } throw new Error("Please provide an array or string"); }; exports.evaluatesEmpty = evaluatesEmpty; const evaluatesNotEmpty = (value) => { if (Array.isArray(value)) { return value.length > 0; } if (typeof value === "string") { return value.length > 0; } throw new Error("Please provide an array or string"); }; exports.evaluatesNotEmpty = evaluatesNotEmpty; const dateIsAfter = (left, right) => { return (0, date_fns_1.isAfter)(util_1.default.types.toDate(left), util_1.default.types.toDate(right)); }; exports.dateIsAfter = dateIsAfter; const dateIsBefore = (left, right) => { return (0, date_fns_1.isBefore)(util_1.default.types.toDate(left), util_1.default.types.toDate(right)); }; exports.dateIsBefore = dateIsBefore; const dateIsEqual = (left, right) => { return (0, date_fns_1.isEqual)(util_1.default.types.toDate(left), util_1.default.types.toDate(right)); }; exports.dateIsEqual = dateIsEqual; const evaluate = (expression) => { const [valid, message] = (0, exports.validate)(expression); if (!valid) { throw new Error(message); } const [operator] = expression; if (operator in types_1.BooleanOperator) { const predicates = expression.slice(1); switch (operator) { case types_1.BooleanOperator.and: for (const predicate of predicates) { if (!(0, exports.evaluate)(predicate)) return false; } return true; case types_1.BooleanOperator.or: for (const predicate of predicates) { if ((0, exports.evaluate)(predicate)) return true; } return false; default: throw new Error(`Invalid operator: '${operator}'`); } } else if (operator in types_1.UnaryOperator) { const [, leftTerm] = expression; const left = (0, exports.parseValue)(leftTerm); // attempt to compare try { switch (operator) { case types_1.UnaryOperator.isTrue: if (typeof left === "string") { const lowerValue = left.toLowerCase(); if ((0, exports.evaluatesTrue)(lowerValue)) { return true; } else if ((0, exports.evaluatesFalse)(lowerValue)) { return false; } } return !!left; case types_1.UnaryOperator.isFalse: if (typeof left === "string") { const lowerValue = left.toLowerCase(); if ((0, exports.evaluatesTrue)(lowerValue)) { return false; } else if ((0, exports.evaluatesFalse)(lowerValue)) { return true; } } return !left; case types_1.UnaryOperator.doesNotExist: return (0, exports.evaluatesNull)(left); case types_1.UnaryOperator.exists: return !(0, exports.evaluatesNull)(left); case types_1.UnaryOperator.isEmpty: if (Array.isArray(left)) { return (0, exports.evaluatesEmpty)(left); } // leftTerm is used here, since "123" would be cast to 123 and would not be a string if (typeof leftTerm === "string") { return (0, exports.evaluatesEmpty)(leftTerm); } throw new Error("Please provide an array or string"); case types_1.UnaryOperator.isNotEmpty: if (Array.isArray(left)) { return left.length > 0; } if (typeof leftTerm === "string") { return leftTerm.length > 0; } throw new Error("Please provide an array or string"); default: throw new Error(`Invalid operator: '${operator}'`); } } catch (error) { throw new Error(`Incompatible comparison arguments, ${error}`); } } else { const [, leftTerm, rightTerm] = expression; let left = null; let right = null; // attempt to convert numeric, array, or object strings as json to native objects, // otherwise fall back to original value if (operator in [types_1.BinaryOperator.dateTimeAfter, types_1.BinaryOperator.dateTimeBefore, types_1.BinaryOperator.dateTimeSame]) { left = (0, exports.parseDate)(leftTerm); right = (0, exports.parseDate)(rightTerm); } else { left = (0, exports.parseValue)(leftTerm); right = (0, exports.parseValue)(rightTerm); } // attempt to compare try { switch (operator) { case types_1.BinaryOperator.equal: return (0, exports.isEqual)(left, right); case types_1.BinaryOperator.notEqual: return !(0, exports.isEqual)(left, right); case types_1.BinaryOperator.greaterThan: return left > right; case types_1.BinaryOperator.greaterThanOrEqual: return left >= right; case types_1.BinaryOperator.lessThan: return left < right; case types_1.BinaryOperator.lessThanOrEqual: return left <= right; case types_1.BinaryOperator.in: return (0, exports.contains)(right, leftTerm); case types_1.BinaryOperator.notIn: return !(0, exports.contains)(right, leftTerm); case types_1.BinaryOperator.exactlyMatches: return left === right || (0, exports.isDeepEqual)(left, right); case types_1.BinaryOperator.doesNotExactlyMatch: return !(left === right || (0, exports.isDeepEqual)(left, right)); case types_1.BinaryOperator.startsWith: return `${right}`.startsWith(`${left}`); case types_1.BinaryOperator.doesNotStartWith: return !`${right}`.startsWith(`${left}`); case types_1.BinaryOperator.endsWith: return `${right}`.endsWith(`${left}`); case types_1.BinaryOperator.doesNotEndWith: return !`${right}`.endsWith(`${left}`); case types_1.BinaryOperator.dateTimeAfter: return (0, exports.dateIsAfter)(util_1.default.types.toDate(left), util_1.default.types.toDate(right)); case types_1.BinaryOperator.dateTimeBefore: return (0, exports.dateIsBefore)(util_1.default.types.toDate(left), util_1.default.types.toDate(right)); case types_1.BinaryOperator.dateTimeSame: return (0, exports.dateIsEqual)(util_1.default.types.toDate(left), util_1.default.types.toDate(right)); default: throw new Error(`Invalid operator: '${operator}'`); } } catch (error) { throw new Error(`Incompatible comparison arguments, ${error}`); } } }; exports.evaluate = evaluate; __exportStar(require("./types"), exports);