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