/*jslint plusplus: true, devel: true, nomen: true, vars: true, node: true, sloppy: true, indent: 4, maxerr: 50 */ "use strict"; var sift = require("sift"), Strings = require("./strings"); function execCond(cond, fact, man, cb) { switch (typeof cond) { case "function": cond(fact, cb); break; case "string": man.getRule(cond, function (err, rule) { if (err) { return cb(err); } execRule(rule, fact, man, cb); }); break; default: try { cb(null, sift(cond).test(fact)); } catch (err) { cb(err); } break; } } function execRule(rl, fact, man, cb) { var cond = rl.condition; if (Array.isArray(cond)) { var completed = 0, iterate = function () { execCond(cond[completed++], fact, man, function (err, res) { if (err || !res || completed >= cond.length) { cb(err, res); } else { iterate(); } }); }; iterate(); } else { execCond(cond, fact, man, cb); } } /** * Constructor for Authority Manager. * * @class Represents an Authority Manager. * @param {(object|object[])=} ruleStores - An object or an array of objects capable of storing rules. * If ommited, {@link MemoryStore} is used. * @param {object=} opts - Additional options. */ function Manager(ruleStores, opts) { var that = this; this.stores = {}; this.opts = opts || {}; if (!ruleStores) { ruleStores = [new (require("./stores/memory"))()]; } else if (!Array.isArray(ruleStores)) { ruleStores = [ruleStores]; } if (!this.opts.defaultStore) { this.opts.defaultStore = ruleStores[0].name; } ruleStores.forEach(function (el) { that.stores[el.name] = el; }); this._stores = ruleStores; } /** * Adds one or more rules to the default store. * * This method is simply alias to the `setRules` method of the default store. * If default store hasn't been set explicitly, the first store in the collection is assumed. * * @param {(object|object[])} rules - The rule or an array of rules to be added to the store. * * @param {boolean=} override - Specifies whether existing rules should be replaced by the ones provided to this method. Rules are matched by their name. * If this parameter is not specified or is false and there is a name conflict an error will be returned or thrown depending on the usage of the method. * * @param {function=} done - Optional callback function that will be called after the rule(s) have been added. * If the method is unsuccessful an error object will be passed as a single parameter to the callback. * If the callback is omitted and the method is unsuccessful an error will be thrown. * * @return {null} */ Manager.prototype.setRules = function (rules, override, done) { this.stores[this.opts.defaultStore].setRules(rules, override, done); }; /** * Retrieves the specified rule from the first store that matches the name. * * @param {string} name - The name of the rule. * @param {function} done - The callback that handles the result. * The first parameter will be an error object if an error occurred, or null otherwise. * The second parameter will contain the requested rule or null if not found. * @return {null} */ Manager.prototype.getRule = function (name, done) { var getters = this._stores, completed = 0, iterate = function () { getters[completed++].getRule(name, function (err, rule) { if (err || rule || completed >= getters.length) { done(err, rule); } else { iterate(); } }); }; iterate(); }; /** * Executes the specified or provided rule. * * @param {(string|object)} rule - The name of a rule or an instance of a rule. * @param {object} fact - An object containing facts to which the current rule is matched against. * @param {function} done - Callback function that will be called after the rule has been evaluated. * The first parameter will be an error object if an error occurred, or null otherwise. * The second parameter will contain `true` or `false` indicating whether all conditions against the provided fact have been satisfied, including all conditions from all nested rules if any. * @return {null} */ Manager.prototype.execute = function (rule, fact, done) { if (!done) { throw new Error(Strings.ERR_REQUIRED_CALLBACK); } if (!rule || !fact) { return done(new Error(Strings.ERR_REQ_RULE_FACT)); } var that = this; if (typeof rule === "string") { return this.getRule(rule, function (err, rl) { if (err) { return done(err); } execRule(rl, fact, that, done); }); } execRule(rule, fact, that, done); }; module.exports = Manager;