shelving
Version:
Toolkit for using data in JavaScript.
154 lines (153 loc) • 5.15 kB
JavaScript
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;
}