SimpleSchema
aldeed:simple-schema
A simple, reactive schema validation package for Meteor. It's used by the Collection2 and AutoForm packages, but you can use it by itself, too.
Table of Contents
Table of Contents generated with DocToc
- Installation
- Basic Usage
- The Object to Validate
- Schema Keys
- Schema Rules
- Cleaning Data
- Validating Data
- Named Validation Contexts
- Unnamed Validation Contexts
- Validating an Object
- Validating Only One Key in an Object
- Validation Options
- Validating and Throwing ValidationErrors
- Validating Using check() or Match.test()
- Custom Validation
- Manually Adding a Validation Error
- Asynchronous Custom Validation on the Client
- Getting a List of Invalid Keys and Validation Error Messages
- Other Validation Context Methods
- Other SimpleSchema Methods
- Customizing Validation Messages
- Dates
- Collection2 and AutoForm
- Best Practice Code Examples
- Debug Mode
- Extending the Schema Options
- Add On Packages
- License
- Contributing
Installation
In your Meteor app directory, enter:
$ meteor add aldeed:simple-schema
Basic Usage
Create one or more SimpleSchema instances and then use them to validate objects. By adding the aldeed:collection2
package to your app, you can attach them to collections to get automatic validation of your insert and update operations. By adding the aldeed:autoform
package to your app, you can attach them to forms, making it easy to both generate and validate the forms from your schema.
Example
1// Define the schema 2BookSchema = new SimpleSchema({ 3 title: { 4 type: String, 5 label: "Title", 6 max: 200 7 }, 8 author: { 9 type: String, 10 label: "Author" 11 }, 12 copies: { 13 type: Number, 14 label: "Number of copies", 15 min: 0 16 }, 17 lastCheckedOut: { 18 type: Date, 19 label: "Last date this book was checked out", 20 optional: true 21 }, 22 summary: { 23 type: String, 24 label: "Brief summary", 25 optional: true, 26 max: 1000 27 } 28}); 29 30// Validate an object against the schema 31obj = {title: "Ulysses", author: "James Joyce"}; 32 33isValid = BookSchema.namedContext("myContext").validate(obj); 34// OR 35isValid = BookSchema.namedContext("myContext").validateOne(obj, "keyToValidate"); 36// OR 37isValid = Match.test(obj, BookSchema); 38// OR 39check(obj, BookSchema); 40 41// Validation errors are available through reactive methods 42if (Meteor.isClient) { 43 Meteor.startup(function() { 44 Tracker.autorun(function() { 45 var context = BookSchema.namedContext("myContext"); 46 if (!context.isValid()) { 47 console.log(context.invalidKeys()); 48 } 49 }); 50 }); 51}
Combining SimpleSchemas
If you have schemas that share one or more subproperties, you can define them in a sub-schema to make your code cleaner and more concise. Here's an example:
1AddressSchema = new SimpleSchema({ 2 street: { 3 type: String, 4 max: 100 5 }, 6 city: { 7 type: String, 8 max: 50 9 }, 10 state: { 11 type: String, 12 regEx: /^A[LKSZRAEP]|C[AOT]|D[EC]|F[LM]|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEHINOPST]|N[CDEHJMVY]|O[HKR]|P[ARW]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY]$/ 13 }, 14 zip: { 15 type: String, 16 regEx: /^[0-9]{5}$/ 17 } 18}); 19 20CustomerSchema = new SimpleSchema({ 21 billingAddress: { 22 type: AddressSchema 23 }, 24 shippingAddresses: { 25 type: [AddressSchema], 26 minCount: 1 27 } 28});
Alternatively, if you want to reuse mini-schemas in multiple places but you don't want a subdocument like you get with sub-schemas, you can pass multiple schemas to the SimpleSchema constructor, and they will be combined.
1cmsBaseSchema = new SimpleSchema({ ... }); 2cmsPageSchema = new SimpleSchema([cmsBaseSchema, {additionalField: {type: String} }]);
Extracting SimpleSchemas
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:
1var profileSchema = new SimpleSchema({ 2 firstName: {type: String}, 3 lastName: {type: String}, 4 username: {type: String} 5}); 6 7var nameSchema = profileSchema.pick(['firstName', 'lastName']);
NOTE: When using pick
on a field of type Array you also need to pick the array item field.
Take the following as an example:
1var profileSchema = new SimpleSchema({ 2 firstName: { 3 type: String 4 }, 5 lastName: { 6 type: String 7 }, 8 comments: { 9 type: [String] 10 } 11}); 12 13var nameSchema = profileSchema.pick('comments', 'comments.$');
The Object to Validate
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
smart package does for you.
Schema Keys
A basic schema key is just the name of the key (property) to expect in the objects that will be validated. If necessary, though, you can use string keys with mongo-style dot notation to validate nested arrays and objects.
For example:
1MySchema = new SimpleSchema({ 2 "mailingAddress.street": { 3 type: String 4 }, 5 "mailingAddress.city": { 6 type: String 7 } 8});
To indicate the presence of an array, use a $
:
1MySchema = new SimpleSchema({ 2 "addresses.$.street": { 3 type: String 4 }, 5 "addresses.$.city": { 6 type: String 7 } 8});
In the examples above, we did not explicitly define the mailingAddress
object or
the addresses
array or the addresses.$
object. This is fine because they will be
implicitly defined for you. However,
note that implicit objects and arrays of objects are assumed to be optional.
This means their required properties will only be required if the object
itself is present in the document or modifier being validated. So in general, it's
clearer if you explicitly define objects and arrays of objects in your schema.
Here's an example of explicitly defining an array of objects such that it will be
required and have a minimum and maximum array count:
1MySchema = new SimpleSchema({ 2 addresses: { 3 type: [Object], 4 minCount: 1, 5 maxCount: 4 6 }, 7 "addresses.$.street": { 8 type: String 9 }, 10 "addresses.$.city": { 11 type: String 12 } 13});
Schema Rules
Here are some specifics about the various rules you can define in your schema.
type
Type can be a standard Javascript object like:
String
Number
Boolean
Object
Or it can be a constructor function like Date
or any custom object.
Or it can be any of those wrapped in array brackets, to indicate that you're expecting an array of values of that type.
[String]
[Number]
[Boolean]
[Object]
[Date]
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 require a field that changes its meaning in some circumstances you can provide a callback function as a label.
1MySchema = new SimpleSchema({ 2 firstName: { 3 type: String, 4 label: function () { 5 return Session.get("lang") == "de" 6 ? "Vorname" : "first name"; 7 } 8 } 9});
Alternatively, you can use the labels
method to alter one or more labels on
the fly:
1MySchema.labels({ 2 password: "Enter your password" 3});
This method causes reactive labels to update.
To get the label for a field, use MySchema.label(fieldName)
, which returns
a usable string. This method is reactive.
optional
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:
- If
type
isArray
or is an array (any type surrounded by array brackets),
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.)
- For items within an array, or when the key name ends with ".$", the
optional
option has no effect. That is, something cannot be "required" to be in an array.
- If a key is required at a deeper level, the key must have a value only if
the object it belongs to is present.
- When the object being validated is a Mongo modifier object, changes that
would unset or null
a required key result in validation errors.
That last point can be confusing, so let's look at a couple examples:
- Say you have a required key "friends.address.city" but "friends.address" is
optional. If "friends.address" is set in the object you're validating, but "friends.address.city" is not, there is a validation error. However, if "friends.address" is not set, then there is no validation error for "friends.address.city" because the object it belongs to is not present.
- If you have a required key "friends.$.name", but the
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".
min/max
- If
type
isNumber
or[Number]
, these rules define the minimum or
maximum numeric value.
- If
type
isString
or[String]
, these rules define the minimum or
maximum string length.
- If
type
isDate
or[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".
exclusiveMin/exclusiveMax
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.
decimal
Set to true
if type
is Number
or [Number]
and you want to allow
non-integers. The default is false
.
minCount/maxCount
Define the minimum or maximum array length. Used only when type is an array
or is Array
.
allowedValues
An array of values that are allowed. A key will be invalid if its value is not one of these.
regEx
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.
The SimpleSchema.RegEx
object defines standard regular
expressions you can use as the value for the regEx
key.
SimpleSchema.RegEx.Email
for emails (uses a permissive regEx recommended by W3C, which most browsers use)SimpleSchema.RegEx.Domain
for external domains and the domain only (requires a tld like.com
)SimpleSchema.RegEx.WeakDomain
for less strict domains and IPv4 and IPv6SimpleSchema.RegEx.IP
for IPv4 or IPv6SimpleSchema.RegEx.IPv4
for just IPv4SimpleSchema.RegEx.IPv6
for just IPv6SimpleSchema.RegEx.Url
for http, https and ftp urlsSimpleSchema.RegEx.Id
for IDs generated byRandom.id()
of the random package, also usable to validate a relation id.SimpleSchema.RegEx.ZipCode
for 5- and 9-digit ZIP codes
Feel free to add more with a pull request. If
you use the built-in e-mail or url validation with an AutoForm, the
form input will be of type email
or url
, respectively, by default.
blackbox
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.
Custom object types are treated as blackbox objects by default. However,
when using collection2, you must ensure that the custom type is not lost
between client and server. This can be done with a transform
function that
converts the generic Object to the custom object. Without this transformation,
client-side inserts and updates might succeed on the client but then fail on
the server. Alternatively, if you don't care about losing the custom type,
you can explicitly set blackbox: true
for a custom object type instead of
using a transformation.
trim
Set to false
if the string value for this key should not be trimmed (i.e., leading and trailing spaces should be kept).
Otherwise, all strings are trimmed when you call mySimpleSchema.clean()
.
custom
Refer to the Custom Validation section.
defaultValue
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()
. Default
values are set only when cleaning non-modifier objects.
Note the following points of confusion:
- A default value itself is not cleaned. So, for example, if your default value is "", it will not be removed by the
removeEmptyStrings
operation in the cleaning. - A default value is always added if there isn't a value set. Even if the property is a child of an optional object, and the optional object is not present, the object will be added and its property will be set to the default value. Effectively, this means that if you provide a default value for one property of an object, you must provide a default value for all properties of that object or risk confusing validation errors.
If you need more control, use the autoValue
option instead.
autoValue
The autoValue
option allows you to specify a function that is called by
mySimpleSchema.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 is passed the document or modifier as its only argument,
but you will generally not need it. Instead, the function context provides a
variety of properties and methods to help you determine what you should return.
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}
.
The following properties and methods are available in this
for an autoValue
function:
isSet
: True if the field is already set in the document or modifierunset()
: Call this method to prevent the original value from being used when
you return undefined.
value
: If isSet = true, this contains the field's current (requested) value
in the document or modifier.
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".
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.
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.
Refer to the aldeed:collection2 package documentation for examples.
Cleaning Data
SimpleSchema instances provide a clean
method that cleans or alters data in
a number of ways. It's intended to be called prior to validation to avoid
any avoidable validation errors.
The clean
method takes the object to be cleaned as its first argument and
the following optional options as its second argument:
filter
: Filter out properties not found in the schema? True by default.autoConvert
: Type convert properties into the correct type where possible? True by default.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
: RunautoValue
functions and inject automatic anddefaultValue
values? True by default.isModifier
: Is the first argument a modifier object? False by default.extendAutoValueContext
: This object will be added to thethis
context of autoValue functions.
Additional notes:
- The object is cleaned in place. That is, the original referenced object will
be cleaned. You do not have to use the return value of the clean
method.
filter
removes any keys not explicitly or implicitly allowed by the schema,
which prevents errors being thrown for those keys during validation.
autoConvert
helps eliminate unnecessary validation
messages by automatically converting values where possible. For example, non-string values can be converted to a String if the schema expects a String, and strings that are numbers can be converted to Numbers if the schema expects a Number.
extendAutoValueContext
can be used to give yourautoValue
functions
additional valuable information, such as userId
. (Note that operations done
using the Collection2 package automatically add userId
to the autoValue
context already.)
1mySchema.clean(obj); 2//obj is now potentially changed
NOTE: The Collection2 package always calls clean
before every insert, update,
or upsert.
Validating Data
Before you can validate an object against your schema, you need to get a new validation context from the SimpleSchema. A validation context provides reactive methods for validating and checking the validation status of a particular object.
Named Validation Contexts
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.
To obtain a named validation context, call namedContext(name)
:
1var ss = new SimpleSchema({ 2 requiredString: { 3 type: String 4 } 5}); 6var ssContext1 = ss.namedContext("userForm");
The first time you request a context with a certain name, it is created. Calling
namedContext()
is equivalent to calling namedContext("default")
.
Unnamed Validation Contexts
To obtain an unnamed validation context, call newContext()
:
1var ss = new SimpleSchema({ 2 requiredString: { 3 type: String 4 } 5}); 6var ssContext1 = ss.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.
Validating an Object
To validate an object against the schema in a validation context, call
myContext.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 and causes the reactive methods to react.
Now you can call myContext.isValid()
to see if the object 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.
Validating Only One Key in an Object
You may have the need to validate just one key. For this,
use myContext.validateOne(obj, key, options)
. This works the same way as the
validate
method, except that only the specified schema key will be validated.
This may cause all of the reactive methods to react.
This method returns true
if the specified schema key is valid according to
the schema or false
if it is not.
Validation Options
Both validate()
and validateOne()
accept 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 thethis
context in
any custom validation functions that are run during validation. See the Custom Validation section.
Validating and Throwing ValidationErrors
- Call
mySimpleSchema.validate(doc)
to validatedoc
against the schema and throw aValidationError
if invalid. This is likecheck(doc, mySimpleSchema)
but without thecheck
dependency and with the ability to pass full schema error details back to a callback on the client. - Call
mySimpleSchema.validator()
to get a function that callsmySimpleSchema.validate
for whatever object is passed to it. This means you can dovalidate: mySimpleSchema.validator()
in the mdg:method package. If you set theclean
option totrue
, then the object will be cleaned before it is validated. If you want to change any of the default cleaning options, you can pass in those, too.
Validating Using check() or Match.test()
A schema can be passed as the second argument to Meteor's check()
and
Match.test()
methods from the Check package.
check()
will throw a Match.Error if the object specified in the first argument
is not valid according to the schema.
1var mySchema = new SimpleSchema({name: {type: String}}); 2 3Match.test({name: "Steve"}, mySchema); // Return true 4Match.test({admin: true}, mySchema); // Return false 5check({admin: true}, mySchema); // throw a Match.Error
Custom Validation
There are three ways to attach custom validation methods:
- To add a custom validation function that is called for all keys in all
defined schemas, use SimpleSchema.addValidator(myFunction)
.
- To add a custom validation function that is called for all keys for a
specific SimpleSchema instance, use mySimpleSchema.addValidator(myFunction)
.
- To add a custom validation function that is called for a specific key in
a specific schema, use the custom
option in the schema definition for that key.
All custom validation functions work the same way and have the same this
context:
- Do any necessary custom validation, and return a String describing the error type if you
determine that the value is invalid. Any non-string return value means the value is valid.
- The error type string can be one of the built-in strings
or any string you want. If you return a custom string, you'll usually want to define a message for it.
- Within the 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 benull
.field()
: 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.
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.
Manually Adding a Validation Error
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.addInvalidKeys(errors)
, where errors
is an array of error objects with the following format:
1{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 following built-in strings:- required
- minString
- maxString
- minNumber
- maxNumber
- minDate
- maxDate
- badDate
- minCount
- maxCount
- noDecimal
- notAllowed
- expectedString
- expectedNumber
- expectedBoolean
- expectedArray
- expectedObject
- expectedConstructor
- regEx
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 define a message for it.
(See Customizing Validation Messages).
Example:
1SimpleSchema.messages({wrongPassword: "Wrong password"}); 2 3myContext.addInvalidKeys([{name: "password", type: "wrongPassword"}]);
Asynchronous Custom Validation on the Client
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:
1username: { 2 type: String, 3 regEx: /^[a-z0-9A-Z_]{3,15}$/, 4 unique: true, 5 custom: function () { 6 if (Meteor.isClient && this.isSet) { 7 Meteor.call("accountsIsUsernameAvailable", this.value, function (error, result) { 8 if (!result) { 9 Meteor.users.simpleSchema().namedContext("createUserForm").addInvalidKeys([{name: "username", type: "notUnique"}]); 10 } 11 }); 12 } 13 } 14}
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.
Getting a List of Invalid Keys and Validation Error Messages
Call myContext.invalidKeys()
to get the full array of invalid key data. Each object
in the array has two keys:
name
: The schema key as specified in the schema.type
: The type of error. One of therequired*
,min*
,max*
etc. strings listed
at Manually Adding a Validation Error.
This is a reactive method.
There is no message
property. Once you see what keys are invalid, you can call ctxt.keyErrorMessage(key)
to get a reactive message string.
If you want to add a message
property to the invalidKeys array objects (which would no longer be reactive), you can do
1var ik = ctxt.invalidKeys(); 2ik = _.map(ik, function (o) { 3 return _.extend({message: ctxt.keyErrorMessage(o.name)}, o); 4});
Other Validation Context Methods
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.resetValidation()
if you need to reset the validation context,
clearing out any invalid field messages and making it valid.
Other SimpleSchema Methods
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.
Customizing Validation Messages
To customize validation messages, pass a messages object to either
SimpleSchema.messages()
or mySimpleSchemaInstance.messages()
. Instance-specific
messages are given priority over global messages.
The format of the messages object is:
1{ 2 errorType: message 3}
You can also specify override messages for specific fields:
1{ 2 "errorType schemaKey": message 3}
For the regEx
error type, you must specify a special message array of objects:
1{ 2 "regEx": [ 3 {msg: "Default Message"}, 4 {exp: SimpleSchema.RegEx.Url, msg: "You call that a URL?"} 5 ], 6 "regEx schemaKey": [ 7 {exp: SimpleSchema.RegEx.Url, msg: "It's very important that you enter a valid URL here"} 8 ] 9}
The message is a string. It can contain a number of different placeholders between square brackets:
[label]
will be replaced with the field label[min]
will be replaced with the minimum allowed value (string length, number, or date)[max]
will be replaced with the maximum allowed value (string length, number, or date)[minCount]
will be replaced with the minimum array count[maxCount]
will be replaced with the maximum array count[value]
will be replaced with the value that was provided to save but was invalid (not available for all error types)[type]
will be replaced with the expected type; useful for theexpectedConstructor
error type
By way of example, here is what it would look like if you defined the default error messages yourself:
1SimpleSchema.messages({ 2 required: "[label] is required", 3 minString: "[label] must be at least [min] characters", 4 maxString: "[label] cannot exceed [max] characters", 5 minNumber: "[label] must be at least [min]", 6 maxNumber: "[label] cannot exceed [max]", 7 minDate: "[label] must be on or after [min]", 8 maxDate: "[label] cannot be after [max]", 9 badDate: "[label] is not a valid date", 10 minCount: "You must specify at least [minCount] values", 11 maxCount: "You cannot specify more than [maxCount] values", 12 noDecimal: "[label] must be an integer", 13 notAllowed: "[value] is not an allowed value", 14 expectedString: "[label] must be a string", 15 expectedNumber: "[label] must be a number", 16 expectedBoolean: "[label] must be a boolean", 17 expectedArray: "[label] must be an array", 18 expectedObject: "[label] must be an object", 19 expectedConstructor: "[label] must be a [type]", 20 regEx: [ 21 {msg: "[label] failed regular expression validation"}, 22 {exp: SimpleSchema.RegEx.Email, msg: "[label] must be a valid e-mail address"}, 23 {exp: SimpleSchema.RegEx.WeakEmail, msg: "[label] must be a valid e-mail address"}, 24 {exp: SimpleSchema.RegEx.Domain, msg: "[label] must be a valid domain"}, 25 {exp: SimpleSchema.RegEx.WeakDomain, msg: "[label] must be a valid domain"}, 26 {exp: SimpleSchema.RegEx.IP, msg: "[label] must be a valid IPv4 or IPv6 address"}, 27 {exp: SimpleSchema.RegEx.IPv4, msg: "[label] must be a valid IPv4 address"}, 28 {exp: SimpleSchema.RegEx.IPv6, msg: "[label] must be a valid IPv6 address"}, 29 {exp: SimpleSchema.RegEx.Url, msg: "[label] must be a valid URL"}, 30 {exp: SimpleSchema.RegEx.Id, msg: "[label] must be a valid alphanumeric ID"} 31 ], 32 keyNotInSchema: "[key] is not allowed by the schema" 33});
You should call this method on both the client and the server to make sure
that your messages are consistent. You can call this method multiple times,
for example to change languages on the fly, and the messages on screen will
reactively change. If your message contains a [label]
placeholder, the
label name reactively updates when changed, too.
Dates
For consistency, you should generally validate and store Dates set to the UTC
time zone. If you care only about the date, then use a Date
object set to the
desired date at midnight UTC. If you need the time, too, then use a Date
object set to the desired date and time UTC.
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.
Collection2 and AutoForm
This all becomes pretty great when put to use in the Collection2 and AutoForm packages. Take a look at their documentation.
Best Practice Code Examples
Make a field conditionally required
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:
1{ 2 field: { 3 type: String, 4 optional: true, 5 custom: function () { 6 var shouldBeRequired = this.field('saleType').value == 1; 7 8 if (shouldBeRequired) { 9 // inserts 10 if (!this.operator) { 11 if (!this.isSet || this.value === null || this.value === "") return "required"; 12 } 13 14 // updates 15 else if (this.isSet) { 16 if (this.operator === "$set" && this.value === null || this.value === "") return "required"; 17 if (this.operator === "$unset") return "required"; 18 if (this.operator === "$rename") return "required"; 19 } 20 } 21 } 22 } 23}
Where customCondition
is whatever should trigger it being required.
Note: In the future we could make this a bit simpler by allowing optional
to be a function that returns
true or false. Pull request welcome.
Validate one key against another
Here's an example of declaring one value valid or invalid based on another value using a custom validation function.
1SimpleSchema.messages({ 2 "passwordMismatch": "Passwords do not match" 3}); 4 5MySchema = new SimpleSchema({ 6 password: { 7 type: String, 8 label: "Enter a password", 9 min: 8 10 }, 11 confirmPassword: { 12 type: String, 13 label: "Enter the password again", 14 min: 8, 15 custom: function () { 16 if (this.value !== this.field('password').value) { 17 return "passwordMismatch"; 18 } 19 } 20 } 21});
Debug Mode
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.
Extending the Schema Options
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:
1SimpleSchema.extendOptions({ 2 index: Match.Optional(Match.OneOf(Number, String, Boolean)), 3 unique: Match.Optional(Boolean), 4 denyInsert: Match.Optional(Boolean), 5 denyUpdate: Match.Optional(Boolean) 6});
Add On Packages
mxab:simple-schema-jsdoc Generate jsdoc from your schemas.
License
MIT
Contributing
Anyone is welcome to contribute. Fork, make and test your changes
(meteor test-packages ./
), and then submit a pull request.
Thanks
(Add your name if it's missing.)
- @mquandalle
- @Nemo64