@discipl/law-reg
Version:
Discipl Law and Regulation Compliance Library
262 lines (203 loc) • 8.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FactChecker = void 0;
var _big = _interopRequireDefault(require("big.js"));
var _index = require("../index");
var _logging_util = require("../utils/logging_util");
var _abundanceService = require("@discipl/abundance-service");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Improve intelisense
// eslint-disable-next-line no-unused-vars
class FactChecker {
/**
* Create a ExpressionChecker
* @param {ServiceProvider} serviceProvider
*/
constructor(serviceProvider) {
this.logger = (0, _logging_util.getDiscplLogger)();
this.serviceProvider = serviceProvider;
}
/**
* Get abundance service
* @return {AbundanceService}
* @private
*/
_getAbundanceService() {
return this.serviceProvider.abundanceService;
}
/**
* Get expression checker
* @return {ExpressionChecker}
* @private
*/
_getExpressionChecker() {
return this.serviceProvider.expressionChecker;
}
/**
* Get expression checker
* @return {ContextExplainer}
* @private
*/
_getContextExplainer() {
return this.serviceProvider.contextExplainer;
}
/**
* Checks a fact by doing
* 1. A lookup in the fact reference
* 2. Checking if it is an expression, and parsing it
* a. If it is a simple expression, pass to the factResolver
* b. If it is a complex expression, parse it and evaluate it by parts
*
* @param {string|object} fact - fact or expression
* @param {ssid} ssid - ssid representing the actor
* @param {Context} context -
* @returns {Promise<boolean>} - result of the fact
*/
async checkFact(fact, ssid, context) {
this.logger.debug('Checking fact', fact);
const factLink = context.facts ? context.facts[fact] : null;
const printResult = aResult => this.logger.debug('Resolved', fact, 'as', aResult);
if (factLink) {
if (context.explanation) {
context.explanation.fact = fact;
}
const newContext = this._getContextExplainer().extendContextWithExplanation(context);
const result = await this._checkFactLink(factLink, fact, ssid, newContext);
this._getContextExplainer().extendContextExplanationWithResult(context, result);
printResult(result);
return result;
}
if (typeof fact === 'string') {
if (context.explanation) {
context.explanation.fact = fact;
}
const newContext = this._getContextExplainer().extendContextWithExplanation(context);
const result = await this.checkFactWithResolver(fact, ssid, newContext);
this._getContextExplainer().extendContextExplanationWithResult(context, result);
printResult(result);
return result;
} else {
const result = await this._getExpressionChecker().checkExpression(fact, ssid, context);
this._getContextExplainer().extendContextExplanationWithResult(context, result);
printResult(result);
return result;
}
}
/**
* Checks a fact by using the factResolver from the context.
* If an empty fact is to be checked, this is because a reference was followed. in this case we fall back
* to the previousFact, which likely contains information that can be used to resolve this.
*
* @param {string} fact - Description of the fact, surrounded with []
* @param {ssid} ssid - Identity of entity doing the checking
* @param {Context} context - context of the checking
* @param {string[]} possibleCreatingActions - Possible creating actions
* @returns {Promise<*>}
*/
async checkFactWithResolver(fact, ssid, context, possibleCreatingActions = []) {
const factToCheck = fact === '[]' || fact === '' ? context.previousFact : fact;
const listNames = context.listNames || [];
const listIndices = context.listIndices || [];
const result = context.factResolver(factToCheck, listNames, listIndices, possibleCreatingActions);
let resolvedResult = await Promise.resolve(result);
if (typeof resolvedResult === 'number') {
resolvedResult = (0, _big.default)(resolvedResult);
}
this.logger.debug('Resolving fact', fact, 'as', String(resolvedResult), 'via', factToCheck, 'by factresolver');
this._getContextExplainer().extendContextExplanationWithResult(context, resolvedResult);
return resolvedResult;
}
/**
* Checks a fact link by checking created objects and passing the function to {@link checkFact}
*
* @param {string} factLink - Link to the fact
* @param {string} fact - Name of the fact
* @param {ssid} ssid - Identity of the entity performing the check
* @param {Context} context - Represents the context of the check
* @returns {Promise<boolean>}
* @private
*/
async _checkFactLink(factLink, fact, ssid, context) {
const core = this._getAbundanceService().getCoreAPI();
const factReference = await core.get(factLink, ssid);
const functionRef = factReference.data[_index.DISCIPL_FLINT_FACT].function;
const result = await this.checkFact(functionRef, ssid, { ...context,
previousFact: fact
});
this._getContextExplainer().extendContextExplanationWithResult(context, result);
return result;
}
/**
* Checks if a fact was created in a act that wasn't terminated yet that the given entity has access to.
*
* @param {string} fact - Description of the fact, surrounded with []
* @param {ssid} ssid - Identity of entity doing the checking
* @param {Context} context - context of the checking
* @returns {Promise<boolean>} - true if the fact has been created
*/
async checkCreatableFactCreated(fact, ssid, context) {
this.logger.debug('Checking if', fact, 'was created');
const creatingActions = (await this.getCreatingActs(fact, ssid, context)).map(action => action.link);
if (creatingActions.length === 0) {
return false;
}
const result = await context.factResolver(fact, context.listNames || [], context.listIndices || [], creatingActions);
if (!creatingActions.includes(result) && typeof result !== 'undefined') {
throw new Error('Invalid choice for creating action: ' + result);
}
if (typeof result === 'undefined' && context.myself) {
const actorType = context.searchingFor;
this.logger.debug('Multiple creating acts found. Checking if you are at least a', actorType);
const isActorType = await this.checkFact(actorType, ssid, context);
this.logger.debug('Resolved you are a', actorType, 'as', isActorType);
return isActorType ? undefined : false;
}
return result;
}
/**
* Get all creating acts where the fact was created and not terminated yet
*
* @param {string} fact - Description of the fact, surrounded with []
* @param {ssid} ssid - Identity of entity getting the acts
* @param {Context} context - context of the getting
* @returns {Promise<CreatingAct[]>}
*/
async getCreatingActs(fact, ssid, context) {
this.logger.debug('Getting creating actions for', fact);
const core = this._getAbundanceService().getCoreAPI();
let caseLink = context.caseLink;
/**
* @type {CreatingAct[]}
*/
const possibleCreatingActions = [];
const terminatedCreatingActions = [];
while (caseLink != null) {
const caseData = await core.get(caseLink, ssid);
const lastTakenAction = caseData.data[_index.DISCIPL_FLINT_ACT_TAKEN];
if (lastTakenAction != null) {
const actData = await core.get(lastTakenAction, ssid);
const act = actData.data[_index.DISCIPL_FLINT_ACT];
if (act.create != null && act.create.includes(fact)) {
this.logger.debug('Found possible creating act', act.act);
possibleCreatingActions.push({
link: caseLink,
contextFact: fact,
facts: caseData.data[_index.DISCIPL_FLINT_FACTS_SUPPLIED]
});
}
if (act.terminate != null && act.terminate.includes(fact)) {
this.logger.debug('Found possible terminating act', act.act);
const terminatedLink = caseData.data[_index.DISCIPL_FLINT_FACTS_SUPPLIED][fact];
terminatedCreatingActions.push(terminatedLink);
}
}
caseLink = caseData.data[_index.DISCIPL_FLINT_PREVIOUS_CASE];
}
const filtered = possibleCreatingActions.filter(creatingAction => !terminatedCreatingActions.includes(creatingAction.link));
this.logger.debug('Creating acts', filtered);
return filtered;
}
}
exports.FactChecker = FactChecker;