pharmacy
Version:
Browser and Node.js library for creating validators and sanitizers
179 lines (151 loc) • 4.82 kB
JavaScript
const Pharmacy = require('./pharmacy.js');
module.exports = Field;
/**
* @typedef {object} FieldOptions
* @property {string[]} path Field path value.
* @property {*} value Value to validate.
* @property {Store} store Pharmacy store object.
* @property {Recipe} recipe Pharmacy recipe instance.
*/
/**
* Field is an object which validate value with recipes from store.
*
* @param {FieldOptions} options Field options object.
* @constructor
*/
function Field(options) {
if (typeof options !== 'object') {
throw new Error('Options should be an object');
}
if (options.path) {
this.path = options.path.slice();
} else {
this.path = [];
}
this.value = options.value;
this.store = options.store;
this.recipe = options.recipe;
this.useRecipe(options.recipe);
}
/**
* Apply recipe to field object.
*
* @param {Recipe} recipe Recipe object with validation rules set.
*/
Field.prototype.useRecipe = function (recipe) {
Object.getOwnPropertyNames(recipe.rules).forEach((name) => {
var rule = this.store.findRule(name);
if (rule && rule.hasOwnProperty('field')) {
rule.field(this);
}
});
};
/**
* Create child field if current value is complex.
*
* @param {string|string[]} path Child field path.
* @param {*} value Child field value.
* @param {string|Recipe} recipe Child field recipe.
* @return {Field} New field instance.
*/
Field.prototype.child = function (path, value, recipe) {
recipe = Pharmacy.Recipe.to(recipe);
var field = new this.constructor({
path: this.path.concat(path),
value: value,
store: this.store,
recipe: recipe
});
return field;
};
/**
* Validate field value.
*
* @return {Promise} Promise with report as result.
*/
Field.prototype.validate = function () {
if (this.report) {
return Promise.resolve(this.report);
}
const report = this.report = new Pharmacy.Report();
const rules = this.recipe.getRules();
let value = this.value;
let accept;
let check;
return new Promise((resolve, reject) => {
const next = () => {
if (! report.isValid()) {
resolve(report);
return;
}
while (rules.length) {
check = rules.shift();
let rule;
if (check.accept instanceof Pharmacy.Rule) {
rule = check.accept;
} else {
rule = this.store.findRule(check.name);
if (! rule) {
continue;
}
}
accept = check.accept;
value = rule.filter(check.accept, value, this);
report.value = value;
let result;
try {
result = rule.validate(check.accept, value, this);
} catch (err) {
reject(err);
return;
}
if (result instanceof Promise === false) {
result = Promise.resolve(result);
}
result.then(update).then(next).catch(reject);
return;
}
resolve(report);
};
const update = (result) => {
if (result === true) {
report.checks++;
return;
}
if (result instanceof Pharmacy.Report) {
result.getIssues().forEach((issue) => {
report.issues.push({
path: this.path.concat(issue.path),
rule: issue.rule,
value: issue.value,
accept: issue.accept,
current: issue.current
});
});
value = report.value = result.value;
report.checks += result.checks;
} else if (Array.isArray(result)) {
result.forEach(update);
} else if (result && typeof result === 'object') {
value = result.value;
report.addIssue({
path: this.path.concat(result.path || []),
rule: result.rule || check.name,
value: result.value,
accept: result.accept,
current: result.current
});
} else {
report.addIssue({
path: this.path.slice(),
rule: check.name,
value: value,
accept: accept,
current: false
});
report.checks++;
}
};
next();
});
};