"use strict"; const asExpression = require("as-expression"); const defaultValue = require("default-value"); const assureArray = require("assure-array"); const flatten = require("flatten"); 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) { return combinator((object, applyValidators, context) => { let allowExtraProperties = defaultValue(context.allowExtraProperties, false); let unexpectedPropertyErrors = asExpression(() => { if (!allowExtraProperties) { return Object.keys(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 = {}; for (let [ key, value ] of Object.entries(object)) { let rule = rules[key]; if (rule != null) { let { errors: keyErrors, newValue } = applyValidators(value, rule); let annotatedErrors = annotateErrors({ pathSegments: [ key ], errors: keyErrors }); errors.push(... annotatedErrors); newObject[key] = newValue; } else { // Extraneous property newObject[key] = value; } } return validationResult({ errors: errors, newValue: newObject }); } }); };