const {checkType} = require("./Utility");
/**
* A filter that detects if an event should be allowed or denied based on some criteria.
*
* @property {boolean} allow - `true` if this EventFilter is intended to allow an event
* @property {boolean} deny - `true` if this EventFilter is intended to deny an event
* @property {number} priority - The relative priority of this filter. Used when part of a {@link EventListener} `filterList`.
* @property {object} criteria - The original criteria object passed to the constructor
*/
class EventFilter {
/**
* Creates a new filter for an event. The filter is a simple detecter for a single set of criteria, but can be chained
* together into a firewall-like set of policies as a {@link EventListener} `filterList`.
*
* @param {"allow"|"deny"} type - Whether this filter is allowing or denying a specific event.
* @param {object} criteria - An Object describing the filter rules
* @param {string} criteria.sourceType - Matches the `sourceType` of the {@link EventBase}
* @param {string} criteria.sourceName - Matches the `sourceName` of the {@link EventBase}
* @param {string} criteria.eventType - Matches the `eventType` of the {@link EventBase}
* @param {Function} criteria.fn - A custom function for making complex filtering decisions. Receives a
* single {@link EventBase} parameter and returns `true` for match and
* `false` for non-match.
* @param {boolean} criteria.any - This filter is `true` if any criteria are true.
* @param {boolean} criteria.all - This filter is `true` if all criteria are true.
* @param {boolean} criteria.none - This filter is `true` if none of criteria are true.
* @param {number} [priority=100] - The priority of this specific filter. Not useful for a single filter,
* but used as part of an {@link EventListener} `filterList`.
*/
constructor(type, criteria, priority = 100) {
checkType("EventFilter.constructor", "type", type, "string");
checkType("EventFilter.constructor", "criteria", criteria, "object");
checkType("EventFilter.constructor", "priority", priority, "number");
this._priority = priority;
if (type === "allow") {
this.allow = true;
} else if (type === "deny") {
this.deny = true;
} else {
throw new TypeError("EventFilter constructor expected 'type' to be 'allow' or 'deny'");
}
this._criteria = criteria;
this._criteriaFn = EventFilter.buildTestFn(criteria);
}
// eslint-disable-next-line jsdoc/require-jsdoc
set allow(v) {
this._isAllow = !!v;
this._isDeny = !this._isAllow;
}
// eslint-disable-next-line jsdoc/require-jsdoc
get allow() {
return this._isAllow;
}
// eslint-disable-next-line jsdoc/require-jsdoc
set deny(v) {
this._isDeny = !!v;
this._isAllow = !this._isDeny;
}
// eslint-disable-next-line jsdoc/require-jsdoc
get deny() {
return this._isDeny;
}
// eslint-disable-next-line jsdoc/require-jsdoc
get priority() {
return this._priority;
}
// eslint-disable-next-line jsdoc/require-jsdoc
get criteria() {
return this._criteria;
}
/**
* Indicates whether the event matches the criteria or not
*
* @param {EventBase} event - The event to evaluate against the criteria
* @returns {boolean} Returns `true` if the specified event matches the criteria specified in the
* constructor, `false` otherwise
*/
matchEvent(event) {
return this._criteriaFn(event);
}
/**
* Indicates whether this event should be denied or not
*
* @param {boolean} event - The event to evaluate against the criteria
* @returns {boolean} Returns `true` if the event matches the criteria and should be denied, `false` otherwise
*/
denyEvent(event) {
if (this.matchEvent(event) && this.deny) {
return true;
}
return false;
}
/**
* Indicates whether this event should be allowed or not
*
* @param {boolean} event - The event to evaluate against the criteria
* @returns {boolean} Returns `true` if the event matches the criteria and should be allowed, `false` otherwise
*/
allowEvent(event) {
if (this.matchEvent(event) && this.allow) {
return true;
}
return false;
}
/**
* Builds a test function for the specified criteria
*
* @param {object} criteria The criteria object. See {@link EventFilter} constructor for details.
* @returns {Function} A function that recieves a single {@link EventBase} parameter and returns `true` if the
* event matches the criteria, `false` otherwise
*/
static buildTestFn(criteria) {
checkType("buildTestFn", "criteria", criteria, "object");
let criteriaFnList = [];
let retFn;
for (let key of Object.keys(criteria)) {
switch (key) {
case "sourceType":
checkType("buildTestFn", "criteria.sourceType", criteria.sourceType, "string");
criteriaFnList.push(matchSourceType.bind(null, criteria.sourceType));
break;
case "sourceName":
checkType("buildTestFn", "criteria.sourceName", criteria.sourceName, "string");
criteriaFnList.push(matchSourceName.bind(null, criteria.sourceName));
break;
case "eventType":
checkType("buildTestFn", "criteria.eventType", criteria.eventType, "string");
criteriaFnList.push(matchEventType.bind(null, criteria.eventType));
break;
case "fn":
throw new Error("not implemented");
case "any":
checkType("buildTestFn", "criteria.any", criteria.any, "boolean");
if (criteria.any) {
retFn = matchCriteriaAny.bind(null, criteriaFnList);
}
break;
case "all":
checkType("buildTestFn", "criteria.all", criteria.all, "boolean");
if (criteria.all) {
retFn = matchCriteriaAll.bind(null, criteriaFnList);
}
break;
case "none":
checkType("buildTestFn", "criteria.none", criteria.none, "boolean");
if (criteria.none) {
retFn = matchCriteriaNone.bind(null, criteriaFnList);
}
break;
default:
throw new TypeError(`key '${key}' isn't a valid filter criteria`);
}
}
if (criteriaFnList.length < 1) {
throw new Error("expected 'criteria' to include at least one of 'sourceType', 'sourceName', 'eventType', 'fn', or 'busName'");
}
if (!retFn) {
throw new Error("expected 'criteria' to include at least one of 'any', 'all', or 'none'");
}
return retFn;
/* eslint-disable-next-line jsdoc/require-jsdoc */
function matchSourceType(name, obj) {
if (obj.sourceType === name) {
return true;
}
return false;
}
/* eslint-disable-next-line jsdoc/require-jsdoc */
function matchSourceName(name, obj) {
if (obj.sourceName === name) {
return true;
}
return false;
}
/* eslint-disable-next-line jsdoc/require-jsdoc */
function matchEventType(type, obj) {
if (obj.type === type) {
return true;
}
return false;
}
/* eslint-disable-next-line jsdoc/require-jsdoc */
function matchCriteriaAny(fnList, e) {
// return true if any function returns true
return fnList.map((fn) => fn(e)).some((i) => i);
}
/* eslint-disable-next-line jsdoc/require-jsdoc */
function matchCriteriaAll(fnList, e) {
// return true if any function returns true
return fnList.map((fn) => fn(e)).every((i) => i);
}
/* eslint-disable-next-line jsdoc/require-jsdoc */
function matchCriteriaNone(fnList, e) {
// return true if any function returns true
return fnList.map((fn) => fn(e)).every((i) => i === false);
}
}
}
module.exports = {
EventFilter,
};