UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

154 lines (153 loc) 5.15 kB
import { RequiredError } from "../error/RequiredError.js"; import { isArray } from "./array.js"; import { getDataProps } from "./data.js"; import { getDictionaryItems } from "./dictionary.js"; import { getNamedMessage } from "./error.js"; import { isIterable } from "./iterate.js"; /** Require a valid value for a given validator, or return `undefined` if the value could not be validated. */ export function getValid(value, validator) { try { return validator.validate(value); } catch (thrown) { if (typeof thrown === "string") return undefined; throw thrown; } } /** Require a valid value for a given validator, or throw `RequiredError` if the value could not be validated. */ export function requireValid(value, validator, caller = requireValid) { try { return validator.validate(value); } catch (thrown) { if (typeof thrown === "string") throw new RequiredError(thrown, { cause: thrown, caller }); throw thrown; } } /** * Validate an iterable set of items with a validator. * * @yield Valid items. * @throw string if one or more items did not validate. */ export function* validateItems(unsafeItems, validator) { let index = 0; const messages = []; for (const unsafeItem of unsafeItems) { try { yield validator.validate(unsafeItem); } catch (thrown) { if (typeof thrown !== "string") throw thrown; messages.push(getNamedMessage(index.toString(), thrown)); } index++; } if (messages.length) throw messages.join("\n"); } /** * Validate an array of items. * * @return Array with valid items. * @throw string if one or more entry values did not validate. */ export function validateArray(unsafeArray, validator) { let index = 0; let changed = false; // start false so we can reuse original if nothing changes const safeArray = []; const messages = []; for (const unsafeItem of unsafeArray) { try { const safeItem = validator.validate(unsafeItem); safeArray.push(safeItem); if (!changed && safeItem !== unsafeItem) changed = true; } catch (thrown) { if (typeof thrown !== "string") throw thrown; messages.push(getNamedMessage(index.toString(), thrown)); } index++; } if (messages.length) throw messages.join("\n"); return changed || !isArray(unsafeArray) ? safeArray : unsafeArray; } /** * Validate the values of the entries in a dictionary object. * * @throw string if one or more entry values did not validate. */ export function validateDictionary(unsafeDictionary, validator) { let changed = false; const safeDictionary = {}; const messages = []; for (const [key, unsafeValue] of getDictionaryItems(unsafeDictionary)) { try { const safeValue = validator.validate(unsafeValue); safeDictionary[key] = safeValue; if (!changed && safeValue !== unsafeValue) changed = true; } catch (thrown) { if (typeof thrown !== "string") throw thrown; messages.push(getNamedMessage(key, thrown)); } } if (messages.length) throw messages.join("\n"); return changed || isIterable(unsafeDictionary) ? safeDictionary : unsafeDictionary; } /** * Validate a data object with a set of validators. * - Defined props in the object will be validated against the corresponding validator. * - `undefined` props in the object will be set to the default value of that prop. * - `undefined` props after validation will not be set in the output object. * * @return Valid object. * @throw string if one or more props did not validate. */ export function validateData(unsafeData, validators) { let changes = 0; const safeData = {}; const messages = []; // Validate the props in `validators`. const props = getDataProps(validators); for (const [key, validator] of props) { const unsafeValue = unsafeData[key]; try { const safeValue = validator.validate(unsafeValue); if (safeValue === undefined) { // Undefined values are not included in the output object if (key in unsafeData) changes++; } else { // Defined values are included in the output object. safeData[key] = safeValue; if (safeValue !== unsafeValue) changes++; } } catch (thrown) { if (typeof thrown !== "string") throw thrown; messages.push(getNamedMessage(key, thrown)); } } if (messages.length) throw messages.join("\n"); if (changes) return safeData; // Check that no excess keys exist. for (const key of Object.keys(unsafeData)) if (!(key in validators)) return safeData; return unsafeData; }