Socket
Socket
Sign inDemoInstall

simpl-schema

Package Overview
Dependencies
2
Maintainers
2
Versions
73
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.2.0 to 3.3.0

dist/cjs/validation/validateDocument.js

364

dist/cjs/doValidation.js
"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>",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc