Security News
vlt Debuts New JavaScript Package Manager and Serverless Registry at NodeConf EU
vlt introduced its new package manager and a serverless registry this week, innovating in a space where npm has stagnated.
simpl-schema
Advanced tools
A schema validation package that supports direct validation of MongoDB update modifier objects.
SimpleSchema validates JavaScript objects to ensure they match a schema. It can also clean the objects to automatically convert types, remove unsupported properties, and add automatic values such that the object is then more likely to pass validation.
There are a lot of similar packages for validating objects. These are some of the features of this package that might be good reasons to choose this one over another:
There are also reasons not to choose this package. Because of all it does, this package is more complex than (but still "simple" :) ) and slower than some other packages. Based on your needs, you should decide whether these tradeoffs are acceptable. One faster but less powerful option is simplecheck.
Table of Contents generated with DocToc
SimpleSchema was first released as a Meteor package in mid-2013. Version 1.0 was released in September 2014. In mid-2016, version 2.0 was released as an NPM package, which can be used in Meteor, NodeJS, or static browser apps.
If you are migrating from the Meteor package, refer to the CHANGELOG
npm install simpl-schema
There are other NPM packages named simpleschema
and simple-schema
. Make sure you install the right package. There is no "e" on "simpl".
NOTE: You may also need to load the babel-polyfill
package if you get any errors related to missing ES6 functions in certain browsers.
In this documentation:
import SimpleSchema from "simpl-schema";
new SimpleSchema({
name: String,
}).validate({
name: 2,
});
An error is thrown for the first invalid object found.
import SimpleSchema from "simpl-schema";
new SimpleSchema({
name: String,
}).validate([{ name: "Bill" }, { name: 2 }]);
import SimpleSchema from "simpl-schema";
const validationContext = new SimpleSchema({
name: String,
}).newContext();
validationContext.validate({
name: 2,
});
console.log(validationContext.isValid());
console.log(validationContext.validationErrors());
import SimpleSchema from "simpl-schema";
const validationContext = new SimpleSchema({
name: String,
}).newContext();
validationContext.validate(
{
$set: {
name: 2,
},
},
{ modifier: true }
);
console.log(validationContext.isValid());
console.log(validationContext.validationErrors());
TO DO
import SimpleSchema from "simpl-schema";
const mySchema = new SimpleSchema(
{
name: String,
},
{
clean: {
autoConvert: true,
extendAutoValueContext: {},
filter: false,
getAutoValues: true,
removeEmptyStrings: true,
removeNullsFromArrays: false,
trimStrings: true,
},
humanizeAutoLabels: false,
requiredByDefault: true,
}
);
These options will be used every time you clean or validate with this particular SimpleSchema instance.
import SimpleSchema from "simpl-schema";
SimpleSchema.constructorOptionDefaults({
clean: {
filter: false,
},
humanizeAutoLabels: false,
});
// If you don't pass in any options, it will return the current defaults.
console.log(SimpleSchema.constructorOptionDefaults());
These options will be used every time you clean or validate with any SimpleSchema instance, but can be overridden by options passed in to the constructor for a single instance.
Important notes:
SimpleSchema.constructorOptionDefaults
before any of your schemas are created, so put it in an entry-point file and/or at the top of your code file.SimpleSchema
objects. In other words, the import SimpleSchema
line in one package might be pulling in the SimpleSchema
object from one package while that line in another package pulls in a completely different SimpleSchema
object. It will be difficult to know that this is happening unless you notice that your defaults are not being used by some of your schemas. To solve this, you can call SimpleSchema.constructorOptionDefaults
multiple times or adjust your package dependencies to ensure that only one version of simpl-schema
is pulled into your project.import SimpleSchema from "simpl-schema";
const mySchema = new SimpleSchema({ name: String });
const doc = { name: 123 };
const cleanDoc = mySchema.clean(doc);
// cleanDoc is now mutated to hopefully have a better chance of passing validation
console.log(typeof cleanDoc.name); // string
Works for a MongoDB modifier, too:
import SimpleSchema from "simpl-schema";
const mySchema = new SimpleSchema({ name: String });
const modifier = { $set: { name: 123 } };
const cleanModifier = mySchema.clean(modifier);
// doc is now mutated to hopefully have a better chance of passing validation
console.log(typeof cleanModifier.$set.name); // string
Let's get into some more details about the different syntaxes that are supported when defining a schema. It's probably best to start with the simplest syntax. Here's an example:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
name: String,
age: SimpleSchema.Integer,
registered: Boolean,
});
This is referred to as "shorthand" syntax. You simply map a property name to a type. When validating, SimpleSchema will make sure that all of those properties are present and are set to a value of that type.
In many cases, you will need to use longhand in order to define additional rules beyond what the data type should be.
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
name: {
type: String,
max: 40,
},
age: {
type: SimpleSchema.Integer,
optional: true,
},
registered: {
type: Boolean,
defaultValue: false,
},
});
You can use any combination of shorthand and longhand:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
name: String,
age: {
type: SimpleSchema.Integer,
optional: true,
},
registered: Boolean,
});
If you set the schema key to a regular expression, then the type
will be String
and the string must match the provided regular expression.
For example, this:
{
exp: /foo/;
}
is equivalent to:
{
exp: { type: String, regEx: /foo/ }
}
You can also set the schema key to an array of some type:
{
friends: [String],
}
is equivalent to:
{
friends: { type: Array },
'friends.$': { type: String },
}
Note: This only applies to shorthand definitions, not to the longhand definition. This example will throw an error { friends: { type: [String] } }
even though it was valid in the meteor-version of this package.
You can define two or more different ways in which a key will be considered valid:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
id: SimpleSchema.oneOf(String, SimpleSchema.Integer),
name: String,
});
And this can be done in any mixture of shorthand and longhand:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
id: SimpleSchema.oneOf(
{
type: String,
min: 16,
max: 16,
},
{
type: SimpleSchema.Integer,
min: 0,
}
),
name: String,
});
When one of the allowed types is an object, use a subschema. Don't mix the object property definitions in with the main schema.
Correct:
import SimpleSchema from "simpl-schema";
const objSchema = new SimpleSchema({
_id: String,
});
const schema = new SimpleSchema({
foo: SimpleSchema.oneOf(String, objSchema),
});
Incorrect:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
foo: SimpleSchema.oneOf(String, Object),
"foo._id": {
type: String,
optional: true,
},
});
NOTE: Multiple definitions is still an experimental feature and may not work as you expect in complex situations, such as where one of the valid definitions is an object or array. By reporting any weirdness you experience, you can help make it more robust.
If there are certain fields that are repeated in many of your schemas, it can be useful to define a SimpleSchema instance just for those fields and then merge them into other schemas:
import SimpleSchema from "simpl-schema";
import { idSchema, addressSchema } from "./sharedSchemas";
const schema = new SimpleSchema({
name: String,
});
schema.extend(idSchema);
schema.extend(addressSchema);
If the key appears in both schemas, the definition will be extended such that the result is the combination of both definitions.
import SimpleSchema from "simpl-schema";
import { idSchema, addressSchema } from "./sharedSchemas";
const schema = new SimpleSchema({
name: {
type: String,
min: 5,
},
});
schema.extend({
name: {
type: String,
max: 15,
},
});
The above will result in the definition of the name
field becoming:
{
name: {
type: String,
min: 5,
max: 15,
},
}
Note also that a plain object was passed to extend
. If you pass a plain object, it is converted to a SimpleSchema
instance for you.
Similar to extending, you can also reference other schemas as a way to define objects that occur within the main object:
import SimpleSchema from "simpl-schema";
import { addressSchema } from "./sharedSchemas";
const schema = new SimpleSchema({
name: String,
homeAddress: addressSchema,
billingAddress: {
type: addressSchema,
optional: true,
},
});
Sometimes you have one large SimpleSchema object, and you need just a subset of it for some purpose.
To pull out certain schema keys into a new schema, you can use the pick
method:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
firstName: String,
lastName: String,
username: String,
});
const nameSchema = schema.pick("firstName", "lastName");
To keep all but certain keys in a new schema, you can use the omit
method:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
firstName: String,
lastName: String,
username: String,
});
const nameSchema = schema.omit("username");
To pull a subschema out of an Object
key in a larger schema, you can use getObjectSchema
:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
firstName: String,
lastName: String,
address: Object,
"address.street1": String,
"address.street2": { type: String, optional: true },
"address.city": String,
"address.state": String,
"address.postalCode": String,
});
const addressSchema = schema.getObjectSchema("address");
// addressSchema is now the same as this:
// new SimpleSchema({
// street1: String,
// street2: { type: String, optional: true },
// city: String,
// state: String,
// postalCode: String,
// });
Sometimes if you want to get the rawDefinition
of some schema just pass in the options { keepRawDefinition: true}
(if not arg is passed the value will be null). Example:
const userSchema = new SimpleSchema(
{
name: String,
number: "SimpleSchema.Integer",
email: String,
},
{ keepRawDefintion: true }
);
userSchema.rawDefinition;
//{
// name: String,
// number: 'SimpleSchema.Integer',
// email: String
//}
A basic schema key is just the name of the key (property) to expect in the objects that will be validated.
Use string keys with MongoDB-style dot notation to validate nested arrays and objects. For example:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
mailingAddress: Object,
"mailingAddress.street": String,
"mailingAddress.city": String,
});
To indicate array items, use a $
:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
addresses: {
type: Array,
minCount: 1,
maxCount: 4,
},
"addresses.$": Object,
"addresses.$.street": String,
"addresses.$.city": String,
});
Here are some specifics about the various rules you can define in your schema.
One of the following:
String
Number
SimpleSchema.Integer
(same as Number
but with decimals/floats disallowed)Boolean
Object
Array
Date
SimpleSchema
instance, meaning Object
type with this schemaSimpleSchema.oneOf(...)
, with multiple of the above typesCan also be a function that returns the label
A string that will be used to refer to this field in validation error messages. The default is an inflected (humanized) derivation of the key name itself. For example, the key "firstName" will have a default label of "First name" if you do not include the label
property in your definition.
You can use the labels
function to alter one or more labels on the fly:
schema.labels({
password: "Enter your password",
});
To get the label for a field, use schema.label(fieldName)
, which returns a usable string.
Can also be a function that returns true or false
By default, all keys are required. Set optional: true
to change that.
With complex keys, it might be difficult to understand what "required" means. Here's a brief explanation of how requiredness is interpreted:
type
is Array
, then "required" means that key must have a value, but an empty array is fine. (If an empty array is not fine, add the minCount: 1
option.)optional
is true, then null
values are valid. If array items are required, then any null
items will fail the type check.null
a required key result in validation errors.That last point can be confusing, so let's look at a couple examples:
friends
array has no objects in the object you are validating, there is no validation error for "friends.$.name". When the friends
array does have objects, every present object is validated, and each object could potentially have a validation error if it is missing the name
property. For example, when there are two objects in the friends array and both are missing the name
property, there will be a validation error for both "friends.0.name" and "friends.1.name".Can also be a function that returns true or false
If you would rather have all your schema keys be optional by default, pass the requiredByDefault: false
option and then use required: true
to make individual keys required.
const schema = new SimpleSchema(
{
optionalProp: String,
requiredProp: { type: String, required: true },
},
{ requiredByDefault: false }
);
Can also be a function that returns the min/max value
type
is Number
or SimpleSchema.Integer
, these rules define the minimum or maximum numeric value.type
is String
, these rules define the minimum or maximum string length.type
is Date
, these rules define the minimum or maximum date, inclusive.You can alternatively provide a function that takes no arguments and returns the appropriate minimum or maximum value. This is useful, for example, if the minimum Date for a field should be "today".
Can also be a function that returns true or false
Set to true
to indicate that the range of numeric values, as set by min/max, are to be treated as an exclusive range. Set to false
(default) to treat ranges as inclusive.
Can also be a function that returns the minCount/maxCount value
Define the minimum or maximum array length. Used only when type is Array
.
Can also be a function that returns the array or the Set
of allowed values
An array or a Set
of values that are allowed. A key will be invalid if its value is not one of these.
You can use schema.getAllowedValuesForKey(key)
to get the allowed values array for a key.
Note: If you wish to restrict the items allowed in an Array
, the allowedValues
property must be on the array item definition.
const schema = new SimpleSchema({
myArray: {
type: Array,
},
"myArray.$": {
type: String,
allowedValues: ["foo", "bar"],
},
});
Can also be a function that returns a regular expression or an array of them
Any regular expression that must be matched for the key to be valid, or an array of regular expressions that will be tested in order.
In earlier releases, the SimpleSchema.RegEx
object defined standard regular expressions that you could use as the value for the regEx
key. However, many of these were prone to DDoS security risks, causing this package to be flagged by security audit tooling. We no longer include any built-in regular expressions. We encourage you to use a custom
function or some other method of validating any strings with complex requirements. If necessary, you can look back at the code for previous SimpleSchema releases and copy specific built-in regular expressions into your codebase.
Also, check out recheck, which includes an ESLint plugin you can add to your project to avoid accidentally writing regular expressions that could be attacked.
Can also be a function that returns true or false
Set to true
when regEx
is set if you want an empty string to always pass validation, even though the regular expression may disallow it.
If you have a key with type Object
, the properties of the object will be validated as well, so you must define all allowed properties in the schema. If this is not possible or you don't care to validate the object's properties, use the blackbox: true
option to skip validation for everything within the object.
Prior to SimpleSchema 2.0, objects that are instances of a custom class were considered to be blackbox by default. This is no longer true, so if you do not want your class instance validated, be sure to add blackbox: true
in your schema.
Used by the cleaning process but not by validation
When you call simpleSchemaInstance.clean()
with trimStrings
set to true
, all string values are trimmed of leading and trailing whitespace. If you set trim
to false
for certain keys in their schema definition, those keys will be skipped.
Refer to the Custom Validation section.
Used by the cleaning process but not by validation
Set this to any value that you want to be used as the default when an object does not include this field or has this field set to undefined
. This value will be injected into the object by a call to mySimpleSchema.clean()
with getAutovalues: true
.
Note the following points of confusion:
removeEmptyStrings
operation in the cleaning.defaultValue: {}
to all ancestor objects.If you need more control, use the autoValue
option instead.
To get the defaultValue for a field, use schema.defaultValue(fieldName)
. It is a shorthand for schema.get(fieldName, 'defaultValue')
.
Used by the cleaning process but not by validation
The autoValue
option allows you to specify a function that is called by simpleSchemaInstance.clean()
to potentially change the value of a property in the object being cleaned. This is a powerful feature that allows you to set up either forced values or default values, potentially based on the values of other fields in the object.
An autoValue
function this
context provides a variety of properties and methods to help you determine what you should return:
this.closestSubschemaFieldName
: If your schema is used as a subschema in another schema, this will be set to the name of the key that references the schema. Otherwise it will be null
.this.field()
: Use this method to get information about other fields. Pass a field name (schema key) as the only argument. The return object will have isSet
, value
, and operator
properties for that field.this.genericKey
: The generic schema key for which the autoValue is running ($
in place of actual array index).this.isInArrayItemObject
: True if we're traversing an object that's in an array.this.isInSubObject
: True if we're traversing an object that's somewhere within another object.this.isModifier
: True if this is running on a MongoDB modifier object.this.isSet
: True if the field is already set in the document or modifierthis.key
: The schema key for which the autoValue is running. This is usually known, but if your autoValue function is shared among various keys or if your schema is used as a subschema in another schema, this can be useful.this.obj
: The full object.this.operator
: If isSet = true and isUpdate = true, this contains the name of the update operator in the modifier in which this field is being changed. For example, if the modifier were {$set: {name: "Alice"}}
, in the autoValue function for the name
field, this.isSet
would be true, this.value
would be "Alice", and this.operator
would be "$set".this.parentField()
: Use this method to get information about the parent object. Works the same way as field()
.this.siblingField()
: Use this method to get information about other fields that have the same parent object. Works the same way as field()
. This is helpful when you use sub-schemas or when you're dealing with arrays of objects.this.unset()
: Call this method to prevent the original value from being used when you return undefined.this.value
: If isSet = true, this contains the field's current (requested) value in the document or modifier.If an autoValue
function does not return anything (i.e., returns undefined
), the field's value will be whatever the document or modifier says it should be. If that field is already in the document or modifier, it stays in the document or modifier with the same value. If it's not in the document or modifier, it's still not there. If you don't want it to be in the doc or modifier, you must call this.unset()
.
Any other return value will be used as the field's value. You may also return special pseudo-modifier objects for update operations. Examples are {$inc: 1}
and {$push: new Date}
.
autoValue
function will always run during cleaning even if that field is not in the object being cleaned. This allows you to provide complex default values. If your function applies only when there is a value, you should add if (!this.isSet) return;
at the top.You may have noticed that many of the rule properties can be set to functions that return the value. If you do this, the this
context within those functions will have the following properties:
this.field()
: Use this method to get information about other fields. Pass a field name (schema key) as the only argument. The return object will have isSet
, value
, and operator
properties for that field.this.genericKey
: The generic schema key for which the autoValue is running ($
in place of actual array index).this.isInArrayItemObject
: True if we're traversing an object that's in an array.this.isInSubObject
: True if we're traversing an object that's somewhere within another object.this.isModifier
: True if this is running on a MongoDB modifier object.this.isSet
: True if the field is already set in the document or modifierthis.key
: The schema key for which the autoValue is running. This is usually known, but if your autoValue function is shared among various keys or if your schema is used as a subschema in another schema, this can be useful.this.obj
: The full object.this.operator
: If isSet = true and isUpdate = true, this contains the name of the update operator in the modifier in which this field is being changed. For example, if the modifier were {$set: {name: "Alice"}}
, in the autoValue function for the name
field, this.isSet
would be true, this.value
would be "Alice", and this.operator
would be "$set".this.parentField()
: Use this method to get information about the parent object. Works the same way as field()
.this.siblingField()
: Use this method to get information about other fields that have the same parent object. Works the same way as field()
. This is helpful when you use sub-schemas or when you're dealing with arrays of objects.this.validationContext
: The current validation contextthis.value
: If isSet = true, this contains the field's current (requested) value in the document or modifier.To obtain field's property value, just call the get
method.
const schema = new SimpleSchema({
friends: {
type: Array,
minCount: 0,
maxCount: 3,
},
});
schema.get("friends", "maxCount"); // 3
The object you pass in when validating can be a normal object, or it can be
a Mongo modifier object (with $set
, etc. keys). In other words, you can pass
in the exact object that you are going to pass to Collection.insert()
or
Collection.update()
. This is what the collection2 package does for you.
There are three ways to validate an object against your schema:
A validation context provides reactive methods for validating and checking the validation status of a particular object.
It's usually best to use a named validation context. That way, the context is automatically persisted by name, allowing you to easily rely on its reactive methods.
Here is an example of obtaining a named validation context:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
name: String,
});
const userFormValidationContext = schema.namedContext("userForm");
The first time you request a context with a certain name, it is created. Calling namedContext()
passing no arguments is equivalent to calling namedContext('default')
.
To obtain an unnamed validation context, call newContext()
:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
name: String,
});
const myValidationContext = schema.newContext();
An unnamed validation context is not persisted anywhere. It can be useful when you need to see if a document is valid but you don't need any of the reactive methods for that context, or if you are going to keep the context reference in memory yourself.
To validate an object against the schema in a validation context, call validationContextInstance.validate(obj, options)
. This method returns true
if the object is valid according to the schema or false
if it is not. It also stores a list of invalid fields and corresponding error messages in the context object.
You can call myContext.isValid()
to see if the object last passed into validate()
was found to be valid. This is a reactive method that returns true
or false
.
For a list of options, see the Validation Options section.
You may have the need to (re)validate certain keys while leaving any errors for other keys unchanged. For example, if you have several errors on a form and you want to revalidate only the invalid field the user is currently typing in. For this situation, call myContext.validate
with the keys
option set to an array of keys that should be validated. This may cause all of the reactive methods to react.
This method returns true
only if all the specified schema keys and their descendent keys are valid according to the schema. Otherwise it returns false
.
validate()
accepts the following options:
modifier
: Are you validating a Mongo modifier object? False by default.upsert
: Are you validating a Mongo modifier object potentially containing upsert operators? False by default.extendedCustomContext
: This object will be added to the this
context in any custom validation functions that are run during validation. See the Custom Validation section.ignore
: An array of validation error types (in SimpleSchema.ErrorTypes enum) to ignore.keys
: An array of keys to validate. If not provided, revalidates the entire object.mySimpleSchema.validate(obj, options)
to validate obj
against the schema and throw a ValidationError
if invalid.SimpleSchema.validate(obj, schema, options)
static function as a shortcut for mySimpleSchema.validate
if you don't want to create mySimpleSchema
first. The schema
argument can be just the schema object, in which case it will be passed to the SimpleSchema
constructor for you. This is like check(obj, schema)
but without the check
dependency and with the ability to pass full schema error details back to a callback on the client.mySimpleSchema.validator()
to get a function that calls mySimpleSchema.validate
for whatever object is passed to it. This means you can do validate: mySimpleSchema.validator()
in the mdg:validated-method package.mySimpleSchema.getFormValidator()
to get a function that validates whatever object is passed to it and returns a Promise that resolves with errors. The returned function is compatible with the Composable Form Specification.You can defineValidationErrorTransform
one time somewhere in your code to customize the error or change it to a more specific type.
import SimpleSchema from "simpl-schema";
SimpleSchema.defineValidationErrorTransform((error) => {
const customError = new MyCustomErrorType(error.message);
customError.errorList = error.details;
return customError;
});
For example, in a Meteor app, in order to ensure that the error details are sent back to the client when throwing an error in a server method, you can convert it to a Meteor.Error
:
import SimpleSchema from "simpl-schema";
SimpleSchema.defineValidationErrorTransform((error) => {
const ddpError = new Meteor.Error(error.message);
ddpError.error = "validation-error";
ddpError.details = error.details;
return ddpError;
});
There are three ways to attach custom validation methods.
To add a custom validation function that is called for ALL keys in ALL schemas (for example, to publish a package that adds global support for some additional rule):
SimpleSchema.addValidator(myFunction);
To add a custom validation function that is called for ALL keys for ONE schema:
import SimpleSchema from 'simpl-schema';
const schema = new SimpleSchema({ ... });
schema.addValidator(myFunction);
To add a custom validation function that is called for ONE key in ONE schema:
import SimpleSchema from "simpl-schema";
const schema = new SimpleSchema({
someKey: {
type: String,
custom: myFunction,
},
});
All custom validation functions work the same way. First, do the necessary custom validation, use this
to get whatever information you need. Then, if valid, return undefined
. If invalid, return an error type string. The error type string can be one of the built-in strings or any string you want.
SimpleSchema.ErrorTypes
constants.Within your custom validation function, this
provides the following properties:
key
: The name of the schema key (e.g., "addresses.0.street")genericKey
: The generic name of the schema key (e.g., "addresses.$.street")definition
: The schema definition object.isSet
: Does the object being validated have this key set?value
: The value to validate.operator
: The Mongo operator for which we're doing validation. Might be null
.validationContext
: The current ValidationContext
instancefield()
: Use this method to get information about other fields. Pass a field name (non-generic schema key) as the only argument. The return object will have isSet
, value
, and operator
properties for that field.siblingField()
: Use this method to get information about other fields that have the same parent object. Works the same way as field()
. This is helpful when you use sub-schemas or when you're dealing with arrays of objects.addValidationErrors(errors)
: Call this to add validation errors for any key. In general, you should use this to add errors for other keys. To add an error for the current key, return the error type string. If you do use this to add an error for the current key, return false
from your custom validation function.NOTE: If you need to do some custom validation on the server and then display errors back on the client, refer to the Asynchronous Custom Validation on the Client section.
Add a validator for all schemas:
import SimpleSchema from "simpl-schema";
SimpleSchema.addDocValidator((obj) => {
// Must return an array, potentially empty, of objects with `name` and `type` string properties and optional `value` property.
return [{ name: "firstName", type: "TOO_SILLY", value: "Reepicheep" }];
});
Add a validator for one schema:
import SimpleSchema from 'simpl-schema';
const schema = new SimpleSchema({ ... });
schema.addDocValidator(obj => {
// Must return an array, potentially empty, of objects with `name` and `type` string properties and optional `value` property.
return [
{ name: 'firstName', type: 'TOO_SILLY', value: 'Reepicheep' }
];
});
Whole-document validators have the following available on this
context:
this.ignoreTypes
: The value of the ignore
option that was passed to validate
.this.isModifier
: True if this is running on a MongoDB modifier object.this.isUpsert
: True if this is running on a MongoDB modifier object that is for an upsert.this.keysToValidate
: The value of the keys
option that was passed to validate
.this.mongoObject
: The MongoObject
instance.this.obj
: The full object.this.schema
: The schema instance.this.validationContext
: The ValidationContext
instance.If you want to reactively display an arbitrary validation error and it is not possible to use a custom validation function (perhaps you have to call a function onSubmit
or wait for asynchronous results), you can add one or more errors to a validation context at any time by calling myContext.addValidationErrors(errors)
, where errors
is an array of error objects with the following format:
{name: key, type: errorType, value: anyValue}
name
: The schema key as specified in the schema.type
: The type of error. Any string you want, or one of the strings in the SimpleSchema.ErrorTypes
list.value
: Optional. The value that was not valid. Will be used to replace the [value]
placeholder in error messages.If you use a custom string for type
, be sure to register a getErrorMessage function. (See Customizing Validation Messages).
Example:
myValidationContext.addValidationErrors([
{ name: "password", type: "wrongPassword" },
]);
NOTE: To use the unique
option in this example, you need to be in a Meteor app with the aldeed:schema-index
package added.
Validation runs synchronously for many reasons, and likely always will. This makes it difficult to wait for asynchronous results as part of custom validation. Here's one example of how you might validate that a username is unique on the client, without publishing all usernames to every client:
username: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/,
unique: true,
custom() {
if (Meteor.isClient && this.isSet) {
Meteor.call("accountsIsUsernameAvailable", this.value, (error, result) => {
if (!result) {
this.validationContext.addValidationErrors([{
name: "username",
type: "notUnique"
}]);
}
});
}
}
}
Note that we're calling our "accountsIsUsernameAvailable" server method and waiting for an asynchronous result, which is a boolean that indicates whether that username is available. If it's taken, we manually invalidate the username
key with a "notUnique" error.
This doesn't change the fact that validation is synchronous. If you use this with an autoform and there are no validation errors, the form would still be submitted. However, the user creation would fail and a second or two later, the form would display the "notUnique" error, so the end result is very similar to actual asynchronous validation.
You can use a technique similar to this to work around asynchronicity issues in both client and server code.
This is a reactive method if you have enabled Tracker reactivity.
Call myValidationContext.validationErrors()
to get the full array of validation errors. Each object in the array has at least two keys:
name
: The schema key as specified in the schema.type
: The type of error. See SimpleSchema.ErrorTypes
.There may also be a value
property, which is the value that was invalid.
There may be a message
property, but usually the error message is constructed from message templates. You should call ctxt.keyErrorMessage(key)
to get a message string rather than using error.message
directly.
The built-in validation errors have built-in English messages associated with them. However, if you have custom validation error types, need messages in other languages, or just want to change some default messages, you will want to register a getErrorMessage
function for your schema.
const schema = new SimpleSchema({
name: String
}, {
getErrorMessage(error, label) {
if (error.type === 'too_long') return `${label} is too long!`
// Returning undefined will fall back to using defaults
}
})
You can also do this globally for all schemas:
globalThis.simpleSchemaGlobalConfig = {
getErrorMessage(error, label) {
if (error.type === 'too_long') return `${label} is too long!`
// Returning undefined will fall back to using defaults
}
};
A getErrorMessage
function in schema options will be tried before a global getErrorMessage
function. If the schema getErrorMessage
returns undefined
, the global getErrorMessage
will be called, and if that returns undefined
, the built-in English message will be used.
myContext.keyIsInvalid(key)
returns true if the specified key is currently
invalid, or false if it is valid. This is a reactive method.
myContext.keyErrorMessage(key)
returns the error message for the specified
key if it is invalid. If it is valid, this method returns an empty string. This
is a reactive method.
Call myContext.reset()
if you need to reset the validation context, clearing out any invalid field messages and making it valid.
myContext.name
is set to the context name, if it is a named context. Create named contexts by calling schema.namedContext(name)
or new ValidationContext(schema, name)
.
Call MySchema.schema([key])
to get the schema definition object. If you specify a key, then only the schema definition for that key is returned.
Note that this may not match exactly what you passed into the SimpleSchema constructor. The schema definition object is normalized internally, and this method returns the normalized copy.
You can call simpleSchemaInstance.clean()
or simpleSchemaValidationContextInstance.clean()
to clean the object you're validating. Do this prior to validating it to avoid any avoidable validation errors.
The clean
function takes the object to be cleaned as its first argument and the following optional options as its second argument:
mutate
: The object is copied before being cleaned. If you don't mind mutating the object you are cleaning, you can pass mutate: true
to get better performance.isModifier
: Is the first argument a modifier object? False by default.filter
: true
by default. If true
, removes any keys not explicitly or implicitly allowed by the schema, which prevents errors being thrown for those keys during validation.autoConvert
: true
by default. If true
, helps eliminate unnecessary validation messages by automatically converting values where possible.
false
and all other numbers being true
removeEmptyStrings
: Remove keys in normal object or $set where the value is an empty string? True by default.trimStrings
: Remove all leading and trailing spaces from string values? True by default.getAutoValues
: Run autoValue
functions and inject automatic and defaultValue
values? True by default.extendAutoValueContext
: This object will be added to the this
context of autoValue functions. extendAutoValueContext
can be used to give your autoValue
functions additional valuable information, such as userId
. (Note that operations done using the Collection2 package automatically add userId
to the autoValue
context already.)You can also set defaults for any of these options in your SimpleSchema constructor options:
const schema = new SimpleSchema(
{
name: String,
},
{
clean: {
trimStrings: false,
},
}
);
NOTE: The Collection2 package always calls clean
before every insert, update, or upsert.
For consistency, if you care only about the date (year, month, date) portion and not the time, then use a Date
object set to the desired date at midnight UTC (note, the clean function won't strip out time). This goes for min
and max
dates, too. If you care only about the date
portion and you want to specify a minimum date, min
should be set to midnight UTC on the minimum date (inclusive).
Following these rules ensures maximum interoperability with HTML5 date inputs and usually just makes sense.
If you have a field that should be required only in certain circumstances, first make the field optional, and then use a custom function similar to this:
{
field: {
type: String,
optional: true,
custom: function () {
let shouldBeRequired = this.field('saleType').value === 1;
if (shouldBeRequired) {
// inserts
if (!this.operator) {
if (!this.isSet || this.value === null || this.value === "") return SimpleSchema.ErrorTypes.REQUIRED;
}
// updates
else if (this.isSet) {
if (this.operator === "$set" && this.value === null || this.value === "") return SimpleSchema.ErrorTypes.REQUIRED;
if (this.operator === "$unset") return SimpleSchema.ErrorTypes.REQUIRED;
if (this.operator === "$rename") return SimpleSchema.ErrorTypes.REQUIRED;
}
}
}
}
}
Where customCondition
is whatever should trigger it being required.
Here's an example of declaring one value valid or invalid based on another value using a custom validation function.
MySchema = new SimpleSchema({
password: {
type: String,
label: "Enter a password",
min: 8,
},
confirmPassword: {
type: String,
label: "Enter the password again",
min: 8,
custom() {
if (this.value !== this.field("password").value) {
return "passwordMismatch";
}
},
},
});
Set SimpleSchema.debug = true
in your app before creating any named
validation contexts to cause all named validation contexts to automatically
log all invalid key errors to the browser console. This can be helpful while
developing an app to figure out why certain actions are failing validation.
You may find at some point that there is something extra you would really like to define within a schema for your package or app. However, if you add unrecognized options to your schema definition, you will get an error. To inform SimpleSchema about your custom option and avoid the error, you need to call SimpleSchema.extendOptions
. By way of example, here is how the Collection2 package adds the additional schema options it provides:
SimpleSchema.extendOptions(["index", "unique", "denyInsert", "denyUpdate"]);
Obviously you need to ensure that extendOptions
is called before any SimpleSchema instances are created with those options.
mxab:simple-schema-jsdoc Generate jsdoc from your schemas.
(Submit a PR to list your package here)
This project exists thanks to all the people who contribute. [Contribute].
You can support this project by becoming a sponsor.
MIT
Anyone is welcome to contribute. Before submitting a pull request, make sure that you've added tests for your changes, and that all tests pass when you run npm test
.
(Add your name if it's missing.)
FAQs
A schema validation package that supports direct validation of MongoDB update modifier objects.
The npm package simpl-schema receives a total of 20,416 weekly downloads. As such, simpl-schema popularity was classified as popular.
We found that simpl-schema demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt introduced its new package manager and a serverless registry this week, innovating in a space where npm has stagnated.
Security News
Research
The Socket Research Team uncovered a malicious Python package typosquatting the popular 'fabric' SSH library, silently exfiltrating AWS credentials from unsuspecting developers.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.