UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

149 lines (148 loc) 5.31 kB
import { ValueError } from "../error/ValueError.js"; import { Feedback } from "../feedback/Feedback.js"; import { ValueFeedbacks } from "../feedback/Feedbacks.js"; import { isArray } from "./array.js"; import { getDataProps } from "./data.js"; import { getDictionaryItems } from "./dictionary.js"; import { PASSTHROUGH } from "./function.js"; import { isIterable } from "./iterate.js"; import { getNull } from "./null.js"; import { getUndefined } from "./undefined.js"; /** Get value that validates against a given `Validator`, or throw `ValueError` */ export function getValid(value, validator, ErrorConstructor = ValueError, caller = getValid) { try { return validator.validate(value); } catch (thrown) { if (thrown instanceof Feedback) throw new ErrorConstructor(thrown.message, { received: value, cause: thrown, caller }); throw thrown; } } /** * Validate an iterable set of items with a validator. * * @yield Valid items. * @throw Feedback if one or more items did not validate. * - `feedback.details` will contain an entry for each invalid item (keyed by their count in the input iterable). */ export function* validateItems(unsafeItems, validator) { let index = 0; let valid = true; const messages = {}; for (const unsafeItem of unsafeItems) { try { yield validator.validate(unsafeItem); } catch (thrown) { if (!(thrown instanceof Feedback)) throw thrown; messages[index] = thrown.message; valid = false; } index++; } if (!valid) throw new ValueFeedbacks(messages, unsafeItems); } /** * Validate an array of items. * * @return Array with valid items. * @throw Feedback if one or more entry values did not validate. * - `feedback.details` will contain an entry for each invalid item (keyed by their count in the input iterable). */ export function validateArray(unsafeArray, validator) { let index = 0; let valid = true; let changed = true; 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 (!(thrown instanceof Feedback)) throw thrown; messages[index] = thrown.message; valid = false; } index++; } if (!valid) throw new ValueFeedbacks(messages, unsafeArray); return changed || !isArray(unsafeArray) ? safeArray : unsafeArray; } /** * Validate the values of the entries in a dictionary object. * * @throw Feedback if one or more entry values did not validate. * - `feedback.details` will contain an entry for each invalid item (keyed by their count in the input iterable). */ export function validateDictionary(unsafeDictionary, validator) { let valid = true; 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 (!(thrown instanceof Feedback)) throw thrown; messages[key] = thrown.message; valid = false; } } if (!valid) throw new ValueFeedbacks(messages, unsafeDictionary); return changed || isIterable(unsafeDictionary) ? safeDictionary : unsafeDictionary; } /** Keep track of whether we're doing a deep-partial match or not. */ let isDeeplyPartial = false; export function validateData(unsafeData, validators, partial = isDeeplyPartial) { let valid = true; let changed = true; const safeData = {}; const messages = {}; try { isDeeplyPartial = partial; for (const [key, validator] of getDataProps(validators)) { const unsafeValue = unsafeData[key]; if (unsafeValue === undefined && partial) continue; // Silently skip `undefined` props if in partial mode. try { const safeValue = validator.validate(unsafeValue); safeData[key] = safeValue; if (!changed && safeValue !== unsafeValue) changed = true; } catch (thrown) { if (!(thrown instanceof Feedback)) throw thrown; messages[key] = thrown.message; valid = false; } } if (!valid) throw new ValueFeedbacks(messages, unsafeData); return changed ? safeData : unsafeData; } finally { isDeeplyPartial = false; } } // Undefined validator always returns `undefined` export const UNDEFINED = { validate: getUndefined }; // Null validator always returns `null` export const NULL = { validate: getNull }; // Unknown validator always passes through its input value as `unknown` export const UNKNOWN = { validate: PASSTHROUGH };