diff --git a/README.md b/README.md index adc383f..e5cc8f2 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ The `default` option supports both values and no-argument functions (like `Date. - `match`: A regex string that should match the value *(optional)* - `validate`: A 1-argument function that returns `false` if the value is invalid *(optional)* - `unique`: A boolean value indicating if a 'unique' index should be set *(optional)* +- `required`: A boolean value indicating if a key value is required *(optional)* To reference another document, just use its class name as the type. diff --git a/lib/base-document.js b/lib/base-document.js index 1b8ca12..6b0d935 100644 --- a/lib/base-document.js +++ b/lib/base-document.js @@ -4,6 +4,7 @@ var _ = require('lodash'); var DB = require('./clients').getClient; var isSupportedType = require('./validate').isSupportedType; var isValidType = require('./validate').isValidType; +var isEmptyValue = require('./validate').isEmptyValue; var isInChoices = require('./validate').isInChoices; var isArray = require('./validate').isArray; var isDocument = require('./validate').isDocument; @@ -139,6 +140,11 @@ class BaseDocument { ' should be ' + typeName + ', got ' + valueName); } + if (that._schema[key].required && isEmptyValue(value)) { + throw new Error('Key ' + that.collectionName() + '.' + key + + ' is required' + ', but got ' + value); + } + if (that._schema[key].match && isString(value) && !that._schema[key].match.test(value)) { throw new Error('Value assigned to ' + that.collectionName() + '.' + key + ' does not match the regex/string ' + that._schema[key].match.toString() + '. Value was ' + value); diff --git a/lib/validate.js b/lib/validate.js index 7ceb48e..1cd6a6e 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -122,6 +122,11 @@ var isInChoices = function(choices, choice) { return choices.indexOf(choice) > -1; }; +var isEmptyValue = function(value) { + return typeof value === 'undefined' || (!(typeof value === 'number' || value instanceof Date || typeof value === 'boolean') + && (0 === Object.keys(value).length)); +}; + exports.isString = isString; exports.isNumber = isNumber; exports.isBoolean = isBoolean; @@ -136,4 +141,5 @@ exports.isNativeId = isNativeId; exports.isSupportedType = isSupportedType; exports.isType = isType; exports.isValidType = isValidType; -exports.isInChoices = isInChoices; \ No newline at end of file +exports.isInChoices = isInChoices; +exports.isEmptyValue = isEmptyValue; \ No newline at end of file diff --git a/test/document.test.js b/test/document.test.js index 1c45c8f..ecfe2fc 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -1086,6 +1086,348 @@ describe('Document', function() { }); }); + describe('required', function() { + it('should accept empty value that is not reqired', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: String, + required: false + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + name: '' + }); + + person.save().then(function() { + validateId(person); + expect(person.name).to.be.equal(''); + }).then(done, done); + }); + + it('should accept value that is not undefined', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: String, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + name: 'Scott' + }); + + person.save().then(function() { + validateId(person); + expect(person.name).to.be.equal('Scott'); + }).then(done, done); + }); + + it('should accept an empty value if default is specified', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: String, + required: true, + default: 'Scott' + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create(); + + person.save().then(function() { + validateId(person); + expect(person.name).to.be.equal('Scott'); + }).then(done, done); + }); + + it('should accept boolean value', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.isSingle = { + type: Boolean, + required: true + }; + this.isMerried = { + type: Boolean, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + isMerried: true, + isSingle: false + }); + + person.save().then(function() { + validateId(person); + expect(person.isMerried).to.be.true; + expect(person.isSingle).to.be.false; + }).then(done, done); + }); + + it('should accept date value', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.birthDate = { + type: Date, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var myBirthDate = new Date(); + + var person = Person.create({ + birthDate: myBirthDate + }); + + person.save().then(function(savedPerson) { + validateId(person); + expect(savedPerson.birthDate).to.equal(myBirthDate); + }).then(done, done); + }); + + it('should accept any number value', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.age = { + type: Number, + required: true + }; + this.level = { + type: Number, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + age: 21, + level: 0 + }); + + person.save().then(function(savedPerson) { + validateId(person); + expect(savedPerson.age).to.equal(21); + expect(savedPerson.level).to.equal(0); + }).then(done, done); + }); + + it('should reject value that is undefined', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: String, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create(); + + person.save().then(function() { + fail(null, Error, 'Expected error, but got none.'); + }).catch(function(error) { + expectError(error); + }).then(done, done); + }); + + it('should reject value if specified default empty value', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: String, + required: true, + default: '' + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create(); + + person.save().then(function() { + fail(null, Error, 'Expected error, but got none.'); + }).catch(function(error) { + expectError(error); + }).then(done, done); + }); + + it('should reject value that is null', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: Object, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + name: null + }); + + person.save().then(function() { + fail(null, Error, 'Expected error, but got none.'); + }).catch(function(error) { + expectError(error); + }).then(done, done); + }); + + it('should reject value that is an empty array', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.names = { + type: Array, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + names: [] + }); + + person.save().then(function() { + fail(null, Error, 'Expected error, but got none.'); + }).catch(function(error) { + expectError(error); + }).then(done, done); + }); + + it('should reject value that is an empty string', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.name = { + type: String, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + name: '' + }); + + person.save().then(function() { + fail(null, Error, 'Expected error, but got none.'); + }).catch(function(error) { + expectError(error); + }).then(done, done); + }); + + it('should reject value that is an empty object', function(done) { + + class Person extends Document { + constructor() { + super(); + + this.names = { + type: Object, + required: true + }; + } + + static collectionName() { + return 'people'; + } + } + + var person = Person.create({ + names: {} + }); + + person.save().then(function() { + fail(null, Error, 'Expected error, but got none.'); + }).catch(function(error) { + expectError(error); + }).then(done, done); + }); + }); + describe('hooks', function() { it('should call all pre and post functions', function(done) {