aldeed:simple-schema

v1.5.0Published 9 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

Build Status

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

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 is Array 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 is Number or [Number], these rules define the minimum or

maximum numeric value.

  • If type is String or [String], these rules define the minimum or

maximum string length.

  • If type is Date 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 IPv6
  • SimpleSchema.RegEx.IP for IPv4 or IPv6
  • SimpleSchema.RegEx.IPv4 for just IPv4
  • SimpleSchema.RegEx.IPv6 for just IPv6
  • SimpleSchema.RegEx.Url for http, https and ftp urls
  • SimpleSchema.RegEx.Id for IDs generated by Random.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 modifier
  • unset(): 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: Run autoValue functions and inject automatic and defaultValue values? True by default.
  • isModifier: Is the first argument a modifier object? False by default.
  • extendAutoValueContext: This object will be added to the this 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 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.)

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 the this 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 validate doc against the schema and throw a ValidationError if invalid. This is like check(doc, mySimpleSchema) but without the check 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 calls mySimpleSchema.validate for whatever object is passed to it. This means you can do validate: mySimpleSchema.validator() in the mdg:method package. If you set the clean option to true, 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.

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 be null.
    • 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 the required*, 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 the expectedConstructor 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

Support via Gittip