src/kindergarten/Sandbox.js
import {
each,
find,
some,
isEmpty
} from 'lodash';
import HeadGoverness from './governesses/HeadGoverness';
import Purpose from './Purpose';
import BaseObject from './BaseObject';
import AllowedMethodsService from './utils/AllowedMethodsService';
import {
isGoverness,
isPerimeter,
isPurpose
} from './utils';
import {
ArgumentError,
NoGovernessError,
RestrictedMethodError
} from './errors';
/**
* The definition of Sandbox class.
* The sandbox is place where children can play governed by a governess.
*/
export default class Sandbox extends BaseObject {
/**
* Create a new empty sandbox.
*/
constructor(child = null, { governess = new HeadGoverness(child), perimeters = [] } = {}) {
super(); // init publish/subscribe
this.child = child;
if (!isGoverness(governess)) {
try {
const Governess = governess;
governess = new Governess();
} catch (ignore) {
// ignore...
}
}
this.governess = governess;
this._perimeters = [];
if (!isEmpty(perimeters)) {
this.loadPerimeter(...perimeters);
}
}
/**
* The getter of the governess.
*/
get governess() {
return this._governess;
}
/**
* The setter of the governess.
* Make sure new governess learn all the rules when governess is set.
*/
set governess(value) {
if (!isGoverness(value)) {
throw new NoGovernessError();
}
// if governess is null perimeter will use the governess of it's sandbox
this._governess = value;
// New governess must know all the rules (if any)
this._learnRules();
return value;
}
guard(...args) {
return this.governess.guard(...args);
}
/**
* Load perimeters.
* Returns the count of addded perimeters.
*/
loadPerimeter(...perimeters) {
let counter = 0;
each(perimeters, (perimeter) => {
// Sandbox only accepts perimeters
if (!isPerimeter(perimeter)) {
throw new ArgumentError(
'Module must be instance of Kindergarten.Perimeter.'
);
}
// Skip if sandbox already have the perimeter
if (this.hasPerimeter(perimeter)) return;
// If perimeter has a governess, then she has to learn the rules as well
if (isGoverness(perimeter.governess)) {
perimeter.governess.learnRules(perimeter);
}
// The governess of a sandbox must know all the rules
this.governess.learnRules(perimeter);
perimeter.sandbox = this;
this._perimeters.push(perimeter);
// Make sure the purpose is available on Sandbox
this._extendPurpose(perimeter);
++counter;
this.trigger('load-perimeter', this, perimeter);
});
return counter;
}
/**
* Alias for loadPerimeter
*/
loadModule(...args) {
return this.loadPerimeter(...args);
}
/**
* Return all loaded perimeters
*/
getPerimeters() {
return this._perimeters || [];
}
/**
* Return perimeter by a purpose or null.
*/
getPerimeter(purpose) {
const perimeter = find(this.getPerimeters(), (p) => (p.purpose === purpose));
return isPerimeter(perimeter) ? perimeter : null;
}
/**
* Return true if sandbox already contains a perimeter.
*/
hasPerimeter(perimeter) {
const purpose = isPerimeter(perimeter) ?
perimeter.purpose :
perimeter;
return some(this.getPerimeters(), (p) => p.purpose === purpose);
}
/**
* Return true if allowed to do action on target.
*/
isAllowed(...args) {
return this.governess.isAllowed(...args);
}
/**
* Return true if not allowed to do action on target.
*/
isNotAllowed(...args) {
return this.governess.isNotAllowed(...args);
}
/**
* Expose the purpose of a perimeter, make sure the purpose of the perimeter
* is available on this sandbox.
* This method is used internally by Sandbox and shouldn't be used
* externally.
*/
_extendPurpose(perimeter) {
const name = perimeter.purpose;
const allowedMethodsService = new AllowedMethodsService(this);
if (allowedMethodsService.isRestricted(name)) {
throw new RestrictedMethodError(
`Cannot expose purpose ${name} to sandbox. Restricted method name.`
);
}
this[name] = isPurpose(this[name]) ? this[name] : new Purpose(name, this);
this[name]._loadPerimeter(perimeter);
}
/**
* Make sure governess know all the rules from all loaded perimeters.
* This method is used internally by Sandbox and shouldn't be used
* externally.
*/
_learnRules() {
each(this.getPerimeters() || [], (perimeter) =>
this.governess.learnRules(perimeter)
);
}
}