Merge branch 'release/1.5.0' into production
This commit is contained in:
commit
800202516c
|
@ -138,7 +138,14 @@ function GeoPoint(data) {
|
|||
assert(data.lat <= 90, 'lat must be <= 90');
|
||||
assert(data.lat >= -90, 'lat must be >= -90');
|
||||
|
||||
/**
|
||||
* @property {Number} lat The latitude point in degrees. Range: -90 to 90.
|
||||
*/
|
||||
this.lat = data.lat;
|
||||
|
||||
/**
|
||||
* @property {Number} lng The longitude point in degrees. Range: -90 to 90.
|
||||
*/
|
||||
this.lng = data.lng;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ exports.Validatable = Validatable;
|
|||
* This class provides methods that add validation cababilities to models.
|
||||
* Each of this validations run when `obj.isValid()` method called.
|
||||
*
|
||||
* Each configurator can accept n params (n-1 field names and one config). Config
|
||||
* is {Object} depends on specific validation, but all of them has one common part:
|
||||
* `message` member. It can be just string, when only one situation possible,
|
||||
* e.g. `Post.validatesPresenceOf('title', { message: 'can not be blank' });`
|
||||
* Each configurator can accept *n* params (*n*-1 field names and one config). Config
|
||||
* is {Object} depends on specific validation, but all of them have a
|
||||
* `message` member property. It can be just string, when only one situation possible,
|
||||
* For example: `Post.validatesPresenceOf('title', { message: 'can not be blank' });`
|
||||
*
|
||||
* In more complicated cases it can be {Hash} of messages (for each case):
|
||||
* `User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});`
|
||||
|
@ -24,24 +24,31 @@ function Validatable() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Validate presence. This validation fails when validated field is blank.
|
||||
*
|
||||
* Default error message "can't be blank"
|
||||
* Validate presence of one or more specified properties.
|
||||
* Requires a model to include a property to be considered valid; fails when validated field is blank.
|
||||
*
|
||||
* For example, validate presence of title
|
||||
* ```
|
||||
* Post.validatesPresenceOf('title');
|
||||
* ```
|
||||
*Example with custom message
|
||||
* Validate that model has first, last, and age properties:
|
||||
* ```
|
||||
* User.validatesPresenceOf('first', 'last', 'age');
|
||||
* ```
|
||||
* Example with custom message
|
||||
* ```
|
||||
* Post.validatesPresenceOf('title', {message: 'Cannot be blank'});
|
||||
* ```
|
||||
*
|
||||
* @param {String} propertyName One or more property names.
|
||||
* @options {Object} errMsg Optional custom error message. Default is "can't be blank"
|
||||
* @property {String} message Error message to use instead of default.
|
||||
*/
|
||||
Validatable.validatesPresenceOf = getConfigurator('presence');
|
||||
|
||||
/**
|
||||
* Validate length. Three kinds of validations: min, max, is.
|
||||
* Validate length. Require a property length to be within a specified range.
|
||||
* Three kinds of validations: min, max, is.
|
||||
*
|
||||
* Default error messages:
|
||||
*
|
||||
|
@ -61,11 +68,17 @@ Validatable.validatesPresenceOf = getConfigurator('presence');
|
|||
* User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}});
|
||||
* User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}});
|
||||
* ```
|
||||
* @param {String} propertyName Property name to validate.
|
||||
* @options {Object} Options
|
||||
* @property {Number} is Value that property must equal to validate.
|
||||
* @property {Number} min Value that property must be less than to be valid.
|
||||
* @property {Number} max Value that property must be less than to be valid.
|
||||
* @property {Object} message Optional Object with string properties for custom error message for each validation: is, min, or max
|
||||
*/
|
||||
Validatable.validatesLengthOf = getConfigurator('length');
|
||||
|
||||
/**
|
||||
* Validate numericality.
|
||||
* Validate numericality. Requires a value for property to be either an integer or number.
|
||||
*
|
||||
* Example
|
||||
* ```
|
||||
|
@ -73,19 +86,17 @@ Validatable.validatesLengthOf = getConfigurator('length');
|
|||
* User.validatesNumericalityOf('age', {int: true, message: { int: '...' }});
|
||||
* ```
|
||||
*
|
||||
* Default error messages:
|
||||
*
|
||||
* @param {String} propertyName Property name to validate.
|
||||
* @options {Object} Options
|
||||
* @property {Boolean} int If true, then property must be an integer to be valid.
|
||||
* @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages:
|
||||
* - number: is not a number
|
||||
* - int: is not an integer
|
||||
*
|
||||
* @sync
|
||||
* @nocode
|
||||
* @see helper/validateNumericality
|
||||
*/
|
||||
Validatable.validatesNumericalityOf = getConfigurator('numericality');
|
||||
|
||||
/**
|
||||
* Validate inclusion in set
|
||||
* Validate inclusion in set. Require a value for property to be in the specified array.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
|
@ -95,33 +106,35 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality');
|
|||
* });
|
||||
* ```
|
||||
*
|
||||
* Default error message: is not included in the list
|
||||
*
|
||||
* @sync
|
||||
* @nocode
|
||||
* @see helper/validateInclusion
|
||||
* @param {String} propertyName Property name to validate.
|
||||
* @options {Object} Options
|
||||
* @property {Array} in Array Property must match one of the values in the array to be valid.
|
||||
* @property {String} message Optional error message if property is not valid. Default error message: "is not included in the list".
|
||||
*/
|
||||
Validatable.validatesInclusionOf = getConfigurator('inclusion');
|
||||
|
||||
/**
|
||||
* Validate exclusion
|
||||
* Validate exclusion. Require a property value not be in the specified array.
|
||||
*
|
||||
* Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});`
|
||||
*
|
||||
* Default error message: is reserved
|
||||
*
|
||||
* @nocode
|
||||
* @see helper/validateExclusion
|
||||
* @param {String} propertyName Property name to validate.
|
||||
* @options {Object} Options
|
||||
* @property {Array} in Array Property must match one of the values in the array to be valid.
|
||||
* @property {String} message Optional error message if property is not valid. Default error message: "is reserved".
|
||||
*/
|
||||
Validatable.validatesExclusionOf = getConfigurator('exclusion');
|
||||
|
||||
/**
|
||||
* Validate format
|
||||
* Validate format. Require a model to include a property that matches the given format.
|
||||
*
|
||||
* Default error message: is invalid
|
||||
* Require a model to include a property that matches the given format. Example:
|
||||
* `User.validatesFormat('name', {with: /\w+/});`
|
||||
*
|
||||
* @nocode
|
||||
* @see helper/validateFormat
|
||||
* @param {String} propertyName Property name to validate.
|
||||
* @options {Object} Options
|
||||
* @property {RegExp} with Regular expression to validate format.
|
||||
* @property {String} message Optional error message if property is not valid. Default error message: " is invalid".
|
||||
*/
|
||||
Validatable.validatesFormatOf = getConfigurator('format');
|
||||
|
||||
|
@ -178,19 +191,32 @@ Validatable.validate = getConfigurator('custom');
|
|||
Validatable.validateAsync = getConfigurator('custom', {async: true});
|
||||
|
||||
/**
|
||||
* Validate uniqueness
|
||||
* Validate uniqueness. Ensure the value for property is unique in the collection of models.
|
||||
* Not available for all connectors. Currently supported with these connectors:
|
||||
* - In Memory
|
||||
* - Oracle
|
||||
* - MongoDB
|
||||
*
|
||||
* Default error message: is not unique
|
||||
* ```
|
||||
* // The login must be unique across all User instances.
|
||||
* User.validatesUniquenessOf('login');
|
||||
*
|
||||
* @async
|
||||
* @nocode
|
||||
* @see helper/validateUniqueness
|
||||
* // Assuming SiteUser.belongsTo(Site)
|
||||
* // The login must be unique within each Site.
|
||||
* SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] });
|
||||
* ```
|
||||
|
||||
* @param {String} propertyName Property name to validate.
|
||||
* @options {Object} Options
|
||||
* @property {RegExp} with Regular expression to validate format.
|
||||
* @property {Array.<String>} scopedTo List of properties defining the scope.
|
||||
* @property {String} message Optional error message if property is not valid. Default error message: "is not unique".
|
||||
*/
|
||||
Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true});
|
||||
|
||||
// implementation of validators
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Presence validator
|
||||
*/
|
||||
function validatePresence(attr, conf, err) {
|
||||
|
@ -199,7 +225,7 @@ function validatePresence(attr, conf, err) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Length validator
|
||||
*/
|
||||
function validateLength(attr, conf, err) {
|
||||
|
@ -217,7 +243,7 @@ function validateLength(attr, conf, err) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Numericality validator
|
||||
*/
|
||||
function validateNumericality(attr, conf, err) {
|
||||
|
@ -231,7 +257,7 @@ function validateNumericality(attr, conf, err) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Inclusion validator
|
||||
*/
|
||||
function validateInclusion(attr, conf, err) {
|
||||
|
@ -242,7 +268,7 @@ function validateInclusion(attr, conf, err) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Exclusion validator
|
||||
*/
|
||||
function validateExclusion(attr, conf, err) {
|
||||
|
@ -253,7 +279,7 @@ function validateExclusion(attr, conf, err) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Format validator
|
||||
*/
|
||||
function validateFormat(attr, conf, err) {
|
||||
|
@ -268,19 +294,28 @@ function validateFormat(attr, conf, err) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Custom validator
|
||||
*/
|
||||
function validateCustom(attr, conf, err, done) {
|
||||
conf.customValidator.call(this, err, done);
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Uniqueness validator
|
||||
*/
|
||||
function validateUniqueness(attr, conf, err, done) {
|
||||
var cond = {where: {}};
|
||||
cond.where[attr] = this[attr];
|
||||
|
||||
if (conf && conf.scopedTo) {
|
||||
conf.scopedTo.forEach(function(k) {
|
||||
var val = this[k];
|
||||
if (val !== undefined)
|
||||
cond.where[k] = this[k];
|
||||
}, this);
|
||||
}
|
||||
|
||||
this.constructor.find(cond, function (error, found) {
|
||||
if (error) {
|
||||
return err();
|
||||
|
@ -312,16 +347,14 @@ function getConfigurator(name, opts) {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method performs validation, triggers validation hooks.
|
||||
* Before validation `obj.errors` collection cleaned.
|
||||
* This method performs validation and triggers validation hooks.
|
||||
* Before validation the `obj.errors` collection is cleaned.
|
||||
* Each validation can add errors to `obj.errors` collection.
|
||||
* If collection is not blank, validation failed.
|
||||
*
|
||||
* @warning This method can be called as sync only when no async validation
|
||||
* NOTE: This method can be called as synchronous only when no asynchronous validation is
|
||||
* configured. It's strongly recommended to run all validations as asyncronous.
|
||||
*
|
||||
* Returns true if no async validation configured and all passed
|
||||
*
|
||||
* Example: ExpressJS controller: render user if valid, show flash otherwise
|
||||
* ```
|
||||
* user.isValid(function (valid) {
|
||||
|
@ -329,7 +362,21 @@ function getConfigurator(name, opts) {
|
|||
* else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users');
|
||||
* });
|
||||
* ```
|
||||
* Another example:
|
||||
* ```
|
||||
* user.isValid(function (valid) {
|
||||
* if (!valid) {
|
||||
* console.log(user.errors);
|
||||
* // => hash of errors
|
||||
* // => {
|
||||
* // => username: [errmessage, errmessage, ...],
|
||||
* // => email: ...
|
||||
* // => }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
* @param {Function} callback called with (valid)
|
||||
* @returns {Boolean} True if no asynchronouse validation is configured and all properties pass validation.
|
||||
*/
|
||||
Validatable.prototype.isValid = function (callback, data) {
|
||||
var valid = true, inst = this, wait = 0, async = false;
|
||||
|
@ -521,7 +568,7 @@ function nullCheck(attr, conf, err) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Return true when v is undefined, blank array, null or empty string
|
||||
* otherwise returns false
|
||||
*
|
||||
|
@ -586,6 +633,56 @@ function ErrorCodes(messages) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ValidationError is raised when the application attempts to save an invalid model instance.
|
||||
* Example:
|
||||
* ```
|
||||
* {
|
||||
* "name": "ValidationError",
|
||||
* "status": 422,
|
||||
* "message": "The Model instance is not valid. \
|
||||
* See `details` property of the error object for more info.",
|
||||
* "statusCode": 422,
|
||||
* "details": {
|
||||
* "context": "user",
|
||||
* "codes": {
|
||||
* "password": [
|
||||
* "presence"
|
||||
* ],
|
||||
* "email": [
|
||||
* "uniqueness"
|
||||
* ]
|
||||
* },
|
||||
* "messages": {
|
||||
* "password": [
|
||||
* "can't be blank"
|
||||
* ],
|
||||
* "email": [
|
||||
* "Email already exists"
|
||||
* ]
|
||||
* }
|
||||
* },
|
||||
* }
|
||||
* ```
|
||||
* You might run into situations where you need to raise a validation error yourself, for example in a "before" hook or a
|
||||
* custom model method.
|
||||
* ```
|
||||
* MyModel.prototype.preflight = function(changes, callback) {
|
||||
* // Update properties, do not save to db
|
||||
* for (var key in changes) {
|
||||
* model[key] = changes[key];
|
||||
* }
|
||||
*
|
||||
* if (model.isValid()) {
|
||||
* return callback(null, { success: true });
|
||||
* }
|
||||
*
|
||||
* // This line shows how to create a ValidationError
|
||||
* err = new ValidationError(model);
|
||||
* callback(err);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function ValidationError(obj) {
|
||||
if (!(this instanceof ValidationError)) return new ValidationError(obj);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-datasource-juggler",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"description": "LoopBack DataSoure Juggler",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// This test written in mocha+should.js
|
||||
var should = require('./init.js');
|
||||
var async = require('async');
|
||||
|
||||
var j = require('../'), db, User;
|
||||
var ValidationError = j.ValidationError;
|
||||
|
@ -172,6 +173,41 @@ describe('validations', function () {
|
|||
})).should.not.be.ok;
|
||||
});
|
||||
|
||||
it('should support multi-key constraint', function(done) {
|
||||
var EMAIL = 'user@xample.com';
|
||||
var SiteUser = db.define('SiteUser', {
|
||||
siteId: String,
|
||||
email: String
|
||||
});
|
||||
SiteUser.validatesUniquenessOf('email', { scopedTo: ['siteId'] });
|
||||
async.waterfall([
|
||||
function automigrate(next) {
|
||||
db.automigrate(next);
|
||||
},
|
||||
function createSite1User(next) {
|
||||
SiteUser.create(
|
||||
{ siteId: 1, email: EMAIL },
|
||||
next);
|
||||
},
|
||||
function createSite2User(user1, next) {
|
||||
SiteUser.create(
|
||||
{ siteId: 2, email: EMAIL },
|
||||
next);
|
||||
},
|
||||
function validateDuplicateUser(user2, next) {
|
||||
var user3 = new SiteUser({ siteId: 1, email: EMAIL });
|
||||
user3.isValid(function(valid) {
|
||||
valid.should.be.false;
|
||||
next();
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err && err.name == 'ValidationError') {
|
||||
console.error('ValidationError:', err.details.messages);
|
||||
}
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('format', function () {
|
||||
|
|
Loading…
Reference in New Issue