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.

101 lines
2.9 KiB
JavaScript

"use strict";
const asExpression = require("as-expression");
const defaultValue = require("default-value");
const assureArray = require("assure-array");
const flatten = require("flatten");
const arrayUnion = require("array-union");
const ValidationError = require("@validatem/error");
const validationResult = require("@validatem/validation-result");
const annotateErrors = require("@validatem/annotate-errors");
const combinator = require("@validatem/combinator");
function containsRules(rules) {
// TODO: We cannot use normalize-rules here (for now) since that would create a cyclical dependency; figure out a way to work around this, maybe by removing the hasShape feature in `normalize-rules`, or moving the flattening itself out into a separate `flatten-rules` package?
if (rules == null) {
return false;
} else {
// TODO: Switch to `Array#flat` once Node 10.x goes EOL (April 2021)
let flattenedRules = flatten(assureArray(rules));
if (!flattenedRules.some((rule) => rule != null)) {
return false;
} else {
return true;
}
}
}
module.exports = function hasShape(rules) {
let validator = combinator((object, applyValidators, context) => {
let allowExtraProperties = defaultValue(context.allowExtraProperties, false);
let unexpectedPropertyErrors = asExpression(() => {
if (!allowExtraProperties) {
return Reflect.ownKeys(object).map((propertyName) => {
if (!containsRules(rules[propertyName])) {
return new ValidationError(`Encountered an unexpected property '${propertyName}'`);
} else {
return null;
}
}).filter((error) => {
return (error != null);
});
} else {
return [];
}
});
if (unexpectedPropertyErrors.length > 0) {
return validationResult({
errors: unexpectedPropertyErrors,
newValue: object
});
} else {
let errors = [];
let newObject = {};
// We need to consider the keys from the ruleset (for detecting missing required properties) *and* the keys from the actual object (for handling extraneous values).
let allKeys = arrayUnion(
Reflect.ownKeys(rules),
Reflect.ownKeys(object)
);
for (let key of allKeys) {
let value = object[key];
let rule = rules[key];
if (rule != null) {
let { errors: keyErrors, newValue } = applyValidators(value, rule);
let annotatedErrors = annotateErrors({
pathSegments: [ key ],
errors: keyErrors
});
errors.push(... annotatedErrors);
// Don't scatter explicit `undefined`s across the result object, just because an optional rule existed for some properties but the corresponding value didn't
if (newValue !== undefined) {
newObject[key] = newValue;
}
} else {
// Extraneous property
// FIXME: Assign non-enumerable if the source property was non-enumerable!
newObject[key] = value;
}
}
return validationResult({
errors: errors,
newValue: newObject
});
}
});
validator.callIfNull = false;
return validator;
};