UNPKG

@boomerang-io/carbon-addons-boomerang-react

Version:
219 lines (216 loc) 7.75 kB
import * as yup from 'yup'; import { getCustomValidator } from './customValidators.js'; /* IBM Confidential 694970X, 69497O0 © Copyright IBM Corp. 2022, 2024 */ // @ts-nocheck class ValidationError { message; constructor(message) { this.message = message; } } /** * Searches for {substring} in {string}. * If found, returns the {string}, sliced after substring. * * @param {string} string - String to be sliced. * @param {string} substring - String to search for. * @returns {string|null} Null if no match found. */ function getSubString(string, substring) { if (!string) return null; const testedIndex = string.indexOf(substring); if (testedIndex > -1) { return string.slice(testedIndex + substring.length); } return null; } /** * Returns a function from yup, by passing in a function name from our schema. * @param {string} functionName - The string to search for a function. * @param {Object} previousInstance - Object from previous validator result or yup itself. * @returns {Function} Either the found yup function or the default validator. */ function getYupFunction(functionName, previousInstance = yup) { // Make sure we're dealing with a string if (functionName instanceof Array) { functionName = functionName[0]; } // Attempt to retrieve any custom validators first const customValidator = getCustomValidator(functionName); if (customValidator) { return customValidator; } const yupName = getSubString(functionName, "yup."); if (yupName && previousInstance[yupName] instanceof Function) { return previousInstance[yupName].bind(previousInstance); } if (yupName && yup[yupName] instanceof Function) { return yup[yupName].bind(yup); } throw new ValidationError(`Could not find validator ${functionName}`); } /** * Here we check to see if a passed array could be a prefix notation function. * @param {Array} item - Item to be checked. * @param {any} item.functionName - We'll check this, and perhaps it's a prefix function name. * @returns {boolean} True if we are actually looking at prefix notation. */ function isPrefixNotation([functionName]) { if (functionName instanceof Array) { if (isPrefixNotation(functionName)) return true; } if (typeof functionName !== "string") return false; if (functionName.indexOf("yup.") < 0) return false; return true; } /** * Ensure that the argument passed is an array. * @param {Any} maybeArray - To be checked. * @returns {Array} forced to array. */ function ensureArray(maybeArray) { if (maybeArray instanceof Array === false) { return [maybeArray]; } return maybeArray; } /** * Returns true if the passed item is a yup.Schema * @param {maybeSchema} item - Item to check */ function isSchema(item) { return [ // All possible yup schema types yup.mixed, yup.date, yup.array, yup.string, yup.number, yup.object, yup.bool, ].some((schemaType) => item instanceof schemaType); } /** * Converts an array of ['yup.number'] to yup.number(). * @param {[Any]} arrayArgument - The validation array. * @param {Object} previousInstance - The result of a call to yup.number() * i.e. an object schema validation set * @returns {Function} generated yup validator */ function convertArray(arrayArgument, previousInstance = yup) { const [functionName, ...argsToPass] = ensureArray(arrayArgument); // Handle the case when we have a previous instance // but we don't want to use it for this transformation // [['yup.array'], ['yup.of', [['yup.object'], ['yup.shape'] ...]]] if (functionName instanceof Array) { return transformArray(arrayArgument); } const gotFunc = getYupFunction(functionName, previousInstance); // Here we'll actually call the function // This might be something like yup.number().min(5) // We could be passing different types of arguments here // so we'll try to transform them before calling the function // yup.object().shape({ test: yup.string()}) should also be transformed const convertedArguments = transformAll(argsToPass, previousInstance); // Handle the case when we've got an array of empty elements if (convertedArguments instanceof Array) { if (convertedArguments.filter((i) => i).length < 1) { if (typeof gotFunc === "function") { return gotFunc(); } if (isSchema(gotFunc)) { return gotFunc; } } // Spread the array over the function return gotFunc(...convertedArguments); } // Handle the case when we're passing another validator return gotFunc(convertedArguments); } /** * Transforms an array JSON schema to yup array schema. * * @param {Array} jsonArray - array in JSON to be transformed. * @returns {Array} Array with same keys, but values as yup validators. */ function transformArray(jsonArray, previousInstance = yup) { let toReturn = convertArray(jsonArray[0]); jsonArray.slice(1).forEach((item) => { // Found an array, move to prefix extraction if (item instanceof Array) { toReturn = convertArray(item, toReturn); return; } // Found an object, move to object extraction if (item instanceof Object) { toReturn = transformObject(item, previousInstance); return; } // Handle an edge case where we have something like // [['yup.ref', 'linkName'], 'custom error'], and we don't want // to consume 'custom error as a variable yet' if (toReturn instanceof Array) { toReturn = toReturn.concat(item); return; } toReturn = [toReturn, item]; }); return toReturn; } /** * Transforms an object JSON schema to yup object schema. * * @param {Object} jsonObject - Object in JSON to be transformed. * @returns {Object} Object with same keys, but values as yup validators. */ function transformObject(jsonObject, previousInstance = yup) { const toReturn = {}; Object.entries(jsonObject).forEach(([key, value]) => { // Found an array move to array extraction if (value instanceof Array) { toReturn[key] = transformArray(value, previousInstance); return; } // Found an object recursive extraction if (value instanceof Object) { toReturn[key] = transformObject(value, previousInstance); return; } toReturn[key] = value; }); return toReturn; } /** * Steps into arrays and objects and resolve the items inside to yup validators. * @param {Any} jsonObjectOrArray - Object to be transformed. * @returns {yup.Validator} */ function transformAll(jsonObjectOrArray, previousInstance = yup) { // We're dealing with an array // This could be a prefix notation function // If so, we'll call the converter if (jsonObjectOrArray instanceof Array) { if (isPrefixNotation(jsonObjectOrArray)) { return transformArray(jsonObjectOrArray, previousInstance); } return jsonObjectOrArray.map((i) => transformAll(i, previousInstance)); } // If we're dealing with an object // we should check each of the values for that object. // Some of them may also be prefix notation functiosn if (jsonObjectOrArray instanceof Object) { return transformObject(jsonObjectOrArray, previousInstance); } // No case here, just return anything else return jsonObjectOrArray; } export { transformAll, transformObject };