Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add documentation to README #2

Merged
merged 10 commits into from

3 participants

@domharrington

Tidy up a few var declarations in schemata.js
Ensure the doc comments in schemata.js are consistent and added @api comment
Change it so that a validator is just a function, not an object with a 'validate' property
Change so that validators don't have to return JS Error objects, they can now just return strings
Change how Object casting happens, now always returns a blank object if the type isn't object

@travisbot

This pull request fails (merged 35e4e9d into da7986d).

@travisbot

This pull request passes (merged 3a7678e into da7986d).

@serby serby merged commit db8c9df into serby:master

1 check passed

Details default The Travis build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 203 additions and 48 deletions.
  1. +4 −0 .travis.yml
  2. +102 −3 README.md
  3. +65 −29 lib/schemata.js
  4. +32 −16 test/schemata.test.js
View
4 .travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+ - 0.6
+ - 0.8
View
105 README.md
@@ -80,22 +80,121 @@ var stripped = contactSchema.stripUnknownProperties({
### Validate an object against the schema
+Validation is easy in schemata, just call **validate()** on your schema passing in the object to validate:
+
```js
+contactSchema.validate(objectToValidate, function(errors){
+ // errors
+});
```
-### Cast an object to the types defined in the schema
+Validators are assigned to a field of the schema by adding them as an array to the **validators** property of the object as follows (this is an extension of the example at the top):
```js
+name: {
+ name: 'Full Name',
+ validators: [validator1, validator2]
+}
```
-### Get friendly name for property
+Validators are functions that have the following signature:
```js
+function(name, value, callback) {}
```
-### Validate an object
+The callback must be called with a falsy value (such as undefined or null) if the validation passes, or with a string with the appropriate error message if it fails validation.
+
+A full validator example:
```js
+var required = function(name, value, callback) {
+ return callback(value ? undefined : name + ' is required');
+};
+
+name: {
+ name: 'Full Name',
+ validators: [required]
+}
+```
+
+If any of the validators fail then the errors will be returned in the callback from **validate()** with the object key being the field name and the value being the error message.
+
+For a comprehensive set of validators including: email, integer, string length, required & UK postcode. Check out [piton-validity](https://github.com/serby/piton-validity).
+
+### Cast an object to the types defined in the schema
+
+Type casting is done in schemata using the **cast()** and **castProperty()** functions. **cast()** is used for when you want to cast multiple properties against a schama, **castProperty()** is used if you want to cast one property and explicitly provide the type.
+
+```js
+var schemata = require('schemata');
+
+var person = schemata({
+ name: {
+ type: String
+ },
+ age: {
+ type: Number
+ },
+ active: {
+ type: Boolean
+ },
+ birthday: {
+ type: Date
+ },
+ friends: {
+ type: Array
+ },
+ extraInfo: {
+ type: Object
+ }
+});
+
+var objectToCast = {
+ name: 123456,
+ age: '83',
+ active: 'no',
+ birthday: '13 February 1991',
+ friends: '',
+ extraInfo: undefined
+};
+
+var casted = person.cast(objectToCast);
+// casted = {
+// name: '123456',
+// age: 83,
+// active: false,
+// birthday: Date('Wed Feb 13 1991 00:00:00 GMT+0000 (GMT)'),
+// friends: [],
+// extraInfo: {}
+// }
+```
+
+### Get friendly name for property
+
+If you want to output the name of a schema property in a human-readable format then you need the **propertyName()** function. If your schema field has a name attribute, then that is returned. If that is not set then the name is obtained by decamelcasing the field name.
+
+Consider the following example:
+
+```js
+var schemata = require('schemata');
+
+var address = schemata({
+ addressLine1: {},
+ addressLine2: {},
+ addressLine3: {
+ name: 'Town'
+ },
+ addressLine4: {
+ name: 'Region'
+ }
+});
+
+console.log(address.propertyName('addressLine1'));
+// Returns 'Address Line 1' because there is no name set
+
+console.log(address.propertyName('addressLine3'));
+// Returns 'Town' because there is a name set
```
## Credits
View
94 lib/schemata.js
@@ -2,6 +2,13 @@ var async = require('async')
, stringUtils = require('piton-string-utils')
;
+/**
+ * Array helper to assist creation of nested array schemas
+ *
+ * @param {Object} schema schema to be used as array type
+ * @return {Object} array schema object
+ * @api public
+ */
function ArrayType(schema) {
return {
arraySchema: schema
@@ -10,7 +17,7 @@ function ArrayType(schema) {
module.exports = function(schema) {
- // Start with a blank scheme to reduce error checking
+ // Start with a blank schema to reduce error checking
schema = schema || {};
/**
@@ -20,9 +27,12 @@ module.exports = function(schema) {
*
* @param {Object} existingEntity Object to extend
* @return {Object} A blank object based on schema
+ * @api public
*/
function makeDefault(existingEntity) {
- var newEntity = makeBlank();
+ var newEntity = makeBlank()
+ ;
+
Object.keys(schema).forEach(function(key) {
var defaultValue = newEntity[key]
, value = schema[key]
@@ -53,23 +63,27 @@ module.exports = function(schema) {
/**
* Returns an object with properties defined in schema but with empty values.
+ *
* The empty value will depend on the type:
- * * array = []
- * * object = {}
- * * string = null
- * * boolean = null
- * * integer = null
- * * real = null
+ * - array = []
+ * - object = {}
+ * - string = null
+ * - boolean = null
+ * - integer = null
+ * - real = null
+ *
* @return {Object} A blank object based on schema
+ * @api public
*/
function makeBlank() {
- var
- newEntity = {},
- value;
+ var newEntity = {}
+ , value
+ ;
Object.keys(schema).forEach(function(key) {
var value = null
- , type = schema[key].type;
+ , type = schema[key].type
+ ;
// If type is schema and has a makeBlank() then used that.
if (typeof type === 'object') {
@@ -97,6 +111,9 @@ module.exports = function(schema) {
/**
* Has a property got a tag
+ *
+ * @return {Boolean}
+ * @api private
*/
function hasTag(schema, key, tag) {
return (tag === undefined) || ((schema[key].tag !== undefined)
@@ -106,13 +123,15 @@ module.exports = function(schema) {
/**
* Takes an object and strips out properties not in the schema. If a tag is given
* then only properties with that tag will remain.
+ *
* @param {Object} entityObject The object to strip
* @param {String} tag Property tag to strip down to. If undefined all properties in the schema will be returned.
- * @return {Object} The striped down object
+ * @return {Object} The stripped down object
+ * @api public
*/
function stripUnknownProperties(entityObject, tag) {
- var
- newEntity = {};
+ var newEntity = {}
+ ;
Object.keys(entityObject).forEach(function(key) {
if ((typeof schema[key] !== 'undefined') && (hasTag(schema, key, tag))) {
@@ -148,6 +167,7 @@ module.exports = function(schema) {
*
* @param {String} type The type you would like to cast value to
* @param {Mixed} value The value to be cast
+ * @api public
*/
function castProperty(type, value) {
if (type === undefined) {
@@ -171,7 +191,8 @@ module.exports = function(schema) {
}
return value.toString && value.toString();
case Object:
- return (value === '' || value === null || value === undefined) ? {} : value;
+ // typeof null === 'object', so the extra check is needed for null
+ return (typeof value !== 'object' || value === null) ? {} : value;
case Date:
return (value === '' || value === null || value === undefined) ? null : (value instanceof Date ? value : new Date(value));
case Array:
@@ -183,13 +204,15 @@ module.exports = function(schema) {
/**
* Casts all the properties in the given entityObject that are defined in the schema.
* If tag is provided then only properties that are in the schema and have the given tag will be cast.
+ *
* @param {Object} entityObject The entity to cast properties on
* @param {String} tag Which properties in the scheme to cast
* @return {Object} A new object with cast properties
+ * @api public
*/
function cast(entityObject, tag) {
- var
- newEntity = {};
+ var newEntity = {}
+ ;
Object.keys(entityObject).forEach(function(key) {
// Copy all properties
@@ -211,11 +234,12 @@ module.exports = function(schema) {
* @param {Mixed} set Either the name or an array of names of the rules to validate entity against
* @param {String} tag The tag to validate against (optional)
* @param {Function} callback Called once validation is complete, passing an array either empty of containing errors
+ * @api public
*/
function validate(entityObject, set, tag, callback) {
- var
- errors = {},
- processedSchema = schema;
+ var errors = {}
+ , processedSchema = schema
+ ;
// This is to be tolerant of no set and tag parameters
if (typeof set === 'function') {
@@ -226,7 +250,8 @@ module.exports = function(schema) {
callback = tag;
// This is to check if all parameters are present and reduce schema
} else if (typeof callback === 'function') {
- var reducedSchema = {};
+ var reducedSchema = {}
+ ;
Object.keys(schema).forEach(function(key) {
if(hasTag(schema, key, tag)) {
reducedSchema[key] = schema[key];
@@ -240,25 +265,29 @@ module.exports = function(schema) {
async.forEach(Object.keys(processedSchema), function(key, propertyCallback) {
- var property = processedSchema[key];
+ var property = processedSchema[key]
+ ;
async.series(
[ function(seriesCallback) {
if ((property.validators === undefined) || (property.validators[set] === undefined)) {
return seriesCallback();
}
- var validators = property.validators[set];
+ var validators = property.validators[set]
+ , propertyName = (property.name === undefined) ? stringUtils.decamelcase(key) : property.name
+ ;
- var propertyName = (property.name === undefined) ? stringUtils.decamelcase(key) : property.name;
async.forEach(validators, function(validator, validateCallback) {
if (errors[key]) {
validateCallback();
} else {
- validator.validate(propertyName, entityObject[key], validateCallback);
+ validator(propertyName, entityObject[key], validateCallback);
}
}, function(error) {
- if (error && error.message) {
- errors[key] = error.message;
+ if (error) {
+ // Backwards compatibility to allow Error objects to be
+ // returned from validators as well as just strings
+ errors[key] = error.message ? error.message : error;
}
seriesCallback(true);
@@ -288,7 +317,14 @@ module.exports = function(schema) {
});
}
- function propertyName (property) {
+ /**
+ * Returns the human readable name for a particular property.
+ *
+ * @param {String} property The property to get the name of
+ * @return {String} Either decamelcased property name, or name if set
+ * @api public
+ */
+ function propertyName(property) {
if (schema[property] === undefined) {
throw new RangeError('No property \'' + property + '\' in schema');
}
View
48 test/schemata.test.js
@@ -286,7 +286,7 @@ describe('schemata', function() {
it('converts object correctly', function() {
var schema = createArraySchema();
- [null, ''].forEach(function(value) {
+ [null, '', 'hello', [], undefined].forEach(function(value) {
Object.keys(schema.castProperty(Object, value)).should.have.lengthOf(0);
});
[{a:'b'}].forEach(function(value) {
@@ -355,7 +355,7 @@ describe('schemata', function() {
var schema = createContactSchema();
schema.schema.name.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.validate(schema.makeDefault({ name: '' }), 'all', function(errors) {
@@ -368,7 +368,7 @@ describe('schemata', function() {
var schema = createContactSchema();
schema.schema.name.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.validate(schema.makeDefault({ name: '' }), '', function(errors) {
@@ -381,11 +381,11 @@ describe('schemata', function() {
var schema = createContactSchema();
schema.schema.name.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.schema.age.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.validate(schema.makeDefault({ name: '', age: 33 }), 'all', function(errors) {
@@ -398,7 +398,7 @@ describe('schemata', function() {
var schema = createContactSchema();
schema.schema.name.validators = {
- all: [validation.required, validation.length(2, 4)]
+ all: [validation.required.validate, validation.length(2, 4).validate]
};
schema.validate(schema.makeDefault({ name: 'A' }), 'all', function(errors) {
@@ -412,12 +412,12 @@ describe('schemata', function() {
// Adding required validation to a schema property with a tag
schema.schema.name.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
// Adding required validation to a schema property without a tag
schema.schema.age.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.validate(schema.makeBlank(), 'all', 'update', function(errors) {
@@ -432,13 +432,13 @@ describe('schemata', function() {
var schema = createContactSchema();
schema.schema.name.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.schema.name.tag = ['newTag'];
schema.schema.age.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.schema.age.tag = ['differentTag'];
@@ -456,11 +456,11 @@ describe('schemata', function() {
var schema = createContactSchema();
schema.schema.name.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.schema.age.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.validate(schema.makeBlank(), function(errors) {
@@ -475,7 +475,7 @@ describe('schemata', function() {
it('Validates sub-schemas', function() {
var schema = createBlogSchema();
schema.schema.author.type.schema.age.validators = {
- all: [validation.required]
+ all: [validation.required.validate]
};
schema.validate(schema.makeBlank(), function(errors) {
errors.should.eql({ author: {
@@ -496,7 +496,7 @@ describe('schemata', function() {
schema.schema.author.validators = {
all: [createPropertyValidator(function(value, callback) {
callback(false);
- }, 'Bad')]
+ }, 'Bad').validate]
};
schema.validate(schema.makeBlank(), function(errors) {
errors.should.eql({ author: 'Bad' });
@@ -509,12 +509,12 @@ describe('schemata', function() {
all: [createPropertyValidator(function(value, callback) {
'This should not get called'.should.equal(false);
callback(false);
- }, 'This should not be seen')]
+ }, 'This should not be seen').validate]
};
schema.schema.author.validators = {
all: [createPropertyValidator(function(value, callback) {
callback(false);
- }, 'From one of the Validators')]
+ }, 'From one of the Validators').validate]
};
schema.validate(schema.makeBlank(), function(errors) {
errors.should.eql({ author: 'From one of the Validators' });
@@ -525,6 +525,22 @@ describe('schemata', function() {
//TODO:
});
+ it('allows error response to be a string instead of Error object', function(done) {
+ var schema = createContactSchema()
+ ;
+
+ schema.schema.name.validators = {
+ all: [function(name, value, callback) {
+ return callback(value ? undefined : name + ' is required');
+ }]
+ };
+
+ schema.validate(schema.makeDefault({ name: '' }), function(errors) {
+ errors.should.eql({name:'Full Name is required'});
+ done();
+ });
+ });
+
});
describe('#propertyName()', function() {
Something went wrong with that request. Please try again.