discipline
Version:
Validates object properties using Pursuit queries.
143 lines (125 loc) • 4.15 kB
JavaScript
/* global require module */
;
var pursuit = require('pursuit');
var isArray = pursuit({ typeOf: 'array' });
var error = {
noMessageAttached: 'Validator check should have a message.'
};
/* mix two objects, used in reduce functions in some cases */
function mix(obj, input) {
return Object.keys(input || {}).reduce(function(acc, key){
if (acc[key]) {
if (isArray(acc[key])) {
if (isArray(input[key])) {
acc[key].concat(input[key]);
}
else {
acc[key].push(input[key]);
}
}
else {
if (isArray(input[key])) {
acc[key] = [acc[key]].concat(input[key]);
}
else {
acc[key] = [acc[key], input[key]];
}
}
}
else {
acc[key] = input[key];
}
return acc;
}, obj || {});
}
function report (input, schema) {
var result = {
valid: false,
issues: [],
date: (new Date())
};
function addIssue (key, message, type) {
result.issues.push({
type: type || 'error',
key: key,
value: input[key],
message: message
});
}
Object.keys(schema)
.filter(function(key) {
return ! schema[key].check(input[key]);
})
.forEach(function(key) {
if (schema[key].intermediate) {
schema[key].intermediate
.filter(function(item) {
return ! item.check(input[key]);
})
.forEach(function (current) {
addIssue(key, current.message, current.type);
})
;
}
else {
addIssue(key, schema[key].message, schema[key].type);
}
})
;
return result;
}
module.exports = function (schema) {
schema = schema || {};
// Compile a function that ensure that the input conform
// to the given schema.
var validate = pursuit(Object.keys(schema).reduce(function(a, b) {
if (isArray(schema[b])) {
// combine the sub checks into one check
a[b] = schema[b].reduce(function(a, b) {
// check if there are duplicates in the validation. To ensure
// situations where two checks would result in at least one
// of them always to be false.
Object.keys(b.check).forEach(function (key) {
if (a[key]) {
throw new Error(
'Check collision. Two checks within a property made a similar check'
);
}
});
return mix(a, b.check);
}, {});
}
else {
a[b] = schema[b].check;
}
return a;
}, {}));
// Compile the individual checks and ensure every check
// have a message attached.
Object.keys(schema).forEach(function(current) {
if (isArray(schema[current])) {
var result = {};
result.check = pursuit(schema[current].reduce(function(a, b) {
// we have already checked if there are dublicate checks
return mix(a, b.check);
}, {}));
result.intermediate = schema[current].map(function (current) {
if (! current.message) {
throw new Error(error.noMessageAttached);
}
current.check = pursuit(current.check);
return current;
});
schema[current] = result;
}
else {
if (! schema[current].message) {
throw new Error(error.noMessageAttached);
}
schema[current].check = pursuit(schema[current].check);
}
});
return function (input) {
return validate(input) ? { valid: true } : report(input, schema);
};
};