Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Add documentation to README #2

Merged
merged 10 commits into from almost 2 years ago

3 participants

domharrington Don't Add Me To Your Organization a.k.a The Travis Bot Paul Serby
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

Don't Add Me To Your Organization a.k.a The Travis Bot

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

Don't Add Me To Your Organization a.k.a The Travis Bot

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

Paul Serby serby merged commit db8c9df into from
Paul Serby serby closed this
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.
4 .travis.yml
... ... @@ -0,0 +1,4 @@
  1 +language: node_js
  2 +node_js:
  3 + - 0.6
  4 + - 0.8
105 README.md
Source Rendered
@@ -80,22 +80,121 @@ var stripped = contactSchema.stripUnknownProperties({
80 80
81 81 ### Validate an object against the schema
82 82
  83 +Validation is easy in schemata, just call **validate()** on your schema passing in the object to validate:
  84 +
83 85 ```js
  86 +contactSchema.validate(objectToValidate, function(errors){
  87 + // errors
  88 +});
84 89 ```
85 90
86   -### Cast an object to the types defined in the schema
  91 +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):
87 92
88 93 ```js
  94 +name: {
  95 + name: 'Full Name',
  96 + validators: [validator1, validator2]
  97 +}
89 98 ```
90 99
91   -### Get friendly name for property
  100 +Validators are functions that have the following signature:
92 101
93 102 ```js
  103 +function(name, value, callback) {}
94 104 ```
95 105
96   -### Validate an object
  106 +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.
  107 +
  108 +A full validator example:
97 109
98 110 ```js
  111 +var required = function(name, value, callback) {
  112 + return callback(value ? undefined : name + ' is required');
  113 +};
  114 +
  115 +name: {
  116 + name: 'Full Name',
  117 + validators: [required]
  118 +}
  119 +```
  120 +
  121 +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.
  122 +
  123 +For a comprehensive set of validators including: email, integer, string length, required & UK postcode. Check out [piton-validity](https://github.com/serby/piton-validity).
  124 +
  125 +### Cast an object to the types defined in the schema
  126 +
  127 +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.
  128 +
  129 +```js
  130 +var schemata = require('schemata');
  131 +
  132 +var person = schemata({
  133 + name: {
  134 + type: String
  135 + },
  136 + age: {
  137 + type: Number
  138 + },
  139 + active: {
  140 + type: Boolean
  141 + },
  142 + birthday: {
  143 + type: Date
  144 + },
  145 + friends: {
  146 + type: Array
  147 + },
  148 + extraInfo: {
  149 + type: Object
  150 + }
  151 +});
  152 +
  153 +var objectToCast = {
  154 + name: 123456,
  155 + age: '83',
  156 + active: 'no',
  157 + birthday: '13 February 1991',
  158 + friends: '',
  159 + extraInfo: undefined
  160 +};
  161 +
  162 +var casted = person.cast(objectToCast);
  163 +// casted = {
  164 +// name: '123456',
  165 +// age: 83,
  166 +// active: false,
  167 +// birthday: Date('Wed Feb 13 1991 00:00:00 GMT+0000 (GMT)'),
  168 +// friends: [],
  169 +// extraInfo: {}
  170 +// }
  171 +```
  172 +
  173 +### Get friendly name for property
  174 +
  175 +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.
  176 +
  177 +Consider the following example:
  178 +
  179 +```js
  180 +var schemata = require('schemata');
  181 +
  182 +var address = schemata({
  183 + addressLine1: {},
  184 + addressLine2: {},
  185 + addressLine3: {
  186 + name: 'Town'
  187 + },
  188 + addressLine4: {
  189 + name: 'Region'
  190 + }
  191 +});
  192 +
  193 +console.log(address.propertyName('addressLine1'));
  194 +// Returns 'Address Line 1' because there is no name set
  195 +
  196 +console.log(address.propertyName('addressLine3'));
  197 +// Returns 'Town' because there is a name set
99 198 ```
100 199
101 200 ## Credits
94 lib/schemata.js
@@ -2,6 +2,13 @@ var async = require('async')
2 2 , stringUtils = require('piton-string-utils')
3 3 ;
4 4
  5 +/**
  6 + * Array helper to assist creation of nested array schemas
  7 + *
  8 + * @param {Object} schema schema to be used as array type
  9 + * @return {Object} array schema object
  10 + * @api public
  11 + */
5 12 function ArrayType(schema) {
6 13 return {
7 14 arraySchema: schema
@@ -10,7 +17,7 @@ function ArrayType(schema) {
10 17
11 18 module.exports = function(schema) {
12 19
13   - // Start with a blank scheme to reduce error checking
  20 + // Start with a blank schema to reduce error checking
14 21 schema = schema || {};
15 22
16 23 /**
@@ -20,9 +27,12 @@ module.exports = function(schema) {
20 27 *
21 28 * @param {Object} existingEntity Object to extend
22 29 * @return {Object} A blank object based on schema
  30 + * @api public
23 31 */
24 32 function makeDefault(existingEntity) {
25   - var newEntity = makeBlank();
  33 + var newEntity = makeBlank()
  34 + ;
  35 +
26 36 Object.keys(schema).forEach(function(key) {
27 37 var defaultValue = newEntity[key]
28 38 , value = schema[key]
@@ -53,23 +63,27 @@ module.exports = function(schema) {
53 63
54 64 /**
55 65 * Returns an object with properties defined in schema but with empty values.
  66 + *
56 67 * The empty value will depend on the type:
57   - * * array = []
58   - * * object = {}
59   - * * string = null
60   - * * boolean = null
61   - * * integer = null
62   - * * real = null
  68 + * - array = []
  69 + * - object = {}
  70 + * - string = null
  71 + * - boolean = null
  72 + * - integer = null
  73 + * - real = null
  74 + *
63 75 * @return {Object} A blank object based on schema
  76 + * @api public
64 77 */
65 78 function makeBlank() {
66   - var
67   - newEntity = {},
68   - value;
  79 + var newEntity = {}
  80 + , value
  81 + ;
69 82
70 83 Object.keys(schema).forEach(function(key) {
71 84 var value = null
72   - , type = schema[key].type;
  85 + , type = schema[key].type
  86 + ;
73 87
74 88 // If type is schema and has a makeBlank() then used that.
75 89 if (typeof type === 'object') {
@@ -97,6 +111,9 @@ module.exports = function(schema) {
97 111
98 112 /**
99 113 * Has a property got a tag
  114 + *
  115 + * @return {Boolean}
  116 + * @api private
100 117 */
101 118 function hasTag(schema, key, tag) {
102 119 return (tag === undefined) || ((schema[key].tag !== undefined)
@@ -106,13 +123,15 @@ module.exports = function(schema) {
106 123 /**
107 124 * Takes an object and strips out properties not in the schema. If a tag is given
108 125 * then only properties with that tag will remain.
  126 + *
109 127 * @param {Object} entityObject The object to strip
110 128 * @param {String} tag Property tag to strip down to. If undefined all properties in the schema will be returned.
111   - * @return {Object} The striped down object
  129 + * @return {Object} The stripped down object
  130 + * @api public
112 131 */
113 132 function stripUnknownProperties(entityObject, tag) {
114   - var
115   - newEntity = {};
  133 + var newEntity = {}
  134 + ;
116 135
117 136 Object.keys(entityObject).forEach(function(key) {
118 137 if ((typeof schema[key] !== 'undefined') && (hasTag(schema, key, tag))) {
@@ -148,6 +167,7 @@ module.exports = function(schema) {
148 167 *
149 168 * @param {String} type The type you would like to cast value to
150 169 * @param {Mixed} value The value to be cast
  170 + * @api public
151 171 */
152 172 function castProperty(type, value) {
153 173 if (type === undefined) {
@@ -171,7 +191,8 @@ module.exports = function(schema) {
171 191 }
172 192 return value.toString && value.toString();
173 193 case Object:
174   - return (value === '' || value === null || value === undefined) ? {} : value;
  194 + // typeof null === 'object', so the extra check is needed for null
  195 + return (typeof value !== 'object' || value === null) ? {} : value;
175 196 case Date:
176 197 return (value === '' || value === null || value === undefined) ? null : (value instanceof Date ? value : new Date(value));
177 198 case Array:
@@ -183,13 +204,15 @@ module.exports = function(schema) {
183 204 /**
184 205 * Casts all the properties in the given entityObject that are defined in the schema.
185 206 * If tag is provided then only properties that are in the schema and have the given tag will be cast.
  207 + *
186 208 * @param {Object} entityObject The entity to cast properties on
187 209 * @param {String} tag Which properties in the scheme to cast
188 210 * @return {Object} A new object with cast properties
  211 + * @api public
189 212 */
190 213 function cast(entityObject, tag) {
191   - var
192   - newEntity = {};
  214 + var newEntity = {}
  215 + ;
193 216
194 217 Object.keys(entityObject).forEach(function(key) {
195 218 // Copy all properties
@@ -211,11 +234,12 @@ module.exports = function(schema) {
211 234 * @param {Mixed} set Either the name or an array of names of the rules to validate entity against
212 235 * @param {String} tag The tag to validate against (optional)
213 236 * @param {Function} callback Called once validation is complete, passing an array either empty of containing errors
  237 + * @api public
214 238 */
215 239 function validate(entityObject, set, tag, callback) {
216   - var
217   - errors = {},
218   - processedSchema = schema;
  240 + var errors = {}
  241 + , processedSchema = schema
  242 + ;
219 243
220 244 // This is to be tolerant of no set and tag parameters
221 245 if (typeof set === 'function') {
@@ -226,7 +250,8 @@ module.exports = function(schema) {
226 250 callback = tag;
227 251 // This is to check if all parameters are present and reduce schema
228 252 } else if (typeof callback === 'function') {
229   - var reducedSchema = {};
  253 + var reducedSchema = {}
  254 + ;
230 255 Object.keys(schema).forEach(function(key) {
231 256 if(hasTag(schema, key, tag)) {
232 257 reducedSchema[key] = schema[key];
@@ -240,25 +265,29 @@ module.exports = function(schema) {
240 265
241 266 async.forEach(Object.keys(processedSchema), function(key, propertyCallback) {
242 267
243   - var property = processedSchema[key];
  268 + var property = processedSchema[key]
  269 + ;
244 270
245 271 async.series(
246 272 [ function(seriesCallback) {
247 273 if ((property.validators === undefined) || (property.validators[set] === undefined)) {
248 274 return seriesCallback();
249 275 }
250   - var validators = property.validators[set];
  276 + var validators = property.validators[set]
  277 + , propertyName = (property.name === undefined) ? stringUtils.decamelcase(key) : property.name
  278 + ;
251 279
252   - var propertyName = (property.name === undefined) ? stringUtils.decamelcase(key) : property.name;
253 280 async.forEach(validators, function(validator, validateCallback) {
254 281 if (errors[key]) {
255 282 validateCallback();
256 283 } else {
257   - validator.validate(propertyName, entityObject[key], validateCallback);
  284 + validator(propertyName, entityObject[key], validateCallback);
258 285 }
259 286 }, function(error) {
260   - if (error && error.message) {
261   - errors[key] = error.message;
  287 + if (error) {
  288 + // Backwards compatibility to allow Error objects to be
  289 + // returned from validators as well as just strings
  290 + errors[key] = error.message ? error.message : error;
262 291 }
263 292
264 293 seriesCallback(true);
@@ -288,7 +317,14 @@ module.exports = function(schema) {
288 317 });
289 318 }
290 319
291   - function propertyName (property) {
  320 + /**
  321 + * Returns the human readable name for a particular property.
  322 + *
  323 + * @param {String} property The property to get the name of
  324 + * @return {String} Either decamelcased property name, or name if set
  325 + * @api public
  326 + */
  327 + function propertyName(property) {
292 328 if (schema[property] === undefined) {
293 329 throw new RangeError('No property \'' + property + '\' in schema');
294 330 }
48 test/schemata.test.js
@@ -286,7 +286,7 @@ describe('schemata', function() {
286 286
287 287 it('converts object correctly', function() {
288 288 var schema = createArraySchema();
289   - [null, ''].forEach(function(value) {
  289 + [null, '', 'hello', [], undefined].forEach(function(value) {
290 290 Object.keys(schema.castProperty(Object, value)).should.have.lengthOf(0);
291 291 });
292 292 [{a:'b'}].forEach(function(value) {
@@ -355,7 +355,7 @@ describe('schemata', function() {
355 355 var schema = createContactSchema();
356 356
357 357 schema.schema.name.validators = {
358   - all: [validation.required]
  358 + all: [validation.required.validate]
359 359 };
360 360
361 361 schema.validate(schema.makeDefault({ name: '' }), 'all', function(errors) {
@@ -368,7 +368,7 @@ describe('schemata', function() {
368 368 var schema = createContactSchema();
369 369
370 370 schema.schema.name.validators = {
371   - all: [validation.required]
  371 + all: [validation.required.validate]
372 372 };
373 373
374 374 schema.validate(schema.makeDefault({ name: '' }), '', function(errors) {
@@ -381,11 +381,11 @@ describe('schemata', function() {
381 381 var schema = createContactSchema();
382 382
383 383 schema.schema.name.validators = {
384   - all: [validation.required]
  384 + all: [validation.required.validate]
385 385 };
386 386
387 387 schema.schema.age.validators = {
388   - all: [validation.required]
  388 + all: [validation.required.validate]
389 389 };
390 390
391 391 schema.validate(schema.makeDefault({ name: '', age: 33 }), 'all', function(errors) {
@@ -398,7 +398,7 @@ describe('schemata', function() {
398 398 var schema = createContactSchema();
399 399
400 400 schema.schema.name.validators = {
401   - all: [validation.required, validation.length(2, 4)]
  401 + all: [validation.required.validate, validation.length(2, 4).validate]
402 402 };
403 403
404 404 schema.validate(schema.makeDefault({ name: 'A' }), 'all', function(errors) {
@@ -412,12 +412,12 @@ describe('schemata', function() {
412 412
413 413 // Adding required validation to a schema property with a tag
414 414 schema.schema.name.validators = {
415   - all: [validation.required]
  415 + all: [validation.required.validate]
416 416 };
417 417
418 418 // Adding required validation to a schema property without a tag
419 419 schema.schema.age.validators = {
420   - all: [validation.required]
  420 + all: [validation.required.validate]
421 421 };
422 422
423 423 schema.validate(schema.makeBlank(), 'all', 'update', function(errors) {
@@ -432,13 +432,13 @@ describe('schemata', function() {
432 432 var schema = createContactSchema();
433 433
434 434 schema.schema.name.validators = {
435   - all: [validation.required]
  435 + all: [validation.required.validate]
436 436 };
437 437
438 438 schema.schema.name.tag = ['newTag'];
439 439
440 440 schema.schema.age.validators = {
441   - all: [validation.required]
  441 + all: [validation.required.validate]
442 442 };
443 443
444 444 schema.schema.age.tag = ['differentTag'];
@@ -456,11 +456,11 @@ describe('schemata', function() {
456 456 var schema = createContactSchema();
457 457
458 458 schema.schema.name.validators = {
459   - all: [validation.required]
  459 + all: [validation.required.validate]
460 460 };
461 461
462 462 schema.schema.age.validators = {
463   - all: [validation.required]
  463 + all: [validation.required.validate]
464 464 };
465 465
466 466 schema.validate(schema.makeBlank(), function(errors) {
@@ -475,7 +475,7 @@ describe('schemata', function() {
475 475 it('Validates sub-schemas', function() {
476 476 var schema = createBlogSchema();
477 477 schema.schema.author.type.schema.age.validators = {
478   - all: [validation.required]
  478 + all: [validation.required.validate]
479 479 };
480 480 schema.validate(schema.makeBlank(), function(errors) {
481 481 errors.should.eql({ author: {
@@ -496,7 +496,7 @@ describe('schemata', function() {
496 496 schema.schema.author.validators = {
497 497 all: [createPropertyValidator(function(value, callback) {
498 498 callback(false);
499   - }, 'Bad')]
  499 + }, 'Bad').validate]
500 500 };
501 501 schema.validate(schema.makeBlank(), function(errors) {
502 502 errors.should.eql({ author: 'Bad' });
@@ -509,12 +509,12 @@ describe('schemata', function() {
509 509 all: [createPropertyValidator(function(value, callback) {
510 510 'This should not get called'.should.equal(false);
511 511 callback(false);
512   - }, 'This should not be seen')]
  512 + }, 'This should not be seen').validate]
513 513 };
514 514 schema.schema.author.validators = {
515 515 all: [createPropertyValidator(function(value, callback) {
516 516 callback(false);
517   - }, 'From one of the Validators')]
  517 + }, 'From one of the Validators').validate]
518 518 };
519 519 schema.validate(schema.makeBlank(), function(errors) {
520 520 errors.should.eql({ author: 'From one of the Validators' });
@@ -525,6 +525,22 @@ describe('schemata', function() {
525 525 //TODO:
526 526 });
527 527
  528 + it('allows error response to be a string instead of Error object', function(done) {
  529 + var schema = createContactSchema()
  530 + ;
  531 +
  532 + schema.schema.name.validators = {
  533 + all: [function(name, value, callback) {
  534 + return callback(value ? undefined : name + ' is required');
  535 + }]
  536 + };
  537 +
  538 + schema.validate(schema.makeDefault({ name: '' }), function(errors) {
  539 + errors.should.eql({name:'Full Name is required'});
  540 + done();
  541 + });
  542 + });
  543 +
528 544 });
529 545
530 546 describe('#propertyName()', function() {

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.