UNPKG

fastest-validator

Version:

The fastest JS validator library for NodeJS

162 lines (142 loc) 4.61 kB
"use strict"; // Quick regex to match most common unquoted JavaScript property names. Note the spec allows Unicode letters. // Unmatched property names will be quoted and validate slighly slower. https://www.ecma-international.org/ecma-262/5.1/#sec-7.6 const identifierRegex = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/; // Regex to escape quoted property names for eval/new Function const escapeEvalRegex = /["'\\\n\r\u2028\u2029]/g; /* istanbul ignore next */ function escapeEvalString(str) { // Based on https://github.com/joliss/js-string-escape return str.replace(escapeEvalRegex, function (character) { switch (character) { case "\"": case "'": case "\\": return "\\" + character; // Four possible LineTerminator characters need to be escaped: case "\n": return "\\n"; case "\r": return "\\r"; case "\u2028": return "\\u2028"; case "\u2029": return "\\u2029"; } }); } /** Signature: function(value, field, parent, errors, context) */ module.exports = function ({ schema, messages }, path, context) { const sourceCode = []; sourceCode.push(` if (typeof value !== "object" || value === null || Array.isArray(value)) { ${this.makeError({ type: "object", actual: "value", messages })} return value; } `); const subSchema = schema.properties || schema.props; if (subSchema) { sourceCode.push("var parentObj = value;"); sourceCode.push("var parentField = field;"); const keys = Object.keys(subSchema).filter(key => !this.isMetaKey(key)); for (let i = 0; i < keys.length; i++) { const property = keys[i]; const rule = this.getRuleFromSchema(subSchema[property]); const name = escapeEvalString(property); const safeSubName = identifierRegex.test(name) ? `.${name}` : `['${name}']`; const safePropName = `parentObj${safeSubName}`; const newPath = (path ? path + "." : "") + property; const labelName = rule.schema.label; const label = labelName ? `'${escapeEvalString(labelName)}'` : undefined; sourceCode.push(`\n// Field: ${escapeEvalString(newPath)}`); sourceCode.push(`field = parentField ? parentField + "${safeSubName}" : "${name}";`); sourceCode.push(`value = ${safePropName};`); sourceCode.push(`label = ${label}`); const innerSource = ` ${safePropName} = ${context.async ? "await " : ""}context.fn[%%INDEX%%](value, field, parentObj, errors, context, label); `; sourceCode.push(this.compileRule(rule, context, newPath, innerSource, safePropName)); if (this.opts.haltOnFirstError === true) { sourceCode.push("if (errors.length) return parentObj;"); } } // Strict handler if (schema.strict) { const allowedProps = Object.keys(subSchema); sourceCode.push(` field = parentField; var invalidProps = []; var props = Object.keys(parentObj); for (let i = 0; i < props.length; i++) { if (${JSON.stringify(allowedProps)}.indexOf(props[i]) === -1) { invalidProps.push(props[i]); } } if (invalidProps.length) { `); if (schema.strict === "remove") { sourceCode.push(` if (errors.length === 0) { `); sourceCode.push(` invalidProps.forEach(function(field) { delete parentObj[field]; }); `); sourceCode.push(` } `); } else { sourceCode.push(` ${this.makeError({ type: "objectStrict", expected: "\"" + allowedProps.join(", ") + "\"", actual: "invalidProps.join(', ')", messages })} `); } sourceCode.push(` } `); } } if (schema.minProps != null || schema.maxProps != null) { // We recalculate props, because: // - if strict equals 'remove', we want to work on // the payload with the extra keys removed, // - if no strict is set, we need them anyway. if (schema.strict) { sourceCode.push(` props = Object.keys(${subSchema ? "parentObj" : "value"}); `); } else { sourceCode.push(` var props = Object.keys(${subSchema ? "parentObj" : "value"}); ${subSchema ? "field = parentField;" : ""} `); } } if (schema.minProps != null) { sourceCode.push(` if (props.length < ${schema.minProps}) { ${this.makeError({ type: "objectMinProps", expected: schema.minProps, actual: "props.length", messages })} } `); } if (schema.maxProps != null) { sourceCode.push(` if (props.length > ${schema.maxProps}) { ${this.makeError({ type: "objectMaxProps", expected: schema.maxProps, actual: "props.length", messages })} } `); } if (subSchema) { sourceCode.push(` return parentObj; `); } else { sourceCode.push(` return value; `); } return { source: sourceCode.join("\n") }; };