Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation to README #2

Merged
merged 10 commits into from
Aug 23, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8
105 changes: 102 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 65 additions & 29 deletions lib/schemata.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 || {};

/**
Expand All @@ -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]
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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)
Expand All @@ -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))) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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') {
Expand All @@ -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];
Expand All @@ -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);
Expand Down Expand Up @@ -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');
}
Expand Down
Loading