Permalink
Browse files

Merge pull request #2 from domharrington/master

Add documentation to README
  • Loading branch information...
2 parents da7986d + 3a7678e commit db8c9df8484cbaacefd0df4ef8d6afd12b259d4e @serby committed Aug 23, 2012
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
@@ -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
@@ -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');
}
Oops, something went wrong.

0 comments on commit db8c9df

Please sign in to comment.