You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

62 lines
2.7 KiB
JavaScript

"use strict";
// TODO: Rename this to something more appropriate, like any/anyOf? How to make sure that doesn't cause confusion with `oneOf`?
const splitFilter = require("split-filter");
const ValidationError = require("@validatem/error");
const combinator = require("@validatem/combinator");
const validationResult = require("@validatem/validation-result");
module.exports = function (alternatives) {
if (!Array.isArray(alternatives)) {
throw new Error(`Must specify an array of alternatives`);
} else if (alternatives.length < 2) {
// This doesn't interfere with conditionally-specified alternatives using ternary expressions, because in those cases there is still *some* item specified, it's just going to have a value of `undefined` (and will subsequently be filtered out)
throw new Error("Must specify at least two alternatives");
} else if (arguments.length > 1) {
throw new Error(`Only one argument is accepted; maybe you forgot to wrap the different alternatives into an array?`);
} else {
return combinator((value, applyValidators) => {
let allErrors = [];
for (let alternative of alternatives) {
let { errors, newValue } = applyValidators(value, alternative);
if (errors.length === 0) {
return newValue;
} else {
allErrors.push(...errors);
}
}
/*
We want to separate out the errors that occurred at this "level"; that is, errors that *didn't* originate from a nested validation combinator like `has-shape` or `array-of`.
Otherwise, the user could get very confusing errors that combine the `either` alternatives and some deeper validation error into a single list, without denoting the path.
An example of such a confusing error:
- At options -> credentials: Must satisfy at least one of: "Must be a plain object (eg. object literal)", "Encountered an unexpected property 'foo'"
With this implementation, it becomes a perfectly reasonable error instead:
- At options -> credentials -> 1: Encountered an unexpected property 'foo'"
*/
let [ sameLevelErrors, nestedErrors ] = splitFilter(allErrors, (error) => {
return (error.path.length === 0);
});
if (nestedErrors.length > 0) {
// At least one of the alternatives *did* match, but it failed somewhere further down the tree. Chances are that the user intended to match that branch, so we pretend that the other alternatives don't exist, and pass through the original error(s).
return validationResult({ errors: nestedErrors });
} else {
let alternativesList = sameLevelErrors
.map((error) => `"${error.message}"`)
.join(", ");
throw new ValidationError(`Must satisfy at least one of: ${alternativesList}`, { errors: sameLevelErrors });
}
});
}
};