simpl-schema
Advanced tools
Comparing version 3.2.0 to 3.3.0
"use strict"; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -17,242 +6,16 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const mongo_object_1 = __importDefault(require("mongo-object")); | ||
const SimpleSchema_js_1 = require("./SimpleSchema.js"); | ||
const index_js_1 = require("./utility/index.js"); | ||
const allowedValuesValidator_js_1 = __importDefault(require("./validation/allowedValuesValidator.js")); | ||
const requiredValidator_js_1 = __importDefault(require("./validation/requiredValidator.js")); | ||
const index_js_2 = __importDefault(require("./validation/typeValidator/index.js")); | ||
function shouldCheck(key) { | ||
if (key === '$pushAll') { | ||
const validateDocument_js_1 = __importDefault(require("./validation/validateDocument.js")); | ||
const validateField_js_1 = __importDefault(require("./validation/validateField.js")); | ||
function shouldCheck(operator) { | ||
if (operator === '$pushAll') { | ||
throw new Error('$pushAll is not supported; use $push + $each'); | ||
} | ||
return !['$pull', '$pullAll', '$pop', '$slice'].includes(key); | ||
return !['$pull', '$pullAll', '$pop', '$slice'].includes(operator); | ||
} | ||
function doValidation({ extendedCustomContext, ignoreTypes, isModifier, isUpsert, keysToValidate, mongoObject, obj, schema, validationContext }) { | ||
// First do some basic checks of the object, and throw errors if necessary | ||
if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) { | ||
throw new Error('The first argument of validate() must be an object'); | ||
} | ||
if (!isModifier && (0, index_js_1.looksLikeModifier)(obj)) { | ||
throw new Error('When the validation object contains mongo operators, you must set the modifier option to true'); | ||
} | ||
function getFieldInfo(key) { | ||
var _a; | ||
// Create mongoObject if necessary, cache for speed | ||
if (mongoObject == null) | ||
mongoObject = new mongo_object_1.default(obj, schema.blackboxKeys()); | ||
const keyInfo = (_a = mongoObject.getInfoForKey(key)) !== null && _a !== void 0 ? _a : { | ||
operator: null, | ||
value: undefined | ||
}; | ||
return Object.assign(Object.assign({}, keyInfo), { isSet: keyInfo.value !== undefined }); | ||
} | ||
let validationErrors = []; | ||
// Validation function called for each affected key | ||
function validate(val, affectedKey, affectedKeyGeneric, def, op, isInArrayItemObject, isInSubObject) { | ||
// Get the schema for this key, marking invalid if there isn't one. | ||
if (def == null) { | ||
// We don't need KEY_NOT_IN_SCHEMA error for $unset and we also don't need to continue | ||
if (op === '$unset' || | ||
(op === '$currentDate' && affectedKey.endsWith('.$type'))) { | ||
return; | ||
} | ||
validationErrors.push({ | ||
name: affectedKey, | ||
type: SimpleSchema_js_1.SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA, | ||
value: val | ||
}); | ||
return; | ||
} | ||
// For $rename, make sure that the new name is allowed by the schema | ||
if (op === '$rename' && !schema.allowsKey(val)) { | ||
validationErrors.push({ | ||
name: val, | ||
type: SimpleSchema_js_1.SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA, | ||
value: null | ||
}); | ||
return; | ||
} | ||
// Prepare the context object for the validator functions | ||
const fieldParentNameWithEndDot = (0, index_js_1.getParentOfKey)(affectedKey, true); | ||
const fieldParentName = fieldParentNameWithEndDot.slice(0, -1); | ||
const fieldValidationErrors = []; | ||
const validatorContext = Object.assign({ addValidationErrors(errors) { | ||
errors.forEach((error) => fieldValidationErrors.push(error)); | ||
}, | ||
field(fName) { | ||
return getFieldInfo(fName); | ||
}, genericKey: affectedKeyGeneric, isInArrayItemObject, | ||
isInSubObject, | ||
isModifier, isSet: val !== undefined, key: affectedKey, obj, operator: op, parentField() { | ||
return getFieldInfo(fieldParentName); | ||
}, | ||
siblingField(fName) { | ||
return getFieldInfo(fieldParentNameWithEndDot + fName); | ||
}, | ||
validationContext, value: val, | ||
// Value checks are not necessary for null or undefined values, except | ||
// for non-optional null array items, or for $unset or $rename values | ||
valueShouldBeChecked: op !== '$unset' && | ||
op !== '$rename' && | ||
((val !== undefined && val !== null) || | ||
((affectedKeyGeneric === null || affectedKeyGeneric === void 0 ? void 0 : affectedKeyGeneric.slice(-2)) === '.$' && | ||
val === null && | ||
def.optional !== true)) }, (extendedCustomContext !== null && extendedCustomContext !== void 0 ? extendedCustomContext : {})); | ||
const builtInValidators = [ | ||
requiredValidator_js_1.default, | ||
index_js_2.default, | ||
allowedValuesValidator_js_1.default | ||
]; | ||
const validators = builtInValidators | ||
// @ts-expect-error | ||
.concat(schema._validators) | ||
// @ts-expect-error | ||
.concat(SimpleSchema_js_1.SimpleSchema._validators); | ||
// Loop through each of the definitions in the SimpleSchemaGroup. | ||
// If any return true, we're valid. | ||
const fieldIsValid = def.type.some((typeDef) => { | ||
// If the type is SimpleSchema.Any, then it is valid | ||
if (typeDef === SimpleSchema_js_1.SimpleSchema.Any) | ||
return true; | ||
const { type } = def, definitionWithoutType = __rest(def | ||
// @ts-expect-error | ||
, ["type"]); // eslint-disable-line no-unused-vars | ||
// @ts-expect-error | ||
const finalValidatorContext = Object.assign(Object.assign({}, validatorContext), { | ||
// Take outer definition props like "optional" and "label" | ||
// and add them to inner props like "type" and "min" | ||
definition: Object.assign(Object.assign({}, definitionWithoutType), typeDef) }); | ||
// Add custom field validators to the list after the built-in | ||
// validators but before the schema and global validators. | ||
const fieldValidators = validators.slice(0); | ||
const customFn = typeDef.custom; | ||
if (customFn != null) | ||
fieldValidators.splice(builtInValidators.length, 0, customFn); | ||
// We use _.every just so that we don't continue running more validator | ||
// functions after the first one returns false or an error string. | ||
return fieldValidators.every((validator) => { | ||
const result = validator.call(finalValidatorContext); | ||
// If the validator returns a string, assume it is the | ||
// error type. | ||
if (typeof result === 'string') { | ||
fieldValidationErrors.push({ | ||
name: affectedKey, | ||
type: result, | ||
value: val | ||
}); | ||
return false; | ||
} | ||
// If the validator returns an object, assume it is an | ||
// error object. | ||
if (typeof result === 'object' && result !== null) { | ||
fieldValidationErrors.push(Object.assign({ name: affectedKey, value: val }, result)); | ||
return false; | ||
} | ||
// If the validator returns false, assume they already | ||
// called this.addValidationErrors within the function | ||
if (result === false) | ||
return false; | ||
// Any other return value we assume means it was valid | ||
return true; | ||
}); | ||
}); | ||
if (!fieldIsValid) { | ||
validationErrors = validationErrors.concat(fieldValidationErrors); | ||
} | ||
} | ||
// The recursive function | ||
function checkObj({ val, affectedKey, operator = null, isInArrayItemObject = false, isInSubObject = false }) { | ||
let affectedKeyGeneric; | ||
let def; | ||
if (affectedKey != null) { | ||
// When we hit a blackbox key, we don't progress any further | ||
if (schema.keyIsInBlackBox(affectedKey)) | ||
return; | ||
// Make a generic version of the affected key, and use that | ||
// to get the schema for this key. | ||
affectedKeyGeneric = mongo_object_1.default.makeKeyGeneric(affectedKey); | ||
if (affectedKeyGeneric === null) | ||
throw new Error(`Failed to get generic key for affected key "${affectedKey}"`); | ||
const shouldValidateKey = (keysToValidate == null) || | ||
keysToValidate.some((keyToValidate) => keyToValidate === affectedKey || | ||
keyToValidate === affectedKeyGeneric || | ||
affectedKey.startsWith(`${keyToValidate}.`) || | ||
(affectedKeyGeneric === null || affectedKeyGeneric === void 0 ? void 0 : affectedKeyGeneric.startsWith(`${keyToValidate}.`))); | ||
// Prepare the context object for the rule functions | ||
const fieldParentNameWithEndDot = (0, index_js_1.getParentOfKey)(affectedKey, true); | ||
const fieldParentName = fieldParentNameWithEndDot.slice(0, -1); | ||
const functionsContext = Object.assign({ field(fName) { | ||
return getFieldInfo(fName); | ||
}, genericKey: affectedKeyGeneric, isInArrayItemObject, | ||
isInSubObject, | ||
isModifier, isSet: val !== undefined, key: affectedKey, obj, | ||
operator, | ||
parentField() { | ||
return getFieldInfo(fieldParentName); | ||
}, | ||
siblingField(fName) { | ||
return getFieldInfo(fieldParentNameWithEndDot + fName); | ||
}, | ||
validationContext, value: val }, (extendedCustomContext !== null && extendedCustomContext !== void 0 ? extendedCustomContext : {})); | ||
// Perform validation for this key | ||
def = schema.getDefinition(affectedKey, null, functionsContext); | ||
if (shouldValidateKey) { | ||
validate(val, affectedKey, affectedKeyGeneric, def, operator, isInArrayItemObject, isInSubObject); | ||
} | ||
} | ||
// If affectedKeyGeneric is undefined due to this being the first run of this | ||
// function, objectKeys will return the top-level keys. | ||
const childKeys = schema.objectKeys(affectedKeyGeneric); | ||
// Temporarily convert missing objects to empty objects | ||
// so that the looping code will be called and required | ||
// descendent keys can be validated. | ||
if ((val === undefined || val === null) && | ||
((def == null) || (def.optional !== true && childKeys.length > 0))) { | ||
val = {}; | ||
} | ||
// Loop through arrays | ||
if (Array.isArray(val)) { | ||
val.forEach((v, i) => { | ||
checkObj({ | ||
val: v, | ||
affectedKey: `${affectedKey}.${i}`, | ||
operator | ||
}); | ||
}); | ||
} | ||
else if ((0, index_js_1.isObjectWeShouldTraverse)(val) && | ||
// @ts-expect-error | ||
((def == null) || !schema._blackboxKeys.has(affectedKey !== null && affectedKey !== void 0 ? affectedKey : ''))) { | ||
// Loop through object keys | ||
// Get list of present keys | ||
const presentKeys = Object.keys(val); | ||
// If this object is within an array, make sure we check for | ||
// required as if it's not a modifier | ||
isInArrayItemObject = (affectedKeyGeneric === null || affectedKeyGeneric === void 0 ? void 0 : affectedKeyGeneric.slice(-2)) === '.$'; | ||
const checkedKeys = []; | ||
// Check all present keys plus all keys defined by the schema. | ||
// This allows us to detect extra keys not allowed by the schema plus | ||
// any missing required keys, and to run any custom functions for other keys. | ||
/* eslint-disable no-restricted-syntax */ | ||
for (const key of [...presentKeys, ...childKeys]) { | ||
// `childKeys` and `presentKeys` may contain the same keys, so make | ||
// sure we run only once per unique key | ||
if (checkedKeys.includes(key)) | ||
continue; | ||
checkedKeys.push(key); | ||
checkObj({ | ||
val: val[key], | ||
affectedKey: (0, index_js_1.appendAffectedKey)(affectedKey, key), | ||
operator, | ||
isInArrayItemObject, | ||
isInSubObject: true | ||
}); | ||
} | ||
/* eslint-enable no-restricted-syntax */ | ||
} | ||
} | ||
function checkModifier(mod) { | ||
const validationErrors = []; | ||
// Kick off the validation | ||
if (isModifier) { | ||
// Loop through operators | ||
Object.keys(mod).forEach((op) => { | ||
const opObj = mod[op]; | ||
for (const [op, opObj] of Object.entries(obj)) { | ||
// If non-operators are mixed in, throw error | ||
@@ -262,51 +25,55 @@ if (op.slice(0, 1) !== '$') { | ||
} | ||
if (shouldCheck(op)) { | ||
// For an upsert, missing props would not be set if an insert is performed, | ||
// so we check them all with undefined value to force any 'required' checks to fail | ||
if (isUpsert && (op === '$set' || op === '$setOnInsert')) { | ||
const presentKeys = Object.keys(opObj); | ||
schema.objectKeys().forEach((schemaKey) => { | ||
if (!presentKeys.includes(schemaKey)) { | ||
checkObj({ | ||
val: undefined, | ||
affectedKey: schemaKey, | ||
operator: op | ||
}); | ||
} | ||
}); | ||
if (!shouldCheck(op)) | ||
continue; | ||
const presentKeys = Object.keys(opObj); | ||
const fields = presentKeys.map((opKey) => { | ||
let value = opObj[opKey]; | ||
if (op === '$push' || op === '$addToSet') { | ||
if (typeof value === 'object' && '$each' in value) { | ||
value = value.$each; | ||
} | ||
else { | ||
opKey = `${opKey}.0`; | ||
} | ||
} | ||
// Don't use forEach here because it will not properly handle an | ||
// object that has a property named `length` | ||
Object.keys(opObj).forEach((k) => { | ||
let v = opObj[k]; | ||
if (op === '$push' || op === '$addToSet') { | ||
if (typeof v === 'object' && '$each' in v) { | ||
v = v.$each; | ||
} | ||
else { | ||
k = `${k}.0`; | ||
} | ||
return { key: opKey, value }; | ||
}); | ||
// For an upsert, missing props would not be set if an insert is performed, | ||
// so we check them all with undefined value to force any 'required' checks to fail | ||
if (isUpsert && (op === '$set' || op === '$setOnInsert')) { | ||
for (const key of schema.objectKeys()) { | ||
if (!presentKeys.includes(key)) { | ||
fields.push({ key, value: undefined }); | ||
} | ||
checkObj({ | ||
val: v, | ||
affectedKey: k, | ||
operator: op | ||
}); | ||
} | ||
} | ||
for (const field of fields) { | ||
const fieldErrors = (0, validateField_js_1.default)({ | ||
affectedKey: field.key, | ||
obj, | ||
op, | ||
schema, | ||
val: field.value, | ||
validationContext | ||
}); | ||
if (fieldErrors.length > 0) { | ||
validationErrors.push(...fieldErrors); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
// Kick off the validation | ||
if (isModifier) { | ||
checkModifier(obj); | ||
} | ||
else { | ||
checkObj({ val: obj }); | ||
const fieldErrors = (0, validateField_js_1.default)({ | ||
obj, | ||
schema, | ||
val: obj, | ||
validationContext | ||
}); | ||
if (fieldErrors.length > 0) { | ||
validationErrors.push(...fieldErrors); | ||
} | ||
} | ||
// Custom whole-doc validators | ||
// @ts-expect-error | ||
const docValidators = schema._docValidators.concat( | ||
// @ts-expect-error | ||
SimpleSchema_js_1.SimpleSchema._docValidators); | ||
const docValidatorContext = Object.assign({ ignoreTypes, | ||
const wholeDocumentErrors = (0, validateDocument_js_1.default)({ | ||
extendedCustomContext, | ||
ignoreTypes, | ||
isModifier, | ||
@@ -318,13 +85,9 @@ isUpsert, | ||
schema, | ||
validationContext }, (extendedCustomContext !== null && extendedCustomContext !== void 0 ? extendedCustomContext : {})); | ||
docValidators.forEach((func) => { | ||
const errors = func.call(docValidatorContext, obj); | ||
if (!Array.isArray(errors)) { | ||
throw new Error('Custom doc validator must return an array of error objects'); | ||
} | ||
if (errors.length > 0) | ||
validationErrors = validationErrors.concat(errors); | ||
validationContext | ||
}); | ||
const addedFieldNames = []; | ||
validationErrors = validationErrors.filter((errObj) => { | ||
if (wholeDocumentErrors.length > 0) { | ||
validationErrors.push(...wholeDocumentErrors); | ||
} | ||
const addedFieldNames = new Set(); | ||
return validationErrors.filter((errObj) => { | ||
// Remove error types the user doesn't care about | ||
@@ -334,9 +97,8 @@ if ((ignoreTypes === null || ignoreTypes === void 0 ? void 0 : ignoreTypes.includes(errObj.type)) === true) | ||
// Make sure there is only one error per fieldName | ||
if (addedFieldNames.includes(errObj.name)) | ||
if (addedFieldNames.has(errObj.name)) | ||
return false; | ||
addedFieldNames.push(errObj.name); | ||
addedFieldNames.add(errObj.name); | ||
return true; | ||
}); | ||
return validationErrors; | ||
} | ||
exports.default = doValidation; |
@@ -208,2 +208,24 @@ "use strict"; | ||
/** | ||
* @param key One specific or generic key for which to get all possible schemas. | ||
* @returns An potentially empty array of possible definitions for one key | ||
* | ||
* Note that this returns the raw, unevaluated definition object. Use `getDefinition` | ||
* if you want the evaluated definition, where any properties that are functions | ||
* have been run to produce a result. | ||
*/ | ||
schemas(key) { | ||
const schemas = []; | ||
const genericKey = mongo_object_1.default.makeKeyGeneric(key); | ||
const keySchema = genericKey == null ? null : this._schema[genericKey]; | ||
if (keySchema != null) | ||
schemas.push(keySchema); | ||
// See if it's defined in any subschema | ||
this.forEachAncestorSimpleSchema(key, (simpleSchema, ancestor, subSchemaKey) => { | ||
const keyDef = simpleSchema.schema(subSchemaKey); | ||
if (keyDef != null) | ||
schemas.push(keyDef); | ||
}); | ||
return schemas; | ||
} | ||
/** | ||
* @returns {Object} The entire schema object with subschemas merged. This is the | ||
@@ -241,6 +263,32 @@ * equivalent of what schema() returned in SimpleSchema < 2.0 | ||
getDefinition(key, propList, functionContext = {}) { | ||
const schemaKeyDefinition = this.schema(key); | ||
if (schemaKeyDefinition == null) | ||
return; | ||
return this.resolveDefinitionForSchema(key, schemaKeyDefinition, propList, functionContext); | ||
} | ||
/** | ||
* Returns the evaluated definition for one key in the schema | ||
* | ||
* @param key Generic or specific schema key | ||
* @param [propList] Array of schema properties you need; performance optimization | ||
* @param [functionContext] The context to use when evaluating schema options that are functions | ||
* @returns The schema definition for the requested key | ||
*/ | ||
getDefinitions(key, propList, functionContext = {}) { | ||
const schemaKeyDefinitions = this.schemas(key); | ||
return schemaKeyDefinitions.map((def) => { | ||
return this.resolveDefinitionForSchema(key, def, propList, functionContext); | ||
}); | ||
} | ||
/** | ||
* Resolves the definition for one key in the schema | ||
* | ||
* @param key Generic or specific schema key | ||
* @param schemaKeyDefinition Unresolved definition as returned from simpleSchema.schema() | ||
* @param [propList] Array of schema properties you need; performance optimization | ||
* @param [functionContext] The context to use when evaluating schema options that are functions | ||
* @returns The schema definition for the requested key | ||
*/ | ||
resolveDefinitionForSchema(key, schemaKeyDefinition, propList, functionContext = {}) { | ||
var _a; | ||
const defs = this.schema(key); | ||
if (defs == null) | ||
return; | ||
const getPropIterator = (obj, newObj) => { | ||
@@ -268,6 +316,6 @@ return (prop) => { | ||
}; | ||
Object.keys(defs).forEach(getPropIterator(defs, result)); | ||
Object.keys(schemaKeyDefinition).forEach(getPropIterator(schemaKeyDefinition, result)); | ||
// Resolve all the types and convert to a normal array to make it easier to use. | ||
if (Array.isArray((_a = defs.type) === null || _a === void 0 ? void 0 : _a.definitions)) { | ||
result.type = defs.type.definitions.map((typeDef) => { | ||
if (Array.isArray((_a = schemaKeyDefinition.type) === null || _a === void 0 ? void 0 : _a.definitions)) { | ||
result.type = schemaKeyDefinition.type.definitions.map((typeDef) => { | ||
const newTypeDef = { | ||
@@ -274,0 +322,0 @@ type: String // will be overwritten |
@@ -8,2 +8,3 @@ "use strict"; | ||
const doValidation_js_1 = __importDefault(require("./doValidation.js")); | ||
const index_js_1 = require("./utility/index.js"); | ||
class ValidationContext { | ||
@@ -71,2 +72,9 @@ /** | ||
validate(obj, { extendedCustomContext = {}, ignore: ignoreTypes = [], keys: keysToValidate, modifier: isModifier = false, mongoObject, upsert: isUpsert = false } = {}) { | ||
// First do some basic checks of the object, and throw errors if necessary | ||
if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) { | ||
throw new Error('The first argument of validate() must be an object'); | ||
} | ||
if (!isModifier && (0, index_js_1.looksLikeModifier)(obj)) { | ||
throw new Error('When the validation object contains mongo operators, you must set the modifier option to true'); | ||
} | ||
const validationErrors = (0, doValidation_js_1.default)({ | ||
@@ -73,0 +81,0 @@ extendedCustomContext, |
@@ -1,252 +0,15 @@ | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
import MongoObject from 'mongo-object'; | ||
import { SimpleSchema } from './SimpleSchema.js'; | ||
import { appendAffectedKey, getParentOfKey, isObjectWeShouldTraverse, looksLikeModifier } from './utility/index.js'; | ||
import allowedValuesValidator from './validation/allowedValuesValidator.js'; | ||
import requiredValidator from './validation/requiredValidator.js'; | ||
import typeValidator from './validation/typeValidator/index.js'; | ||
function shouldCheck(key) { | ||
if (key === '$pushAll') { | ||
import validateDocument from './validation/validateDocument.js'; | ||
import validateField from './validation/validateField.js'; | ||
function shouldCheck(operator) { | ||
if (operator === '$pushAll') { | ||
throw new Error('$pushAll is not supported; use $push + $each'); | ||
} | ||
return !['$pull', '$pullAll', '$pop', '$slice'].includes(key); | ||
return !['$pull', '$pullAll', '$pop', '$slice'].includes(operator); | ||
} | ||
function doValidation({ extendedCustomContext, ignoreTypes, isModifier, isUpsert, keysToValidate, mongoObject, obj, schema, validationContext }) { | ||
// First do some basic checks of the object, and throw errors if necessary | ||
if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) { | ||
throw new Error('The first argument of validate() must be an object'); | ||
} | ||
if (!isModifier && looksLikeModifier(obj)) { | ||
throw new Error('When the validation object contains mongo operators, you must set the modifier option to true'); | ||
} | ||
function getFieldInfo(key) { | ||
var _a; | ||
// Create mongoObject if necessary, cache for speed | ||
if (mongoObject == null) | ||
mongoObject = new MongoObject(obj, schema.blackboxKeys()); | ||
const keyInfo = (_a = mongoObject.getInfoForKey(key)) !== null && _a !== void 0 ? _a : { | ||
operator: null, | ||
value: undefined | ||
}; | ||
return Object.assign(Object.assign({}, keyInfo), { isSet: keyInfo.value !== undefined }); | ||
} | ||
let validationErrors = []; | ||
// Validation function called for each affected key | ||
function validate(val, affectedKey, affectedKeyGeneric, def, op, isInArrayItemObject, isInSubObject) { | ||
// Get the schema for this key, marking invalid if there isn't one. | ||
if (def == null) { | ||
// We don't need KEY_NOT_IN_SCHEMA error for $unset and we also don't need to continue | ||
if (op === '$unset' || | ||
(op === '$currentDate' && affectedKey.endsWith('.$type'))) { | ||
return; | ||
} | ||
validationErrors.push({ | ||
name: affectedKey, | ||
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA, | ||
value: val | ||
}); | ||
return; | ||
} | ||
// For $rename, make sure that the new name is allowed by the schema | ||
if (op === '$rename' && !schema.allowsKey(val)) { | ||
validationErrors.push({ | ||
name: val, | ||
type: SimpleSchema.ErrorTypes.KEY_NOT_IN_SCHEMA, | ||
value: null | ||
}); | ||
return; | ||
} | ||
// Prepare the context object for the validator functions | ||
const fieldParentNameWithEndDot = getParentOfKey(affectedKey, true); | ||
const fieldParentName = fieldParentNameWithEndDot.slice(0, -1); | ||
const fieldValidationErrors = []; | ||
const validatorContext = Object.assign({ addValidationErrors(errors) { | ||
errors.forEach((error) => fieldValidationErrors.push(error)); | ||
}, | ||
field(fName) { | ||
return getFieldInfo(fName); | ||
}, genericKey: affectedKeyGeneric, isInArrayItemObject, | ||
isInSubObject, | ||
isModifier, isSet: val !== undefined, key: affectedKey, obj, operator: op, parentField() { | ||
return getFieldInfo(fieldParentName); | ||
}, | ||
siblingField(fName) { | ||
return getFieldInfo(fieldParentNameWithEndDot + fName); | ||
}, | ||
validationContext, value: val, | ||
// Value checks are not necessary for null or undefined values, except | ||
// for non-optional null array items, or for $unset or $rename values | ||
valueShouldBeChecked: op !== '$unset' && | ||
op !== '$rename' && | ||
((val !== undefined && val !== null) || | ||
((affectedKeyGeneric === null || affectedKeyGeneric === void 0 ? void 0 : affectedKeyGeneric.slice(-2)) === '.$' && | ||
val === null && | ||
def.optional !== true)) }, (extendedCustomContext !== null && extendedCustomContext !== void 0 ? extendedCustomContext : {})); | ||
const builtInValidators = [ | ||
requiredValidator, | ||
typeValidator, | ||
allowedValuesValidator | ||
]; | ||
const validators = builtInValidators | ||
// @ts-expect-error | ||
.concat(schema._validators) | ||
// @ts-expect-error | ||
.concat(SimpleSchema._validators); | ||
// Loop through each of the definitions in the SimpleSchemaGroup. | ||
// If any return true, we're valid. | ||
const fieldIsValid = def.type.some((typeDef) => { | ||
// If the type is SimpleSchema.Any, then it is valid | ||
if (typeDef === SimpleSchema.Any) | ||
return true; | ||
const { type } = def, definitionWithoutType = __rest(def | ||
// @ts-expect-error | ||
, ["type"]); // eslint-disable-line no-unused-vars | ||
// @ts-expect-error | ||
const finalValidatorContext = Object.assign(Object.assign({}, validatorContext), { | ||
// Take outer definition props like "optional" and "label" | ||
// and add them to inner props like "type" and "min" | ||
definition: Object.assign(Object.assign({}, definitionWithoutType), typeDef) }); | ||
// Add custom field validators to the list after the built-in | ||
// validators but before the schema and global validators. | ||
const fieldValidators = validators.slice(0); | ||
const customFn = typeDef.custom; | ||
if (customFn != null) | ||
fieldValidators.splice(builtInValidators.length, 0, customFn); | ||
// We use _.every just so that we don't continue running more validator | ||
// functions after the first one returns false or an error string. | ||
return fieldValidators.every((validator) => { | ||
const result = validator.call(finalValidatorContext); | ||
// If the validator returns a string, assume it is the | ||
// error type. | ||
if (typeof result === 'string') { | ||
fieldValidationErrors.push({ | ||
name: affectedKey, | ||
type: result, | ||
value: val | ||
}); | ||
return false; | ||
} | ||
// If the validator returns an object, assume it is an | ||
// error object. | ||
if (typeof result === 'object' && result !== null) { | ||
fieldValidationErrors.push(Object.assign({ name: affectedKey, value: val }, result)); | ||
return false; | ||
} | ||
// If the validator returns false, assume they already | ||
// called this.addValidationErrors within the function | ||
if (result === false) | ||
return false; | ||
// Any other return value we assume means it was valid | ||
return true; | ||
}); | ||
}); | ||
if (!fieldIsValid) { | ||
validationErrors = validationErrors.concat(fieldValidationErrors); | ||
} | ||
} | ||
// The recursive function | ||
function checkObj({ val, affectedKey, operator = null, isInArrayItemObject = false, isInSubObject = false }) { | ||
let affectedKeyGeneric; | ||
let def; | ||
if (affectedKey != null) { | ||
// When we hit a blackbox key, we don't progress any further | ||
if (schema.keyIsInBlackBox(affectedKey)) | ||
return; | ||
// Make a generic version of the affected key, and use that | ||
// to get the schema for this key. | ||
affectedKeyGeneric = MongoObject.makeKeyGeneric(affectedKey); | ||
if (affectedKeyGeneric === null) | ||
throw new Error(`Failed to get generic key for affected key "${affectedKey}"`); | ||
const shouldValidateKey = (keysToValidate == null) || | ||
keysToValidate.some((keyToValidate) => keyToValidate === affectedKey || | ||
keyToValidate === affectedKeyGeneric || | ||
affectedKey.startsWith(`${keyToValidate}.`) || | ||
(affectedKeyGeneric === null || affectedKeyGeneric === void 0 ? void 0 : affectedKeyGeneric.startsWith(`${keyToValidate}.`))); | ||
// Prepare the context object for the rule functions | ||
const fieldParentNameWithEndDot = getParentOfKey(affectedKey, true); | ||
const fieldParentName = fieldParentNameWithEndDot.slice(0, -1); | ||
const functionsContext = Object.assign({ field(fName) { | ||
return getFieldInfo(fName); | ||
}, genericKey: affectedKeyGeneric, isInArrayItemObject, | ||
isInSubObject, | ||
isModifier, isSet: val !== undefined, key: affectedKey, obj, | ||
operator, | ||
parentField() { | ||
return getFieldInfo(fieldParentName); | ||
}, | ||
siblingField(fName) { | ||
return getFieldInfo(fieldParentNameWithEndDot + fName); | ||
}, | ||
validationContext, value: val }, (extendedCustomContext !== null && extendedCustomContext !== void 0 ? extendedCustomContext : {})); | ||
// Perform validation for this key | ||
def = schema.getDefinition(affectedKey, null, functionsContext); | ||
if (shouldValidateKey) { | ||
validate(val, affectedKey, affectedKeyGeneric, def, operator, isInArrayItemObject, isInSubObject); | ||
} | ||
} | ||
// If affectedKeyGeneric is undefined due to this being the first run of this | ||
// function, objectKeys will return the top-level keys. | ||
const childKeys = schema.objectKeys(affectedKeyGeneric); | ||
// Temporarily convert missing objects to empty objects | ||
// so that the looping code will be called and required | ||
// descendent keys can be validated. | ||
if ((val === undefined || val === null) && | ||
((def == null) || (def.optional !== true && childKeys.length > 0))) { | ||
val = {}; | ||
} | ||
// Loop through arrays | ||
if (Array.isArray(val)) { | ||
val.forEach((v, i) => { | ||
checkObj({ | ||
val: v, | ||
affectedKey: `${affectedKey}.${i}`, | ||
operator | ||
}); | ||
}); | ||
} | ||
else if (isObjectWeShouldTraverse(val) && | ||
// @ts-expect-error | ||
((def == null) || !schema._blackboxKeys.has(affectedKey !== null && affectedKey !== void 0 ? affectedKey : ''))) { | ||
// Loop through object keys | ||
// Get list of present keys | ||
const presentKeys = Object.keys(val); | ||
// If this object is within an array, make sure we check for | ||
// required as if it's not a modifier | ||
isInArrayItemObject = (affectedKeyGeneric === null || affectedKeyGeneric === void 0 ? void 0 : affectedKeyGeneric.slice(-2)) === '.$'; | ||
const checkedKeys = []; | ||
// Check all present keys plus all keys defined by the schema. | ||
// This allows us to detect extra keys not allowed by the schema plus | ||
// any missing required keys, and to run any custom functions for other keys. | ||
/* eslint-disable no-restricted-syntax */ | ||
for (const key of [...presentKeys, ...childKeys]) { | ||
// `childKeys` and `presentKeys` may contain the same keys, so make | ||
// sure we run only once per unique key | ||
if (checkedKeys.includes(key)) | ||
continue; | ||
checkedKeys.push(key); | ||
checkObj({ | ||
val: val[key], | ||
affectedKey: appendAffectedKey(affectedKey, key), | ||
operator, | ||
isInArrayItemObject, | ||
isInSubObject: true | ||
}); | ||
} | ||
/* eslint-enable no-restricted-syntax */ | ||
} | ||
} | ||
function checkModifier(mod) { | ||
const validationErrors = []; | ||
// Kick off the validation | ||
if (isModifier) { | ||
// Loop through operators | ||
Object.keys(mod).forEach((op) => { | ||
const opObj = mod[op]; | ||
for (const [op, opObj] of Object.entries(obj)) { | ||
// If non-operators are mixed in, throw error | ||
@@ -256,51 +19,55 @@ if (op.slice(0, 1) !== '$') { | ||
} | ||
if (shouldCheck(op)) { | ||
// For an upsert, missing props would not be set if an insert is performed, | ||
// so we check them all with undefined value to force any 'required' checks to fail | ||
if (isUpsert && (op === '$set' || op === '$setOnInsert')) { | ||
const presentKeys = Object.keys(opObj); | ||
schema.objectKeys().forEach((schemaKey) => { | ||
if (!presentKeys.includes(schemaKey)) { | ||
checkObj({ | ||
val: undefined, | ||
affectedKey: schemaKey, | ||
operator: op | ||
}); | ||
} | ||
}); | ||
if (!shouldCheck(op)) | ||
continue; | ||
const presentKeys = Object.keys(opObj); | ||
const fields = presentKeys.map((opKey) => { | ||
let value = opObj[opKey]; | ||
if (op === '$push' || op === '$addToSet') { | ||
if (typeof value === 'object' && '$each' in value) { | ||
value = value.$each; | ||
} | ||
else { | ||
opKey = `${opKey}.0`; | ||
} | ||
} | ||
// Don't use forEach here because it will not properly handle an | ||
// object that has a property named `length` | ||
Object.keys(opObj).forEach((k) => { | ||
let v = opObj[k]; | ||
if (op === '$push' || op === '$addToSet') { | ||
if (typeof v === 'object' && '$each' in v) { | ||
v = v.$each; | ||
} | ||
else { | ||
k = `${k}.0`; | ||
} | ||
return { key: opKey, value }; | ||
}); | ||
// For an upsert, missing props would not be set if an insert is performed, | ||
// so we check them all with undefined value to force any 'required' checks to fail | ||
if (isUpsert && (op === '$set' || op === '$setOnInsert')) { | ||
for (const key of schema.objectKeys()) { | ||
if (!presentKeys.includes(key)) { | ||
fields.push({ key, value: undefined }); | ||
} | ||
checkObj({ | ||
val: v, | ||
affectedKey: k, | ||
operator: op | ||
}); | ||
} | ||
} | ||
for (const field of fields) { | ||
const fieldErrors = validateField({ | ||
affectedKey: field.key, | ||
obj, | ||
op, | ||
schema, | ||
val: field.value, | ||
validationContext | ||
}); | ||
if (fieldErrors.length > 0) { | ||
validationErrors.push(...fieldErrors); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
// Kick off the validation | ||
if (isModifier) { | ||
checkModifier(obj); | ||
} | ||
else { | ||
checkObj({ val: obj }); | ||
const fieldErrors = validateField({ | ||
obj, | ||
schema, | ||
val: obj, | ||
validationContext | ||
}); | ||
if (fieldErrors.length > 0) { | ||
validationErrors.push(...fieldErrors); | ||
} | ||
} | ||
// Custom whole-doc validators | ||
// @ts-expect-error | ||
const docValidators = schema._docValidators.concat( | ||
// @ts-expect-error | ||
SimpleSchema._docValidators); | ||
const docValidatorContext = Object.assign({ ignoreTypes, | ||
const wholeDocumentErrors = validateDocument({ | ||
extendedCustomContext, | ||
ignoreTypes, | ||
isModifier, | ||
@@ -312,13 +79,9 @@ isUpsert, | ||
schema, | ||
validationContext }, (extendedCustomContext !== null && extendedCustomContext !== void 0 ? extendedCustomContext : {})); | ||
docValidators.forEach((func) => { | ||
const errors = func.call(docValidatorContext, obj); | ||
if (!Array.isArray(errors)) { | ||
throw new Error('Custom doc validator must return an array of error objects'); | ||
} | ||
if (errors.length > 0) | ||
validationErrors = validationErrors.concat(errors); | ||
validationContext | ||
}); | ||
const addedFieldNames = []; | ||
validationErrors = validationErrors.filter((errObj) => { | ||
if (wholeDocumentErrors.length > 0) { | ||
validationErrors.push(...wholeDocumentErrors); | ||
} | ||
const addedFieldNames = new Set(); | ||
return validationErrors.filter((errObj) => { | ||
// Remove error types the user doesn't care about | ||
@@ -328,9 +91,8 @@ if ((ignoreTypes === null || ignoreTypes === void 0 ? void 0 : ignoreTypes.includes(errObj.type)) === true) | ||
// Make sure there is only one error per fieldName | ||
if (addedFieldNames.includes(errObj.name)) | ||
if (addedFieldNames.has(errObj.name)) | ||
return false; | ||
addedFieldNames.push(errObj.name); | ||
addedFieldNames.add(errObj.name); | ||
return true; | ||
}); | ||
return validationErrors; | ||
} | ||
export default doValidation; |
@@ -77,2 +77,11 @@ import { ClientError } from './errors.js'; | ||
/** | ||
* @param key One specific or generic key for which to get all possible schemas. | ||
* @returns An potentially empty array of possible definitions for one key | ||
* | ||
* Note that this returns the raw, unevaluated definition object. Use `getDefinition` | ||
* if you want the evaluated definition, where any properties that are functions | ||
* have been run to produce a result. | ||
*/ | ||
schemas(key: string): StandardSchemaKeyDefinition[]; | ||
/** | ||
* @returns {Object} The entire schema object with subschemas merged. This is the | ||
@@ -96,2 +105,21 @@ * equivalent of what schema() returned in SimpleSchema < 2.0 | ||
/** | ||
* Returns the evaluated definition for one key in the schema | ||
* | ||
* @param key Generic or specific schema key | ||
* @param [propList] Array of schema properties you need; performance optimization | ||
* @param [functionContext] The context to use when evaluating schema options that are functions | ||
* @returns The schema definition for the requested key | ||
*/ | ||
getDefinitions(key: string, propList?: string[] | null, functionContext?: Record<string, unknown>): StandardSchemaKeyDefinitionWithSimpleTypes[]; | ||
/** | ||
* Resolves the definition for one key in the schema | ||
* | ||
* @param key Generic or specific schema key | ||
* @param schemaKeyDefinition Unresolved definition as returned from simpleSchema.schema() | ||
* @param [propList] Array of schema properties you need; performance optimization | ||
* @param [functionContext] The context to use when evaluating schema options that are functions | ||
* @returns The schema definition for the requested key | ||
*/ | ||
resolveDefinitionForSchema(key: string, schemaKeyDefinition: StandardSchemaKeyDefinition, propList?: string[] | null, functionContext?: Record<string, unknown>): StandardSchemaKeyDefinitionWithSimpleTypes; | ||
/** | ||
* Returns a string identifying the best guess data type for a key. For keys | ||
@@ -98,0 +126,0 @@ * that allow multiple types, the first type is used. This can be useful for |
@@ -201,2 +201,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
/** | ||
* @param key One specific or generic key for which to get all possible schemas. | ||
* @returns An potentially empty array of possible definitions for one key | ||
* | ||
* Note that this returns the raw, unevaluated definition object. Use `getDefinition` | ||
* if you want the evaluated definition, where any properties that are functions | ||
* have been run to produce a result. | ||
*/ | ||
schemas(key) { | ||
const schemas = []; | ||
const genericKey = MongoObject.makeKeyGeneric(key); | ||
const keySchema = genericKey == null ? null : this._schema[genericKey]; | ||
if (keySchema != null) | ||
schemas.push(keySchema); | ||
// See if it's defined in any subschema | ||
this.forEachAncestorSimpleSchema(key, (simpleSchema, ancestor, subSchemaKey) => { | ||
const keyDef = simpleSchema.schema(subSchemaKey); | ||
if (keyDef != null) | ||
schemas.push(keyDef); | ||
}); | ||
return schemas; | ||
} | ||
/** | ||
* @returns {Object} The entire schema object with subschemas merged. This is the | ||
@@ -234,6 +256,32 @@ * equivalent of what schema() returned in SimpleSchema < 2.0 | ||
getDefinition(key, propList, functionContext = {}) { | ||
const schemaKeyDefinition = this.schema(key); | ||
if (schemaKeyDefinition == null) | ||
return; | ||
return this.resolveDefinitionForSchema(key, schemaKeyDefinition, propList, functionContext); | ||
} | ||
/** | ||
* Returns the evaluated definition for one key in the schema | ||
* | ||
* @param key Generic or specific schema key | ||
* @param [propList] Array of schema properties you need; performance optimization | ||
* @param [functionContext] The context to use when evaluating schema options that are functions | ||
* @returns The schema definition for the requested key | ||
*/ | ||
getDefinitions(key, propList, functionContext = {}) { | ||
const schemaKeyDefinitions = this.schemas(key); | ||
return schemaKeyDefinitions.map((def) => { | ||
return this.resolveDefinitionForSchema(key, def, propList, functionContext); | ||
}); | ||
} | ||
/** | ||
* Resolves the definition for one key in the schema | ||
* | ||
* @param key Generic or specific schema key | ||
* @param schemaKeyDefinition Unresolved definition as returned from simpleSchema.schema() | ||
* @param [propList] Array of schema properties you need; performance optimization | ||
* @param [functionContext] The context to use when evaluating schema options that are functions | ||
* @returns The schema definition for the requested key | ||
*/ | ||
resolveDefinitionForSchema(key, schemaKeyDefinition, propList, functionContext = {}) { | ||
var _a; | ||
const defs = this.schema(key); | ||
if (defs == null) | ||
return; | ||
const getPropIterator = (obj, newObj) => { | ||
@@ -261,6 +309,6 @@ return (prop) => { | ||
}; | ||
Object.keys(defs).forEach(getPropIterator(defs, result)); | ||
Object.keys(schemaKeyDefinition).forEach(getPropIterator(schemaKeyDefinition, result)); | ||
// Resolve all the types and convert to a normal array to make it easier to use. | ||
if (Array.isArray((_a = defs.type) === null || _a === void 0 ? void 0 : _a.definitions)) { | ||
result.type = defs.type.definitions.map((typeDef) => { | ||
if (Array.isArray((_a = schemaKeyDefinition.type) === null || _a === void 0 ? void 0 : _a.definitions)) { | ||
result.type = schemaKeyDefinition.type.definitions.map((typeDef) => { | ||
const newTypeDef = { | ||
@@ -267,0 +315,0 @@ type: String // will be overwritten |
import { ObjectToValidate } from '../types.js'; | ||
export declare function appendAffectedKey(affectedKey: string | null | undefined, key: string): string | null | undefined; | ||
export declare function appendAffectedKey(affectedKey: string | undefined, key: string): string | undefined; | ||
/** | ||
@@ -4,0 +4,0 @@ * Given a Date instance, returns a date string of the format YYYY-MM-DD |
import MongoObject from 'mongo-object'; | ||
import doValidation from './doValidation.js'; | ||
import { looksLikeModifier } from './utility/index.js'; | ||
export default class ValidationContext { | ||
@@ -65,2 +66,9 @@ /** | ||
validate(obj, { extendedCustomContext = {}, ignore: ignoreTypes = [], keys: keysToValidate, modifier: isModifier = false, mongoObject, upsert: isUpsert = false } = {}) { | ||
// First do some basic checks of the object, and throw errors if necessary | ||
if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) { | ||
throw new Error('The first argument of validate() must be an object'); | ||
} | ||
if (!isModifier && looksLikeModifier(obj)) { | ||
throw new Error('When the validation object contains mongo operators, you must set the modifier option to true'); | ||
} | ||
const validationErrors = doValidation({ | ||
@@ -67,0 +75,0 @@ extendedCustomContext, |
{ | ||
"name": "simpl-schema", | ||
"version": "3.2.0", | ||
"version": "3.3.0", | ||
"description": "A schema validation package that supports direct validation of MongoDB update modifier objects.", | ||
@@ -5,0 +5,0 @@ "author": "Eric Dobbertin <eric@dairystatedesigns.com>", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
333595
83
6426