UNPKG

@prismatic-io/spectral

Version:

Utility library for building Prismatic connectors and code-native integrations

571 lines (570 loc) 24.8 kB
"use strict"; /** * The `util` module provides a set of functions commonly needed to author custom components. * Many functions in the `util` module are used to coerce data into a particular type, and can be accessed through `util.types`. * For example, `util.types.toInt("5.5")` will return an integer, `5`. */ 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.toObject = exports.lowerCaseHeaders = exports.isObjectWithTruthyKeys = exports.isObjectWithOneTruthyKey = void 0; /** */ const parseISO_1 = __importDefault(require("date-fns/parseISO")); const isValid_1 = __importDefault(require("date-fns/isValid")); const isDate_1 = __importDefault(require("date-fns/isDate")); const fromUnixTime_1 = __importDefault(require("date-fns/fromUnixTime")); const omitBy_1 = __importDefault(require("lodash/omitBy")); const safe_stable_stringify_1 = require("safe-stable-stringify"); const valid_url_1 = require("valid-url"); const isObjectWithOneTruthyKey = (value, keys) => { return (value !== null && typeof value === "object" && keys.some((key) => key in value && Boolean(value === null || value === void 0 ? void 0 : value[key]))); }; exports.isObjectWithOneTruthyKey = isObjectWithOneTruthyKey; const isObjectWithTruthyKeys = (value, keys) => { return (value !== null && typeof value === "object" && keys.every((key) => key in value && Boolean(value === null || value === void 0 ? void 0 : value[key]))); }; exports.isObjectWithTruthyKeys = isObjectWithTruthyKeys; /** * This function checks if value is an Element. * `util.types.isElement({key: "foo"})` and `util.types.isElement({key: "foo", label: "Foo"})` return true. * @param value The variable to test. * @returns This function returns true or false, depending on if `value` is an Element. */ const isElement = (value) => (0, exports.isObjectWithTruthyKeys)(value, ["key"]); /** * @param value The value to test * @returns This function returns true if the type of `value` is an ObjectSelection, or false otherwise. */ const isObjectSelection = (value) => { if (typeof value === "string" && isJSON(value)) { return isObjectSelection(JSON.parse(value)); } return Array.isArray(value) && value.every((item) => (0, exports.isObjectWithTruthyKeys)(item, ["object"])); }; /** * This function coerces a provided value into an ObjectSelection if possible. * @param value The value to coerce to ObjectSelection. * @returns This function returns the the value as an ObjectSelection if possible. */ const toObjectSelection = (value) => { if (typeof value === "string" && isJSON(value)) { return toObjectSelection(JSON.parse(value)); } if (isObjectSelection(value)) { return value; } throw new Error(`Value '${typeof value === "string" ? value : JSON.stringify(value)}' cannot be coerced to ObjectSelection.`); }; /** * @param value The value to test * @returns This function returns true if the type of `value` is an ObjectFieldMap, or false otherwise. */ const isObjectFieldMap = (value) => { if (typeof value === "string" && isJSON(value)) { return isObjectFieldMap(JSON.parse(value)); } if (Boolean(value) && typeof value === "object") { const { fields } = value; return (Array.isArray(fields) && fields.every((item) => (0, exports.isObjectWithTruthyKeys)(item, ["field"]) && isElement(item === null || item === void 0 ? void 0 : item.field))); } return false; }; /** * This function coerces a provided value into an ObjectFieldMap if possible. * @param value The value to coerce to ObjectFieldMap. * @returns This function returns the the value as an ObjectFieldMap if possible. */ const toObjectFieldMap = (value) => { if (typeof value === "string" && isJSON(value)) { return toObjectFieldMap(JSON.parse(value)); } if (isObjectFieldMap(value)) { return value; } throw new Error(`Value '${typeof value === "string" ? value : JSON.stringify(value)}' cannot be coerced to ObjectFieldMap.`); }; /** * @param value The value to test * @returns This function returns true if the type of `value` is a JSONForm, or false otherwise. */ const isJSONForm = (value) => { if (typeof value === "string" && isJSON(value)) { return isJSONForm(JSON.parse(value)); } return (0, exports.isObjectWithTruthyKeys)(value, ["schema", "uiSchema", "data"]); }; /** * This function coerces a provided value into a JSONForm if possible. * @param value The value to coerce to JSONForm. * @returns This function returns the the value as a JSONForm if possible. */ const toJSONForm = (value) => { if (typeof value === "string" && isJSON(value)) { return toJSONForm(JSON.parse(value)); } if (isJSONForm(value)) { return value; } throw new Error(`Value '${typeof value === "string" ? value : JSON.stringify(value)}' cannot be coerced to JSONForm.`); }; /** * Determine if a variable is a boolean (true or false). * * - `util.types.isBool(false)` will return `true`, since `false` is a boolean. * - `util.types.isBool("Hello")` will return `false`, since `"Hello"` is not a boolean. * * @param value The variable to test. * @returns True if the value is a boolean, or false otherwise. */ const isBool = (value) => value === true || value === false; /** * Convert truthy (true, "t", "true", "y", "yes") values to boolean `true`, * and falsy (false, "f", "false", "n", "no") values to boolean `false`. * Truthy/falsy checks are case-insensitive. * * In the event that `value` is undefined or an empty string, a default value can be provided. * For example, `util.types.toBool('', true)` will return `true`. * * @param value The value to convert to a boolean. * @param defaultValue The value to return if `value` is undefined or an empty string. * @returns The boolean equivalent of the truthy or falsy `value`. */ const toBool = (value, defaultValue) => { if (isBool(value)) { return value; } if (typeof value === "string") { const lowerValue = value.toLowerCase(); if (["t", "true", "y", "yes"].includes(lowerValue)) { return true; } else if (["f", "false", "n", "no"].includes(lowerValue)) { return false; } } return Boolean(value || defaultValue); }; /** * This function checks if value is an integer. * `util.types.isInt(5)` returns true, while `util.types.isInt("5")` or `util.types.isInt(5.5)` returns false. * @param value The variable to test. * @returns This function returns true or false, depending on if `value` is an integer. */ const isInt = (value) => Number.isInteger(value); /** * This function converts a variable to an integer if possible. * `util.types.toInt(5.5)` will return `5`. `util.types.toInt("20.3")` will return `20`. * * In the event that `value` is undefined or an empty string, a default value can be provided. * For example, `util.types.toInt('', 1)` will return `1`. * * This function will throw an exception if `value` cannot be coerced to an integer. * @param value The value to convert to an integer. * @param defaultValue The value to return if `value` is undefined, an empty string, or not able to be coerced. * @returns This function returns an integer if possible. */ const toInt = (value, defaultValue) => { if (isInt(value)) return value; // Turn a float into an int if (typeof value === "number") { return ~~value; } if (typeof value === "string") { const intValue = Number.parseInt(value); if (!Number.isNaN(intValue)) { return intValue; } } if (typeof value === "undefined" || value === "") { return defaultValue || 0; } if (defaultValue) { return defaultValue; } throw new Error(`Value '${value}' cannot be coerced to int.`); }; /** * Determine if a variable is a number, or can easily be coerced into a number. * * - `util.types.isNumber(3.21)` will return `true`, since `3.21` is a number. * - `util.types.isBool("5.5")` will return `true`, since the string `"5.5"` can easily be coerced into a number. * - `util.types.isBool("Hello")` will return `false`, since `"Hello"` is not a number. * * @param value The variable to test. * @returns This function returns true if `value` can easily be coerced into a number, and false otherwise. */ const isNumber = (value) => !Number.isNaN(Number(value)); /** * This function coerces a value (number or string) into a number. * In the event that `value` is undefined or an empty string, a `defaultValue` can be provided, or zero will be returned. * If `value` is not able to be coerced into a number but is defined, an error will be thrown. * * - `util.types.toNumber("3.22")` will return the number `3.22`. * - `util.types.toNumber("", 5.5)` will return the default value `5.5`, since `value` was an empty string. * - `util.types.toNumber(null, 5.5)` will return the default value `5.5`, since `value` was `null`. * - `util.types.toNumber(undefined)` will return `0`, since `value` was undefined and no `defaultValue` was given. * - `util.types.toNumber("Hello")` will throw an error, since the string `"Hello"` cannot be coerced into a number. * @param value The value to turn into a number. * @param defaultValue The value to return if `value` is undefined or an empty string. * @returns This function returns the numerical version of `value` if possible, or the `defaultValue` if `value` is undefined or an empty string. */ const toNumber = (value, defaultValue) => { if (typeof value === "undefined" || value === "" || value === null) { return defaultValue || 0; } if (isNumber(value)) { return Number(value); } throw new Error(`Value '${value}' cannot be coerced to a number.`); }; /** * @param value The value to test * @returns This function returns true if the type of `value` is a bigint, or false otherwise. */ const isBigInt = (value) => typeof value === "bigint"; /** * This function coerces a provided value into a bigint if possible. * The provided `value` must be a bigint, integer, string representing an integer, or a boolean. * * - `util.types.toBigInt(3)` will return `3n`. * - `util.types.toBigInt("-5")` will return `-5n`. * - `util.types.toBigInt(true)` will return `1n` (and `false` will return `0n`). * - `util.types.toBigInt("5.5")` will throw an error, as `5.5` is not an integer. * @param value The value to coerce to bigint. * @returns This function returns the bigint representation of `value`. */ const toBigInt = (value) => { if (isBigInt(value)) { return value; } try { return BigInt(toString(value)); } catch (error) { throw new Error(`Value '${value}' cannot be coerced to bigint.`); } }; /** This function returns true if `value` is a variable of type `Date`, and false otherwise. */ const isDate = (value) => (0, isDate_1.default)(value); /** * This function parses an ISO date or UNIX epoch timestamp if possible, or throws an error if the value provided * cannot be coerced into a Date object. * * - `util.types.toDate(new Date('1995-12-17T03:24:00'))` will return `Date('1995-12-17T09:24:00.000Z')` since a `Date` object was passed in. * - `util.types.toDate('2021-03-20')` will return `Date('2021-03-20T05:00:00.000Z')` since a valid ISO date was passed in. * - `util.types.toDate(1616198400) will return `Date('2021-03-20T00:00:00.000Z')` since a UNIX epoch timestamp was passed in. * - `parseISODate('2021-03-20-05')` will throw an error since `value` was not a valid ISO date. * @param value The value to turn into a date. * @returns The date equivalent of `value`. */ const toDate = (value) => { if (isDate(value)) { return value; } if (isNumber(value) && toNumber(value)) { return (0, fromUnixTime_1.default)(toNumber(value)); } if (typeof value === "string") { const dt = (0, parseISO_1.default)(value); if ((0, isValid_1.default)(dt)) { return dt; } } throw new Error(`Value '${value}' cannot be coerced to date.`); }; /** * This function tests if the string provided is a valid URL, and returns `true` if the URL is valid. * Note: this function only tests that the string is a syntactically correct URL; it does not check * if the URL is web accessible. * * - `util.types.isUrl('https://prismatic.io')` will return true. * - `util.types.isUrl('https:://prismatic.io')` will return false due to the extraneous `:` symbol. * @param value The URL to test. * @returns This function returns true if `value` is a valid URL, and false otherwise. */ const isUrl = (value) => (0, valid_url_1.isWebUri)(value) !== undefined; /** * This function checks if value is a valid picklist. * * - `util.types.isPicklist(["value", new String("value")])` will return `true`. * * @param value The variable to test. * @returns This function returns true if `value` is a valid picklist. */ const isPicklist = (value) => Array.isArray(value) && (value.every(isString) || value.every(isElement)); /** * This function checks if value is a valid schedule. * * - `util.types.isSchedule({value: "00 00 * * 2,3"})` will return `true`. * - `util.types.isSchedule({value: "00 00 * * 2,3", scheduleType: "week", timeZone: "America/Chicago"})` will return `true`. * * @param value The variable to test. * @returns This function returns true if `value` is a valid schedule. */ const isSchedule = (value) => (0, exports.isObjectWithTruthyKeys)(value, ["value"]); /** * This function helps to transform key-value lists to objects. * This is useful for transforming inputs that are key-value collections into objects. * * For example, an input that is a collection might return `[{key: "foo", value: "bar"},{key: "baz", value: 5}]`. * If that array were passed into `util.types.keyValPairListToObject()`, an object would be returned of the form * `{foo: "bar", baz: 5}`. * @param kvpList An array of objects with `key` and `value` properties. * @param valueConverter Optional function to call for each `value`. */ const keyValPairListToObject = (kvpList, valueConverter) => { return (kvpList || []).reduce((result, { key, value }) => (Object.assign(Object.assign({}, result), { [key]: valueConverter ? valueConverter(value) : value })), {}); }; /** * This function tests if the object provided is a Prismatic `DataPayload` object. * A `DataPayload` object is an object with a `data` attribute that is a Buffer, and optional `contentType` attribute. * * @param value The value to test * @returns This function returns true if `value` is a DataPayload object, and false otherwise. */ const isBufferDataPayload = (value) => value instanceof Object && "data" in value && Buffer.isBuffer(value.data); /** * Many libraries for third-party API that handle binary files expect `Buffer` objects. * This function helps to convert strings, Uint8Arrays, and Arrays to a data structure * that has a Buffer and a string representing `contentType`. * * You can access the buffer like this: * `const { data, contentType } = util.types.toBufferDataPayload(someData);` * * If `value` cannot be converted to a Buffer, an error will be thrown. * @param value The string, Buffer, Uint8Array, or Array to convert to a Buffer. * @returns This function returns an object with two keys: `data`, which is a `Buffer`, and `contentType`, which is a string. */ const toBufferDataPayload = (value) => { if (isBufferDataPayload(value)) { return value; } if (typeof value === "string") { return { data: Buffer.from(value, "utf-8"), contentType: "text/plain", }; } if (value instanceof Buffer) { return { data: value, contentType: "application/octet-stream", }; } if (value instanceof Uint8Array) { return { data: Buffer.from(value), contentType: "application/octet-stream", }; } if (value instanceof Object || Array.isArray(value)) { const json = JSON.stringify(value); return { data: Buffer.from(json, "utf-8"), contentType: "application/json", }; } throw new Error(`Value '${value}' cannot be converted to a Buffer.`); }; /** * @deprecated This function tests if the object provided is a Prismatic `DataPayload` object. * A `DataPayload` object is an object with a `data` attribute, and optional `contentType` attribute. * * @param value The value to test * @returns This function returns true if `value` is a DataPayload object, and false otherwise. */ const isData = (value) => isBufferDataPayload(value); /** * @deprecated Many libraries for third-party API that handle binary files expect `Buffer` objects. * This function helps to convert strings, Uint8Arrays, and Arrays to a data structure * that has a Buffer and a string representing `contentType`. * * You can access the buffer like this: * `const { data, contentType } = util.types.toData(someData);` * * If `value` cannot be converted to a Buffer, an error will be thrown. * @param value The string, Buffer, Uint8Array, or Array to convert to a Buffer. * @returns This function returns an object with two keys: `data`, which is a `Buffer`, and `contentType`, which is a string. */ const toData = (value) => toBufferDataPayload(value); /** * This function checks if value is a string. * `util.types.isString("value")` and `util.types.isString(new String("value"))` return true. * @param value The variable to test. * @returns This function returns true or false, depending on if `value` is a string. */ const isString = (value) => typeof value === "string" || value instanceof String; /** * This function converts a `value` to a string. * If `value` is undefined or an empty string, an optional `defaultValue` can be returned. * * - `util.types.toString("Hello")` will return `"Hello"`. * - `util.types.toString(5.5)` will return `"5.5"`. * - `util.types.toString("", "Some default")` will return `"Some Default"`. * - `util.types.toString(undefined)` will return `""`. * @param value The value to convert to a string. * @param defaultValue A default value to return if `value` is undefined or an empty string. * @returns This function returns the stringified version fo `value`, or `defaultValue` in the case that `value` is undefined or an empty string. */ const toString = (value, defaultValue = "") => `${value !== null && value !== void 0 ? value : defaultValue}`; /** * This function checks if value is a valid connection. * @param value The variable to test. * @returns This function returns true or false, depending on if `value` is a valid connection. */ const isConnection = (value) => { if (typeof value === "string" && isJSON(value)) { return isConnection(JSON.parse(value)); } if (Boolean(value) && typeof value === "object") { const { inputs } = value; if ((0, exports.isObjectWithTruthyKeys)(value, ["key", "label", "oauth2Type"])) { return ((0, exports.isObjectWithTruthyKeys)(inputs, ["authorizeUrl", "tokenUrl", "clientId", "clientSecret"]) || (0, exports.isObjectWithTruthyKeys)(inputs, ["tokenUrl", "clientId", "clientSecret"])); } return (0, exports.isObjectWithTruthyKeys)(value, ["key", "label"]) && typeof inputs === "object"; } return false; }; /** * This function returns true if `value` resembles the shape of JSON, and false otherwise. * * - `isJSON(undefined) will return `false` * - `isJSON(null) will return `true` * - `isJSON("") will return `false` * - `isJSON(5) will return `true` * - `isJSON('{"name":"John", "age":30, "car":null}') will return `true` * @param value The value to test against * @returns This function returns a boolean, dependant on whether `value` can be parsed to JSON. * */ const isJSON = (value) => { try { JSON.parse(value); return true; } catch (_a) { return false; } }; /** * This function accepts an arbitrary object/value and safely serializes it (handles cyclic references). * * @param value Arbitrary object/value to serialize. * @param prettyPrint When true, convert to pretty printed JSON with 2 spaces and newlines. When false, JSON is compact. * @param retainKeyOrder When true, the order of keys in the JSON output will be the same as the order in the input object. * @returns JSON serialized text that can be safely logged. */ const toJSON = (value, prettyPrint = true, retainKeyOrder = false) => { const stringify = (0, safe_stable_stringify_1.configure)({ circularValue: undefined, deterministic: !retainKeyOrder, }); return prettyPrint ? stringify(value, null, 2) : stringify(value); }; /** * This function returns a lower cased version of the headers passed to it. * * - `lowerCaseHeaders({"Content-Type": "Application/JSON"})` will return `{"content-type": "Application/JSON"}` * - `lowerCaseHeaders({"Cache-Control": "max-age=604800"})` will return `{"cache-control": "max-age=604800"}` * - `lowerCaseHeaders({"Accept-Language": "en-us"})` will return `{"accept-language": "en-us"}` * @param headers The headers to convert to lower case * @returns This function returns a header object * */ const lowerCaseHeaders = (headers) => Object.entries(headers).reduce((result, [key, val]) => { return Object.assign(Object.assign({}, result), { [key.toLowerCase()]: val }); }, {}); exports.lowerCaseHeaders = lowerCaseHeaders; /** * This function parses a JSON string (if JSON) and returns an object, or returns the object. * * - `toObject('{"foo":"bar","baz":123}')` will return object `{foo: "bar", baz: 123}` * - `toObject({foo:"bar",baz:123})` will return object `{foo: "bar", baz: 123}` * * @param value The JSON string or object to convert * @returns This function returns an object, parsing JSON as necessary */ const toObject = (value) => { if (typeof value === "string" && isJSON(value)) { return JSON.parse(value); } else { return value; } }; exports.toObject = toObject; /** * This function removes any properties of an object that match a certain predicate. * By default properties with values of undefined, null and "" are removed. * * - `cleanObject({foo: undefined, bar: "abc", baz: null, buz: ""})` will return `{bar: "abc"}` * - `cleanObject({foo: 1, bar: 2, baz: 3}, v => v % 2 === 0)` will filter even number values, returning `{foo: 1, baz: 3}` * * @param obj A key-value object to remove properties from * @param predicate A function that returns true for properties to remove. Defaults to removing properties with undefined, null and "" values. * @returns An object with certain properties removed */ const cleanObject = (obj, predicate) => { const defaultPredicate = (v) => v === undefined || v === null || v === ""; return (0, omitBy_1.default)(obj, predicate || defaultPredicate); }; __exportStar(require("./errors"), exports); __exportStar(require("./conditionalLogic"), exports); exports.default = { types: { isBool, toBool, isInt, toInt, isNumber, toNumber, isBigInt, toBigInt, isDate, toDate, isUrl, isBufferDataPayload, toBufferDataPayload, isData, toData, isString, toString, keyValPairListToObject, isJSON, toJSON, lowerCaseHeaders: exports.lowerCaseHeaders, isObjectSelection, toObjectSelection, isObjectFieldMap, toObjectFieldMap, isJSONForm, toJSONForm, isPicklist, isSchedule, isConnection, isElement, toObject: exports.toObject, cleanObject, }, };