mongoose-beautiful-unique-validation
Advanced tools
Comparing version
# Changelog | ||
## v3.0.2 | ||
### Fixed bugs | ||
* Abandon parsing duplicated values using regexes and use | ||
JSON.parse for a more robust result. This allows duplicated | ||
values that contain spaces. | ||
## v3.0.1 | ||
@@ -4,0 +12,0 @@ |
61
index.js
@@ -7,3 +7,3 @@ 'use strict'; | ||
var errorRegex = /index:\s*(?:.+?\.\$)?(.*?)\s*dup key:\s*\{(.*?)\}/; | ||
var errorRegex = /index:\s*(?:.+?\.\$)?(.*?)\s*dup key:\s*(\{.*?\})/; | ||
var indexesCache = {}; | ||
@@ -58,7 +58,6 @@ | ||
function beautify(error, model, messages) { | ||
var matches, indexName, rawValues, valueRegex; | ||
return new Promise(function (resolve, reject) { | ||
// get index name from the error message | ||
matches = errorRegex.exec(error.message); | ||
// get the index name and duplicated values | ||
// from the error message (with a hacky regex) | ||
var matches = errorRegex.exec(error.message); | ||
@@ -74,6 +73,27 @@ if (!matches) { | ||
indexName = matches[1], rawValues = matches[2].trim() + ','; | ||
valueRegex = /\s*:\s*(\S*),/g; | ||
var indexName = matches[1]; | ||
var valuesHash = matches[2].trim(), valuesList; | ||
// look for the index contained in the MongoDB error | ||
// 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 | ||
// fail if we do not find such index | ||
getIndexes(model.collection).then(function (indexes) { | ||
@@ -93,19 +113,12 @@ var index = indexes[indexName]; | ||
// populate validation error with the index's fields | ||
index.forEach(function (item) { | ||
var value = valueRegex.exec(rawValues)[1], | ||
path = item[0], props; | ||
// the value is parsed directly from the error | ||
// string. We try to guess the value type (in a basic way) | ||
if (value[0] === '"' || value[0] === "'") { | ||
value = value.substr(1, value.length - 2); | ||
} else if (!isNaN(value)) { | ||
value = parseFloat(value); | ||
} | ||
props = { | ||
// the index contains the field keys in the same order | ||
// (hopefully) as in the original error message. Create a | ||
// duplication error for each field in the index, using | ||
// valuesList to get the duplicated values | ||
index.forEach(function (item, i) { | ||
var path = item[0]; | ||
var props = { | ||
type: 'Duplicate value', | ||
path: path, | ||
value: value | ||
value: valuesList[i] | ||
}; | ||
@@ -117,3 +130,3 @@ | ||
createdError.errors[item[0]] = | ||
createdError.errors[path] = | ||
new MongooseError.ValidatorError(props); | ||
@@ -120,0 +133,0 @@ }); |
{ | ||
"name": "mongoose-beautiful-unique-validation", | ||
"description": "Plug-in for Mongoose that turns duplicate errors into regular Mongoose validation errors", | ||
"version": "3.0.1", | ||
"version": "3.0.2", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "main": "index.js", |
@@ -6,66 +6,85 @@ 'use strict'; | ||
var mongoose = require('mongoose'); | ||
var Promise = require('promise'); | ||
var beautifulValidation = require('../'); | ||
// use global promise | ||
// see http://mongoosejs.com/docs/promises.html | ||
mongoose.Promise = Promise; | ||
/** | ||
* Create an unique model name | ||
* (should be sufficiently unique for a few tests) | ||
* Return a promise that is resolved | ||
* nth milliseconds afterwards | ||
* | ||
* @return {string} Random model name | ||
* @param {number} time Time to wait in milliseconds | ||
* @return {Promise} | ||
*/ | ||
function makeUniqueName() { | ||
return crypto.randomBytes(8).toString('hex'); | ||
function wait(time) { | ||
return new Promise(function (resolve) { | ||
setTimeout(resolve, time); | ||
}); | ||
} | ||
/** | ||
* Create a model with an unique "name" | ||
* property and a random name | ||
* Generate a 8-chars random string | ||
* (should be sufficiently unique for a few tests) | ||
* | ||
* @param {string} custom Custom unique validation message | ||
* @return {Model} Moongoose model | ||
* @return {string} Random string | ||
*/ | ||
function makeModel(custom, fieldName) { | ||
var fields = {}; | ||
fields[fieldName || 'name'] = { | ||
type: String, | ||
unique: custom || true | ||
}; | ||
var schema = new mongoose.Schema(fields); | ||
schema.plugin(beautifulValidation); | ||
return mongoose.model(makeUniqueName(), schema); | ||
function uniqueString() { | ||
return crypto.randomBytes(8).toString('hex'); | ||
} | ||
/** | ||
* Create a model with a compound unique index | ||
* Create a randomly-named model with any number | ||
* of fields. This model has an unique index over | ||
* all created fields | ||
* | ||
* @param {string} custom Custom unique validation message | ||
* @param {Array<string>|string} fieldNames Names of the fields | ||
* @param {string} [message=default message] Custom unique validation message | ||
* @return {Model} Mongoose model | ||
*/ | ||
function makeCompoundModel(custom) { | ||
var schema = new mongoose.Schema({ | ||
name: String, | ||
email: String | ||
}); | ||
function makeModel(fieldNames, message) { | ||
var schema, fields = {}; | ||
schema.index({ | ||
name: 1, | ||
email: 1 | ||
}, {unique: custom || true}); | ||
if (Array.isArray(fieldNames) && fieldNames.length > 1) { | ||
// if there is more than one field, | ||
// we create a compound index | ||
var index = {}; | ||
fieldNames.forEach(function (fieldName) { | ||
fields[fieldName] = String; | ||
index[fieldName] = 1; | ||
}); | ||
schema = new mongoose.Schema(fields); | ||
schema.index(index, { | ||
unique: message || true | ||
}); | ||
} else { | ||
// otherwise, we create a simple index | ||
if (Array.isArray(fieldNames)) { | ||
fieldNames = fieldNames[0]; | ||
} | ||
fields[fieldNames] = { | ||
type: String, | ||
unique: message || true | ||
}; | ||
schema = new mongoose.Schema(fields); | ||
} | ||
schema.plugin(beautifulValidation); | ||
return mongoose.model(makeUniqueName(), schema); | ||
return mongoose.model(uniqueString(), schema); | ||
} | ||
mongoose.connect('mongodb://127.0.0.1/test'); | ||
// 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 Model = makeModel(), | ||
instance, name = 'testing'; | ||
var name = 'normal-test', Model = makeModel('name'); | ||
instance = new Model({ | ||
new Model({ | ||
name: name | ||
}); | ||
instance.save(function (saveErr) { | ||
}).save(function (saveErr) { | ||
assert.error(saveErr, 'should save instance successfully'); | ||
@@ -84,10 +103,7 @@ | ||
test('should work with promises', function (assert) { | ||
var Model = makeModel(), | ||
instance, name = 'testing'; | ||
var name = 'promise-test', Model = makeModel('name'); | ||
instance = new Model({ | ||
new Model({ | ||
name: name | ||
}); | ||
instance.save().then(function () { | ||
}).save().then(function () { | ||
Model.find({ | ||
@@ -100,3 +116,3 @@ name: name | ||
}); | ||
}).catch(function (err) { | ||
}, function (err) { | ||
assert.error(err, 'should save instance successfully'); | ||
@@ -108,30 +124,30 @@ assert.end(); | ||
test('should emit duplicate validation error', function (assert) { | ||
var Model = makeModel(), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
var name = 'duplicate-test', Model = makeModel('name'); | ||
originalInst = new Model({ | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
duplicateInst = new Model({ | ||
// 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(); | ||
}); | ||
return duplicateInst.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.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -141,34 +157,24 @@ }); | ||
test('should work with compound unique indexes', function (assert) { | ||
var Model = makeCompoundModel(), | ||
name = 'duptest', email = 'duptest@example.com', | ||
originalInst, duplicateInst; | ||
test('should work with spaces in field name', function (assert) { | ||
var name = 'duplicate-test-spaces', Model = makeModel('display name'); | ||
originalInst = new Model({ | ||
name: name, | ||
email: email | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
// save the first instance | ||
new Model({ | ||
'display name': name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
duplicateInst = new Model({ | ||
name: name, | ||
email: email | ||
// 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(); | ||
}); | ||
return duplicateInst.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.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -178,5 +184,5 @@ }); | ||
test('should use custom validation message', function (assert) { | ||
var message = 'works!', Model = makeModel(message), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
test('should work with spaces in field value', function (assert) { | ||
var message = 'works!', Model = makeModel('name', message), | ||
originalInst, duplicateInst, name = 'dup test'; | ||
@@ -200,29 +206,70 @@ originalInst = new Model({ | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value'); | ||
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(); | ||
}); | ||
}); | ||
test('should work with spaces in field name', function (assert) { | ||
var message = 'works!', Model = makeModel(message, 'display name'), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
test('should work with compound unique indexes', function (assert) { | ||
var name = 'duplicate-test-compound', email = 'test@example.com', | ||
Model = makeModel(['name', 'email']); | ||
originalInst = new Model({ | ||
'display name': name | ||
}); | ||
// 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'); | ||
duplicateInst = new Model({ | ||
'display name': name | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
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 () { | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
// 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.equal(err.errors['display name'].properties.path, 'display name', 'should keep the key with spaces'); | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -233,23 +280,24 @@ }); | ||
test('should use custom validation message (compound index)', function (assert) { | ||
var message = 'works!', Model = makeCompoundModel(message), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
var name = 'duplicate-test-message-compound', message = 'works!', | ||
Model = makeModel(['name', 'email'], message); | ||
originalInst = new Model({ | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}); | ||
duplicateInst = new Model({ | ||
name: name | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
// 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.equal(err.errors.name.message, message, 'message should be our custom value (compound)'); | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -260,5 +308,11 @@ }); | ||
test('closing connection', function (assert) { | ||
mongoose.disconnect(); | ||
assert.end(); | ||
// 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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
28075
13.57%478
18.02%10
-9.09%