@prismatic-io/spectral
Version:
Utility library for building Prismatic connectors and code-native integrations
308 lines (307 loc) • 13.4 kB
JavaScript
;
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);