src/kindergarten/governesses/HeadGoverness.js
import {
each,
filter,
forIn,
isFunction,
isString,
isEmpty
} from 'lodash';
import Rule from '../Rule';
import BaseObject from '../BaseObject';
import isRule from '../utils/isRule';
import {
AccessDenied,
ArgumentError
} from '../errors';
export default class HeadGoverness extends BaseObject {
/**
* Creates a new instance of the HeadGoverness
*/
constructor() {
super();
this.rules = [];
}
/**
* Throws an error if child is not allowed to do some action
*/
guard(action, ...args) {
const target = args[0];
if (this.isAllowed(action, ...args)) {
return target;
}
throw new AccessDenied(
`Child is not allowed to ${action} ${isString(target) ? target : 'the target'}.`
);
}
/**
* Watch over some child action. By default we only execute it, but custom
* governesses can override it to do some custom stuff like calling `guard()`
* or something else (see. `StrictGoverness` class).
*/
governed(callback, args = [], callingContext = null) {
return callback.apply(callingContext, args);
}
/**
* Returns true if child is allowed to perform some action
*/
isAllowed(action, ...args) {
if (this.isGuarded()) {
const allowRules = [];
const strictDisallowRules = [];
each(this.getRules(action), (rule) => {
const verificationResult = rule.verify(...args);
if (isRule(rule)) {
// Is there any rule explicitly allowing the child to do that?
if (rule.type.isPositive && verificationResult) {
allowRules.push(rule);
}
// Is there any rule strictly disallowing the child to do that?
if (!verificationResult && rule.definition.isStrict) {
strictDisallowRules.push(rule);
}
}
});
if (isEmpty(allowRules) || !isEmpty(strictDisallowRules)) {
return false;
}
}
return true;
}
/**
* Returns false if child is allowed to perform some action
*/
isNotAllowed(...args) {
return !this.isAllowed(...args);
}
/**
* The getter of unguarded property. If governess is ungarded, then no errors will be
* thrown when guard() method is called.
*/
get unguarded() {
return !!this._unguarded;
}
/**
* The setter of unguarded property. If governess is ungarded, then no errors will be
* thrown when guard() method is called.
*/
set unguarded(value) {
this._unguarded = !!value;
return value;
}
getRules(type) {
return type ?
filter(this.rules, (rule) => rule.type.type === type) :
this.rules;
}
learnRules(perimeter) {
const governObj = perimeter.govern || {};
let keys = 0;
forIn(governObj, (val, key) => {
if (governObj.hasOwnProperty(key)) {
keys++;
const ruleDef = governObj[key];
// function rules must be called in context of perimeter to have access
// to `this.child`
if (isFunction(ruleDef)) {
ruleDef.ruleContext = ruleDef.ruleContext || perimeter;
}
this.addRule(new Rule(
key, ruleDef
));
}
});
return keys;
}
addRule(...rules) {
let counter = 0;
each(rules, (rule) => {
if (!isRule(rule)) {
throw new ArgumentError(
'Governess cannot learn the rule. Does it inherit from Rule class?'
);
}
++counter;
this.rules.push(rule);
});
return counter;
}
/**
* The governess is empty when no rules have been defined
*/
hasAnyRules() {
return !isEmpty(this.rules);
}
/**
* Perform some stuff unguarded
*/
doUnguarded(callback, context) {
let returnValue;
context = context || null;
if (isFunction(callback)) {
const before = this.unguarded;
this.unguarded = true;
returnValue = callback.apply(context);
this.unguarded = before;
}
return returnValue;
}
isUnguarded() {
return this.unguarded;
}
isGuarded() {
return !this.isUnguarded();
}
}