mongoose-beautiful-unique-validation
Advanced tools
Comparing version 3.0.2 to 4.0.0
# Changelog | ||
## v4.0.0 | ||
### Breaking changes | ||
* Beautifies any kind of Mongoose field instead of passing | ||
along the original error when working with `ObjectId`s, `Buffer`s | ||
and `Date`s. | ||
* The `trySave()` method, deprecated in v3.0.0, is removed. | ||
## v3.0.2 | ||
@@ -4,0 +14,0 @@ |
43
index.js
@@ -7,3 +7,3 @@ 'use strict'; | ||
var errorRegex = /index:\s*(?:.+?\.\$)?(.*?)\s*dup key:\s*(\{.*?\})/; | ||
var errorRegex = /index:\s*(?:.+?\.\$)?(.*?)\s*dup/; | ||
var indexesCache = {}; | ||
@@ -73,24 +73,4 @@ | ||
var indexName = matches[1]; | ||
var valuesHash = matches[2].trim(), valuesList; | ||
var valuesMap = error.getOperation() || {}; | ||
// values hash is a pseudo-JSON hash containing the values | ||
// of the unique fields in the index, in which they keys are missing: | ||
// { : "value of field 1", : "value of field 2" } | ||
// we fix that by converting it to an array, then parsing it as JSON: | ||
// ["value of field 1", "value of field 2"] | ||
valuesHash = valuesHash.replace(/^\{ :/, '['); | ||
valuesHash = valuesHash.replace(/\}$/, ']'); | ||
valuesHash = valuesHash.replace(/, :/, ','); | ||
try { | ||
valuesList = JSON.parse(valuesHash); | ||
} catch (err) { | ||
reject(new Error( | ||
'mongoose-beautiful-unique-validation error: ' + | ||
'cannot parse duplicated values to a meaningful value. ' + | ||
'Parsing error: ' + err.message | ||
)); | ||
return; | ||
} | ||
// retrieve the index by name in the collection | ||
@@ -116,3 +96,3 @@ // fail if we do not find such index | ||
// valuesList to get the duplicated values | ||
index.forEach(function (item, i) { | ||
index.forEach(function (item) { | ||
var path = item[0]; | ||
@@ -122,3 +102,3 @@ var props = { | ||
path: path, | ||
value: valuesList[i] | ||
value: valuesMap[path] | ||
}; | ||
@@ -223,17 +203,2 @@ | ||
}; | ||
/** | ||
* Deprecated, use #save instead | ||
*/ | ||
schema.methods.trySave = function (callback) { | ||
// /!\ Deprecation warning | ||
console.warn( | ||
'mongoose-beautiful-unique-validation: Model#trySave() is ' + | ||
'deprecated, use Model#save() instead. To disable ' + | ||
'beautifying on plugged-in models, set the "beautifyUnique" ' + | ||
'option to false in this function\'s arguments' | ||
); | ||
return this.save({}, callback); | ||
}; | ||
}; |
{ | ||
"name": "mongoose-beautiful-unique-validation", | ||
"description": "Plug-in for Mongoose that turns duplicate errors into regular Mongoose validation errors", | ||
"version": "3.0.2", | ||
"description": "Plugin for Mongoose that turns duplicate errors into regular Mongoose validation errors", | ||
"version": "4.0.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "main": "index.js", |
# mongoose-beautiful-unique-validation | ||
Plug-in for Mongoose that turns duplicate errors into regular Mongoose validation errors. | ||
Plugin for Mongoose that turns duplicate errors into regular Mongoose | ||
validation errors. | ||
@@ -10,3 +11,4 @@ [![npm version](https://img.shields.io/npm/v/mongoose-beautiful-unique-validation.svg?style=flat-square)](https://www.npmjs.com/package/mongoose-beautiful-unique-validation) | ||
Mongoose's unique constraint actually relies on MongoDB's `unique` indexes. It means that, if you have a schema like that one: | ||
Mongoose's unicity constraint actually relies on MongoDB's `unique` indexes. | ||
It means that, if you have a schema like this one: | ||
@@ -22,3 +24,3 @@ ```js | ||
Unique constraint failures will be reported with this kind of error: | ||
Duplicates will be reported with this kind of error: | ||
@@ -35,5 +37,7 @@ ```json | ||
This is not the same kind of error as normal [Validation](http://mongoosejs.com/docs/validation.html) | ||
errors: it will introduce more checks in your code if you want to handle them. | ||
This plug-in solves this problem by converting uniqueness errors (E11000 and E11001) into regular Validation errors. | ||
This is not the same kind of error as normal | ||
[Validation](http://mongoosejs.com/docs/validation.html) errors, so you need | ||
to handle that as a special case―and special cases allow room for bugs. | ||
This plugin solves this problem by converting driver-level duplicate errors | ||
(E11000 and E11001) into regular Validation errors. | ||
@@ -117,6 +121,6 @@ ```json | ||
`Validator failed for path xxx with value xxx`. | ||
If you want to override it, add your custom message in the `unique` field (instead of `true`), | ||
during the schema's creation. | ||
If you want to override it, add your custom message in the `unique` | ||
field (instead of `true`), during the schema's creation. | ||
This plug-in overrides the `.save()` method to add beautifying | ||
This plugin overrides the `.save()` method to add beautifying | ||
behavior. If you do not want it, you can pass the | ||
@@ -126,3 +130,3 @@ `beautifyUnique` option to `false`: | ||
```js | ||
document.save({ | ||
doc.save({ | ||
beautifyUnique: false | ||
@@ -134,8 +138,18 @@ }).then(() => { | ||
## Contributing | ||
## Supported versions | ||
Check out the [contribution guide.](https://github.com/matteodelabre/mongoose-beautiful-unique-validation/blob/master/CONTRIBUTING.md) | ||
The latest version of this module supports all of the following Node.js versions. | ||
If you find a bug while using one of these versions, you can | ||
[fill a bug report](https://github.com/matteodelabre/mongoose-beautiful-unique-validation/issues/new) | ||
and we will take care of it as soon as possible! | ||
* 6.* | ||
* 5.* | ||
* 4.* | ||
* 0.12.* | ||
* 0.10.* | ||
## License | ||
See the [LICENSE.](https://github.com/matteodelabre/mongoose-beautiful-unique-validation/blob/master/LICENSE) | ||
Released under the MIT license. | ||
[See the full license text.](https://github.com/matteodelabre/mongoose-beautiful-unique-validation/blob/master/LICENSE) |
'use strict'; | ||
var test = require('tape'); | ||
var crypto = require('crypto'); | ||
var mongoose = require('mongoose'); | ||
var Promise = require('promise'); | ||
var Schema = mongoose.Schema; | ||
var assertDuplicateFailure = require('./utils').assertDuplicateFailure; | ||
var beautifulValidation = require('../'); | ||
// use global promise | ||
// see http://mongoosejs.com/docs/promises.html | ||
mongoose.Promise = Promise; | ||
/** | ||
* Return a promise that is resolved | ||
* nth milliseconds afterwards | ||
* | ||
* @param {number} time Time to wait in milliseconds | ||
* @return {Promise} | ||
*/ | ||
function wait(time) { | ||
return new Promise(function (resolve) { | ||
setTimeout(resolve, time); | ||
test('should save documents', function (assert) { | ||
var NormalSaveSchema = new Schema({ | ||
name: String | ||
}); | ||
} | ||
/** | ||
* Generate a 8-chars random string | ||
* (should be sufficiently unique for a few tests) | ||
* | ||
* @return {string} Random string | ||
*/ | ||
function uniqueString() { | ||
return crypto.randomBytes(8).toString('hex'); | ||
} | ||
NormalSaveSchema.plugin(beautifulValidation); | ||
/** | ||
* Create a randomly-named model with any number | ||
* of fields. This model has an unique index over | ||
* all created fields | ||
* | ||
* @param {Array<string>|string} fieldNames Names of the fields | ||
* @param {string} [message=default message] Custom unique validation message | ||
* @return {Model} Mongoose model | ||
*/ | ||
function makeModel(fieldNames, message) { | ||
var schema, fields = {}; | ||
var NormalSave = mongoose.model('NormalSave', NormalSaveSchema); | ||
var instance = new NormalSave({ | ||
name: 'Test for Normal Save' | ||
}); | ||
if (Array.isArray(fieldNames) && fieldNames.length > 1) { | ||
// if there is more than one field, | ||
// we create a compound index | ||
var index = {}; | ||
instance.save(function (saveError) { | ||
assert.error(saveError, 'should save an instance successfully'); | ||
fieldNames.forEach(function (fieldName) { | ||
fields[fieldName] = String; | ||
index[fieldName] = 1; | ||
NormalSave.find({ | ||
name: 'Test for Normal Save' | ||
}, function (findError, result) { | ||
assert.error(findError, 'should find back the saved instance'); | ||
assert.looseEqual(result[0].name, 'Test for Normal Save', 'should keep the document intact'); | ||
assert.end(); | ||
}); | ||
}); | ||
}); | ||
schema = new mongoose.Schema(fields); | ||
schema.index(index, { | ||
unique: message || true | ||
test('should provide a promise', function (assert) { | ||
var PromiseSaveSchema = new Schema({ | ||
name: String | ||
}); | ||
PromiseSaveSchema.plugin(beautifulValidation); | ||
var PromiseSave = mongoose.model('PromiseSave', PromiseSaveSchema); | ||
var instance = new PromiseSave({ | ||
name: 'Test for Promise Save' | ||
}); | ||
instance.save().then(function () { | ||
PromiseSave.find({ | ||
name: 'Test for Promise Save' | ||
}, function (findError, result) { | ||
assert.error(findError, 'should find back the saved instance'); | ||
assert.looseEqual(result[0].name, 'Test for Promise Save', 'should keep the document intact'); | ||
assert.end(); | ||
}); | ||
} else { | ||
// otherwise, we create a simple index | ||
if (Array.isArray(fieldNames)) { | ||
fieldNames = fieldNames[0]; | ||
} | ||
}).catch(function (error) { | ||
assert.error(error, 'should save an instance successfully'); | ||
}); | ||
}); | ||
fields[fieldNames] = { | ||
test('should report duplicates', function (assert) { | ||
var DuplicateSchema = new Schema({ | ||
address: { | ||
type: String, | ||
unique: message || true | ||
}; | ||
unique: true | ||
} | ||
}); | ||
schema = new mongoose.Schema(fields); | ||
} | ||
DuplicateSchema.plugin(beautifulValidation); | ||
var Duplicate = mongoose.model('Duplicate', DuplicateSchema); | ||
schema.plugin(beautifulValidation); | ||
return mongoose.model(uniqueString(), schema); | ||
} | ||
assertDuplicateFailure(assert, Duplicate, { | ||
address: '123 Fake St.' | ||
}); | ||
}); | ||
// connect to a database with a random name | ||
mongoose.connect('mongodb://127.0.0.1/mongoose-buv-' + uniqueString()); | ||
mongoose.connection.on('open', function () { | ||
test('should work like save', function (assert) { | ||
var name = 'normal-test', Model = makeModel('name'); | ||
test('should report duplicates on fields containing spaces', function (assert) { | ||
var SpacesSchema = new Schema({ | ||
'display name': { | ||
type: String, | ||
unique: true | ||
} | ||
}); | ||
new Model({ | ||
name: name | ||
}).save(function (saveErr) { | ||
assert.error(saveErr, 'should save instance successfully'); | ||
SpacesSchema.plugin(beautifulValidation); | ||
var Spaces = mongoose.model('Spaces', SpacesSchema); | ||
Model.find({ | ||
name: name | ||
}, function (findErr, found) { | ||
assert.error(findErr, 'should run find correctly'); | ||
assert.equal(found[0].name, name, 'should keep props & values'); | ||
assert.end(); | ||
}); | ||
}); | ||
assertDuplicateFailure(assert, Spaces, { | ||
'display name': 'Testing display names' | ||
}); | ||
}); | ||
test('should work with promises', function (assert) { | ||
var name = 'promise-test', Model = makeModel('name'); | ||
test('should report duplicates on compound indexes', function (assert) { | ||
var CompoundSchema = new Schema({ | ||
name: String, | ||
age: Number | ||
}); | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
Model.find({ | ||
name: name | ||
}, function (err, found) { | ||
assert.error(err, 'should run find correctly'); | ||
assert.equal(found[0].name, name, 'should keep props & values'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save instance successfully'); | ||
assert.end(); | ||
}); | ||
CompoundSchema.index({ | ||
name: 1, | ||
age: 1 | ||
}, { | ||
unique: true | ||
}); | ||
test('should emit duplicate validation error', function (assert) { | ||
var name = 'duplicate-test', Model = makeModel('name'); | ||
CompoundSchema.plugin(beautifulValidation); | ||
var Compound = mongoose.model('Compound', CompoundSchema); | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.ok(err, 'err should exist'); | ||
assert.equal(err.name, 'ValidationError', 'outer err should be of type ValidationError'); | ||
assert.equal(err.errors.name.name, 'ValidatorError', 'inner err should be ValidatorError'); | ||
assert.equal(err.errors.name.kind, 'Duplicate value', 'kind of err should be Duplicate value'); | ||
assert.equal(err.errors.name.properties.path, 'name', 'err should be on the correct path'); | ||
assert.equal(err.errors.name.properties.type, 'Duplicate value'); | ||
assert.equal(err.errors.name.properties.value, name, 'err should contain the problematic value'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
assertDuplicateFailure(assert, Compound, { | ||
name: 'John Doe', | ||
age: 42 | ||
}); | ||
}); | ||
test('should work with spaces in field name', function (assert) { | ||
var name = 'duplicate-test-spaces', Model = makeModel('display name'); | ||
// save the first instance | ||
new Model({ | ||
'display name': name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
'display name': name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors['display name'].properties.path, 'display name', 'should keep the key with spaces'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
test('should report duplicates with the custom validation message', function (assert) { | ||
var MessageSchema = new Schema({ | ||
address: { | ||
type: String, | ||
unique: 'this is our custom message!' | ||
} | ||
}); | ||
test('should work with spaces in field value', function (assert) { | ||
var message = 'works!', Model = makeModel('name', message), | ||
originalInst, duplicateInst, name = 'dup test'; | ||
MessageSchema.plugin(beautifulValidation); | ||
var Message = mongoose.model('Message', MessageSchema); | ||
originalInst = new Model({ | ||
name: name | ||
}); | ||
assertDuplicateFailure(assert, Message, { | ||
address: '123 Fake St.' | ||
}, 'this is our custom message!'); | ||
}); | ||
duplicateInst = new Model({ | ||
name: name | ||
}); | ||
test('should report duplicates on compound indexes with the custom validation message', function (assert) { | ||
var CompoundMessageSchema = new Schema({ | ||
name: String, | ||
age: Number | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}).then(function () { | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'should have correct error message'); | ||
assert.end(); | ||
}).catch(function (err) { | ||
console.error(err.stack); | ||
assert.fail('correct error message not found'); | ||
assert.end(); | ||
}); | ||
CompoundMessageSchema.index({ | ||
name: 1, | ||
age: 1 | ||
}, { | ||
unique: 'yet another custom message' | ||
}); | ||
test('should work with compound unique indexes', function (assert) { | ||
var name = 'duplicate-test-compound', email = 'test@example.com', | ||
Model = makeModel(['name', 'email']); | ||
CompoundMessageSchema.plugin(beautifulValidation); | ||
var CompoundMessage = mongoose.model('CompoundMessage', CompoundMessageSchema); | ||
// save the first instance | ||
new Model({ | ||
name: name, | ||
email: email | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name, | ||
email: email | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.ok(err, 'err should exist'); | ||
assert.equal(err.name, 'ValidationError', 'outer err should be of type ValidationError'); | ||
assert.equal(err.errors.name.name, 'ValidatorError', 'inner err should be ValidatorError'); | ||
assert.equal(err.errors.name.kind, 'Duplicate value', 'kind of err should be Duplicate value'); | ||
assert.equal(err.errors.name.properties.path, 'name', 'err should be on the correct path'); | ||
assert.equal(err.errors.name.properties.type, 'Duplicate value'); | ||
assert.equal(err.errors.name.properties.value, name, 'err should contain the problematic value'); | ||
assertDuplicateFailure(assert, CompoundMessage, { | ||
name: 'John Doe', | ||
age: 42 | ||
}, 'yet another custom message'); | ||
}); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
test('should report duplicates on any mongoose type', function (assert) { | ||
var AnyTypeSchema = new Schema({ | ||
name: String, | ||
group: Schema.Types.ObjectId, | ||
age: Number, | ||
date: Date, | ||
blob: Buffer, | ||
isVerified: Boolean, | ||
list: [] | ||
}); | ||
test('should use custom validation message', function (assert) { | ||
var name = 'duplicate-test-message', message = 'works!', | ||
Model = makeModel('name', message); | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
AnyTypeSchema.index({ | ||
name: 1, | ||
group: 1, | ||
age: 1, | ||
date: 1, | ||
blob: 1, | ||
isVerified: 1, | ||
list: 1 | ||
}, { | ||
unique: true | ||
}); | ||
test('should use custom validation message (compound index)', function (assert) { | ||
var name = 'duplicate-test-message-compound', message = 'works!', | ||
Model = makeModel(['name', 'email'], message); | ||
AnyTypeSchema.plugin(beautifulValidation); | ||
var AnyType = mongoose.model('AnyType', AnyTypeSchema); | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value (compound)'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
assertDuplicateFailure(assert, AnyType, { | ||
name: 'test', | ||
group: new mongoose.Types.ObjectId, | ||
age: 42, | ||
date: new Date, | ||
blob: new Buffer('abc'), | ||
isVerified: false, | ||
list: [1, 2, 3] | ||
}); | ||
test('closing connection', function (assert) { | ||
// clean up the test database | ||
mongoose.connection.db.dropDatabase().then(function () { | ||
mongoose.disconnect(); | ||
assert.end(); | ||
}).catch(function (err) { | ||
assert.error(err, 'should clean up the test database'); | ||
assert.end(); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
12
150
24462
405
1