@magda/typescript-common 1.1.0-alpha.2 → 1.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AspectBuilder.d.ts +7 -0
- package/dist/AspectBuilder.js +3 -0
- package/dist/AspectBuilder.js.map +1 -0
- package/dist/AspectCreationFailure.d.ts +5 -0
- package/dist/AspectCreationFailure.js +10 -0
- package/dist/AspectCreationFailure.js.map +1 -0
- package/dist/AsyncPage.d.ts +18 -0
- package/dist/AsyncPage.js +140 -0
- package/dist/AsyncPage.js.map +1 -0
- package/dist/ConnectionResult.d.ts +16 -0
- package/dist/ConnectionResult.js +77 -0
- package/dist/ConnectionResult.js.map +1 -0
- package/dist/ConnectorRecordId.d.ts +15 -0
- package/dist/ConnectorRecordId.js +46 -0
- package/dist/ConnectorRecordId.js.map +1 -0
- package/dist/JsonConnector.d.ts +195 -0
- package/dist/JsonConnector.js +427 -0
- package/dist/JsonConnector.js.map +1 -0
- package/dist/JsonTransformer.d.ts +112 -0
- package/dist/JsonTransformer.js +225 -0
- package/dist/JsonTransformer.js.map +1 -0
- package/dist/OpaCompileResponseParser.d.ts +480 -0
- package/dist/OpaCompileResponseParser.js +1134 -0
- package/dist/OpaCompileResponseParser.js.map +1 -0
- package/dist/RecordCreationFailure.d.ts +7 -0
- package/dist/RecordCreationFailure.js +11 -0
- package/dist/RecordCreationFailure.js.map +1 -0
- package/dist/SimpleOpaSQLTranslator.d.ts +14 -0
- package/dist/SimpleOpaSQLTranslator.js +79 -0
- package/dist/SimpleOpaSQLTranslator.js.map +1 -0
- package/dist/addTrailingSlash.d.ts +1 -0
- package/dist/addTrailingSlash.js +15 -0
- package/dist/addTrailingSlash.js.map +1 -0
- package/dist/appendUrlSegments.d.ts +2 -0
- package/dist/appendUrlSegments.js +18 -0
- package/dist/appendUrlSegments.js.map +1 -0
- package/dist/authorization-api/AccessControlError.d.ts +4 -0
- package/dist/authorization-api/AccessControlError.js +13 -0
- package/dist/authorization-api/AccessControlError.js.map +1 -0
- package/dist/authorization-api/ApiClient.d.ts +141 -0
- package/dist/authorization-api/ApiClient.js +335 -0
- package/dist/authorization-api/ApiClient.js.map +1 -0
- package/dist/authorization-api/AuthError.d.ts +4 -0
- package/dist/authorization-api/AuthError.js +13 -0
- package/dist/authorization-api/AuthError.js.map +1 -0
- package/dist/authorization-api/GenericError.d.ts +9 -0
- package/dist/authorization-api/GenericError.js +17 -0
- package/dist/authorization-api/GenericError.js.map +1 -0
- package/dist/authorization-api/authMiddleware.d.ts +7 -0
- package/dist/authorization-api/authMiddleware.js +52 -0
- package/dist/authorization-api/authMiddleware.js.map +1 -0
- package/dist/authorization-api/model.d.ts +99 -0
- package/dist/authorization-api/model.js +3 -0
- package/dist/authorization-api/model.js.map +1 -0
- package/dist/coerceJson.d.ts +2 -0
- package/dist/coerceJson.js +13 -0
- package/dist/coerceJson.js.map +1 -0
- package/dist/createServiceError.d.ts +19 -0
- package/dist/createServiceError.js +45 -0
- package/dist/createServiceError.js.map +1 -0
- package/dist/delay.d.ts +2 -0
- package/dist/delay.js +9 -0
- package/dist/delay.js.map +1 -0
- package/dist/express/status.d.ts +30 -0
- package/dist/express/status.js +180 -0
- package/dist/express/status.js.map +1 -0
- package/dist/formatServiceError.d.ts +1 -0
- package/dist/formatServiceError.js +23 -0
- package/dist/formatServiceError.js.map +1 -0
- package/dist/generated/registry/api.d.ts +596 -0
- package/dist/generated/registry/api.js +2060 -0
- package/dist/generated/registry/api.js.map +1 -0
- package/dist/getAbsoluteUrl.d.ts +12 -0
- package/dist/getAbsoluteUrl.js +35 -0
- package/dist/getAbsoluteUrl.js.map +1 -0
- package/dist/getBasePathFromUrl.d.ts +10 -0
- package/dist/getBasePathFromUrl.js +27 -0
- package/dist/getBasePathFromUrl.js.map +1 -0
- package/dist/markdownToHtml.d.ts +3 -0
- package/dist/markdownToHtml.js +38 -0
- package/dist/markdownToHtml.js.map +1 -0
- package/dist/opa/OpaTypes.d.ts +41 -0
- package/dist/opa/OpaTypes.js +3 -0
- package/dist/opa/OpaTypes.js.map +1 -0
- package/dist/opa/getAuthDecision.d.ts +19 -0
- package/dist/opa/getAuthDecision.js +94 -0
- package/dist/opa/getAuthDecision.js.map +1 -0
- package/dist/opa/queryOpa.d.ts +1 -0
- package/dist/opa/queryOpa.js +41 -0
- package/dist/opa/queryOpa.js.map +1 -0
- package/dist/registry/AuthorizedRegistryClient.d.ts +29 -0
- package/dist/registry/AuthorizedRegistryClient.js +115 -0
- package/dist/registry/AuthorizedRegistryClient.js.map +1 -0
- package/dist/registry/RegistryClient.d.ts +36 -0
- package/dist/registry/RegistryClient.js +59 -0
- package/dist/registry/RegistryClient.js.map +1 -0
- package/dist/registry/TenantConsts.d.ts +31 -0
- package/dist/registry/TenantConsts.js +35 -0
- package/dist/registry/TenantConsts.js.map +1 -0
- package/dist/registry-manual/api.d.ts +9 -0
- package/dist/registry-manual/api.js +10 -0
- package/dist/registry-manual/api.js.map +1 -0
- package/dist/request.d.ts +3 -0
- package/dist/request.js +19 -0
- package/dist/request.js.map +1 -0
- package/dist/retry.d.ts +1 -0
- package/dist/retry.js +21 -0
- package/dist/retry.js.map +1 -0
- package/dist/retryBackoff.d.ts +1 -0
- package/dist/retryBackoff.js +21 -0
- package/dist/retryBackoff.js.map +1 -0
- package/dist/runLater.d.ts +1 -0
- package/dist/runLater.js +16 -0
- package/dist/runLater.js.map +1 -0
- package/dist/session/GetUserId.d.ts +5 -0
- package/dist/session/GetUserId.js +41 -0
- package/dist/session/GetUserId.js.map +1 -0
- package/dist/session/GetUserSession.d.ts +3 -0
- package/dist/session/GetUserSession.js +22 -0
- package/dist/session/GetUserSession.js.map +1 -0
- package/dist/session/addJwtSecretFromEnvVar.d.ts +14 -0
- package/dist/session/addJwtSecretFromEnvVar.js +23 -0
- package/dist/session/addJwtSecretFromEnvVar.js.map +1 -0
- package/dist/session/buildJwt.d.ts +1 -0
- package/dist/session/buildJwt.js +8 -0
- package/dist/session/buildJwt.js.map +1 -0
- package/dist/session/cookieUtils.d.ts +16 -0
- package/dist/session/cookieUtils.js +19 -0
- package/dist/session/cookieUtils.js.map +1 -0
- package/dist/session/destroySession.d.ts +10 -0
- package/dist/session/destroySession.js +44 -0
- package/dist/session/destroySession.js.map +1 -0
- package/dist/session/getSessionId.d.ts +2 -0
- package/dist/session/getSessionId.js +28 -0
- package/dist/session/getSessionId.js.map +1 -0
- package/dist/tenant-api/AuthorizedTenantClient.d.ts +25 -0
- package/dist/tenant-api/AuthorizedTenantClient.js +61 -0
- package/dist/tenant-api/AuthorizedTenantClient.js.map +1 -0
- package/dist/tenant-api/Tenant.d.ts +17 -0
- package/dist/tenant-api/Tenant.js +10 -0
- package/dist/tenant-api/Tenant.js.map +1 -0
- package/dist/test/JsonConnector.spec.d.ts +1 -0
- package/dist/test/JsonConnector.spec.js +352 -0
- package/dist/test/JsonConnector.spec.js.map +1 -0
- package/dist/test/JsonTransformer.spec.d.ts +1 -0
- package/dist/test/JsonTransformer.spec.js +98 -0
- package/dist/test/JsonTransformer.spec.js.map +1 -0
- package/dist/test/arbitraries.d.ts +98 -0
- package/dist/test/arbitraries.js +264 -0
- package/dist/test/arbitraries.js.map +1 -0
- package/dist/test/aspect-templates/organization-details.d.ts +4 -0
- package/dist/test/aspect-templates/organization-details.js +11 -0
- package/dist/test/aspect-templates/organization-details.js.map +1 -0
- package/dist/test/buildApiClient.spec.d.ts +1 -0
- package/dist/test/buildApiClient.spec.js +177 -0
- package/dist/test/buildApiClient.spec.js.map +1 -0
- package/dist/test/buildAuthorizedTenantClient.spec.d.ts +1 -0
- package/dist/test/buildAuthorizedTenantClient.spec.js +60 -0
- package/dist/test/buildAuthorizedTenantClient.spec.js.map +1 -0
- package/dist/test/connectors/MockExpressServer.d.ts +5 -0
- package/dist/test/connectors/MockExpressServer.js +28 -0
- package/dist/test/connectors/MockExpressServer.js.map +1 -0
- package/dist/test/connectors/MockRegistry.d.ts +7 -0
- package/dist/test/connectors/MockRegistry.js +93 -0
- package/dist/test/connectors/MockRegistry.js.map +1 -0
- package/dist/test/connectors/runConnectorTest.d.ts +5 -0
- package/dist/test/connectors/runConnectorTest.js +106 -0
- package/dist/test/connectors/runConnectorTest.js.map +1 -0
- package/dist/test/db/getTestDBConfig.d.ts +7 -0
- package/dist/test/db/getTestDBConfig.js +13 -0
- package/dist/test/db/getTestDBConfig.js.map +1 -0
- package/dist/test/db/runMigrationSql.d.ts +9 -0
- package/dist/test/db/runMigrationSql.js +82 -0
- package/dist/test/db/runMigrationSql.js.map +1 -0
- package/dist/test/express/status.spec.d.ts +1 -0
- package/dist/test/express/status.spec.js +160 -0
- package/dist/test/express/status.spec.js.map +1 -0
- package/dist/test/fakeArgv.d.ts +2 -0
- package/dist/test/fakeArgv.js +7 -0
- package/dist/test/fakeArgv.js.map +1 -0
- package/dist/test/getAuthDecision.spec.d.ts +1 -0
- package/dist/test/getAuthDecision.spec.js +22 -0
- package/dist/test/getAuthDecision.spec.js.map +1 -0
- package/dist/test/getBasePathFromUrl.spec.d.ts +1 -0
- package/dist/test/getBasePathFromUrl.spec.js +25 -0
- package/dist/test/getBasePathFromUrl.spec.js.map +1 -0
- package/dist/test/jsverify.d.ts +2 -0
- package/dist/test/jsverify.js +8 -0
- package/dist/test/jsverify.js.map +1 -0
- package/dist/test/mockAuthApiHost.d.ts +14 -0
- package/dist/test/mockAuthApiHost.js +174 -0
- package/dist/test/mockAuthApiHost.js.map +1 -0
- package/dist/test/mockAuthorization.d.ts +3 -0
- package/dist/test/mockAuthorization.js +24 -0
- package/dist/test/mockAuthorization.js.map +1 -0
- package/dist/test/mockTenantDataStore.d.ts +8 -0
- package/dist/test/mockTenantDataStore.js +41 -0
- package/dist/test/mockTenantDataStore.js.map +1 -0
- package/dist/test/mockUserDataStore.d.ts +11 -0
- package/dist/test/mockUserDataStore.js +56 -0
- package/dist/test/mockUserDataStore.js.map +1 -0
- package/dist/test/registry/buildAuthorizedClient.spec.d.ts +1 -0
- package/dist/test/registry/buildAuthorizedClient.spec.js +101 -0
- package/dist/test/registry/buildAuthorizedClient.spec.js.map +1 -0
- package/dist/test/sampleOpaResponse.json +480 -0
- package/dist/test/sampleOpaResponseSimple.json +173 -0
- package/dist/test/sampleOpaResponseUnconditionalTrue.json +193 -0
- package/dist/test/sampleOpaResponseUnconditionalTrueWithDefaultRule.json +303 -0
- package/dist/test/sampleOpaResponseWithDefaultRule.json +303 -0
- package/dist/test/session/buildJwt.spec.d.ts +1 -0
- package/dist/test/session/buildJwt.spec.js +50 -0
- package/dist/test/session/buildJwt.spec.js.map +1 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaGroupsAndOwnerTest.spec.d.ts +1 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaGroupsAndOwnerTest.spec.js +64 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaGroupsAndOwnerTest.spec.js.map +1 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaGroupsTest.spec.d.ts +1 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaGroupsTest.spec.js +52 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaGroupsTest.spec.js.map +1 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaOwnerTest.spec.d.ts +1 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaOwnerTest.spec.js +36 -0
- package/dist/test/session/buildJwtForRegistryEsriOpaOwnerTest.spec.js.map +1 -0
- package/dist/test/testAsyncPage.spec.d.ts +1 -0
- package/dist/test/testAsyncPage.spec.js +463 -0
- package/dist/test/testAsyncPage.spec.js.map +1 -0
- package/dist/test/testOpaCompileResponseParser.spec.d.ts +1 -0
- package/dist/test/testOpaCompileResponseParser.spec.js +148 -0
- package/dist/test/testOpaCompileResponseParser.spec.js.map +1 -0
- package/dist/test/util.d.ts +1 -0
- package/dist/test/util.js +8 -0
- package/dist/test/util.js.map +1 -0
- package/dist/util/arrayToMaybe.d.ts +2 -0
- package/dist/util/arrayToMaybe.js +8 -0
- package/dist/util/arrayToMaybe.js.map +1 -0
- package/dist/util/cleanOrgTitle.d.ts +1 -0
- package/dist/util/cleanOrgTitle.js +13 -0
- package/dist/util/cleanOrgTitle.js.map +1 -0
- package/dist/util/escapeRegExp.d.ts +2 -0
- package/dist/util/escapeRegExp.js +5 -0
- package/dist/util/escapeRegExp.js.map +1 -0
- package/dist/util/getMinikubeIP.d.ts +1 -0
- package/dist/util/getMinikubeIP.js +13 -0
- package/dist/util/getMinikubeIP.js.map +1 -0
- package/dist/util/isUuid.d.ts +2 -0
- package/dist/util/isUuid.js +6 -0
- package/dist/util/isUuid.js.map +1 -0
- package/dist/util/unionToThrowable.d.ts +1 -0
- package/dist/util/unionToThrowable.js +12 -0
- package/dist/util/unionToThrowable.js.map +1 -0
- package/dist/utilityTypes.d.ts +1 -0
- package/dist/utilityTypes.js +3 -0
- package/dist/utilityTypes.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.unknown2Ref = exports.value2String = exports.RegoRef = exports.RegoExp = exports.RegoTerm = exports.RegoOperators = exports.RegoRule = void 0;
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
/**
|
|
9
|
+
* @class RegoRule
|
|
10
|
+
* @export
|
|
11
|
+
*
|
|
12
|
+
* RegoRule represents [Rule](https://www.openpolicyagent.org/docs/latest/how-do-i-write-policies/#rules) concept in Rego language.
|
|
13
|
+
* - A simple rule is made up of Rule head, Rule value & Rule body
|
|
14
|
+
* - The Rule value defines the final result of the rule if the rule is matched (i.e. all expressions in rule body are true)
|
|
15
|
+
* - If you didn't sepcify the rule value, it will be assume as boolean value `true`
|
|
16
|
+
* - The rule body is made up of one of more rego expressions (see @class RegoExp) and each of the expression is made up of terms (see @class RegoTerm)
|
|
17
|
+
* - The rule is considered as matched if all expressions in rule body are `true`
|
|
18
|
+
* You can opt to define a `default` rule. A default rule has no rule body and will only considered as matched if all other rules are not matched.
|
|
19
|
+
*/
|
|
20
|
+
class RegoRule {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.isCompleteEvaluated = false;
|
|
23
|
+
this.name = lodash_1.default.isString(options.name) ? options.name : "";
|
|
24
|
+
this.fullName = lodash_1.default.isString(options.fullName) ? options.fullName : "";
|
|
25
|
+
this.isDefault = lodash_1.default.isBoolean(options.isDefault)
|
|
26
|
+
? options.isDefault
|
|
27
|
+
: false;
|
|
28
|
+
this.value = lodash_1.default.isUndefined(options.value) ? true : options.value;
|
|
29
|
+
this.expressions = lodash_1.default.isArray(options.expressions)
|
|
30
|
+
? options.expressions
|
|
31
|
+
: [];
|
|
32
|
+
this.isCompleteEvaluated = lodash_1.default.isBoolean(options.isCompleteEvaluated)
|
|
33
|
+
? options.isCompleteEvaluated
|
|
34
|
+
: false;
|
|
35
|
+
this.parser = options.parser;
|
|
36
|
+
if (this.name === "") {
|
|
37
|
+
throw new Error("Rule name can't be empty");
|
|
38
|
+
}
|
|
39
|
+
if (this.fullName === "") {
|
|
40
|
+
throw new Error("Rule fullName can't be empty");
|
|
41
|
+
}
|
|
42
|
+
if (!(this.parser instanceof OpaCompileResponseParser)) {
|
|
43
|
+
throw new Error("Require parser parameter to create a RegoRule");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
clone(options = {}) {
|
|
47
|
+
const regoRule = new RegoRule(Object.assign({ name: this.name, fullName: this.fullName, isDefault: this.isDefault, value: this.value, isCompleteEvaluated: this.isCompleteEvaluated, expressions: this.expressions.map((e) => e.clone()), parser: this.parser }, options));
|
|
48
|
+
regoRule.isMatched = this.isMatched;
|
|
49
|
+
return regoRule;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Re-evaluate this rule
|
|
53
|
+
* If fully evaluated, this.isCompleteEvaluated will be set to true
|
|
54
|
+
*
|
|
55
|
+
* @returns
|
|
56
|
+
* @memberof RegoRule
|
|
57
|
+
*/
|
|
58
|
+
evaluate() {
|
|
59
|
+
this.expressions = this.expressions.map((exp) => exp.evaluate());
|
|
60
|
+
const falseExpression = this.expressions.find((exp) => exp.isMatch() === false);
|
|
61
|
+
if (!lodash_1.default.isUndefined(falseExpression)) {
|
|
62
|
+
// --- rule expressions are always evaluated in the context of AND
|
|
63
|
+
// --- any false expression will make the rule not match
|
|
64
|
+
this.isCompleteEvaluated = true;
|
|
65
|
+
this.isMatched = false;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// --- filter out all expressions are evaluated
|
|
69
|
+
// --- note any non-false value will considered as a match (true) i.e. 0 is equivalent to true
|
|
70
|
+
// --- empty expression array indicates unconditional match (true)
|
|
71
|
+
const idx = this.expressions.findIndex((exp) => !exp.isCompleteEvaluated);
|
|
72
|
+
if (idx === -1) {
|
|
73
|
+
this.isCompleteEvaluated = true;
|
|
74
|
+
this.isMatched = true;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// --- further dry the rule if the rule has unsolved exps
|
|
78
|
+
// --- if a exp is matched (i.e. true) it can be strip out as true AND xxxx = xxxx
|
|
79
|
+
this.expressions = this.expressions.filter((exp) => exp.isMatch() !== true);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Generate Human Readable string of this rule
|
|
86
|
+
* If it's fully evaluated, the output will be true or false (or actual rule value)
|
|
87
|
+
* Otherwise, will generate expressions concate with `AND`
|
|
88
|
+
*
|
|
89
|
+
* @returns {string}
|
|
90
|
+
* @memberof RegoRule
|
|
91
|
+
*/
|
|
92
|
+
toHumanReadableString() {
|
|
93
|
+
if (this.isCompleteEvaluated) {
|
|
94
|
+
if (this.isMatched) {
|
|
95
|
+
return value2String(this.value);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const parts = this.expressions.map((e) => e.toHumanReadableString());
|
|
103
|
+
return parts.join(" AND \n");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create RegoRule from Opa response data
|
|
108
|
+
*
|
|
109
|
+
* @static
|
|
110
|
+
* @param {*} r
|
|
111
|
+
* @param {string} packageName
|
|
112
|
+
* @param {OpaCompileResponseParser} parser
|
|
113
|
+
* @returns {RegoRule}
|
|
114
|
+
* @memberof RegoRule
|
|
115
|
+
*/
|
|
116
|
+
static parseFromData(r, packageName, parser) {
|
|
117
|
+
const ruleName = r.head && r.head.name ? r.head.name : "";
|
|
118
|
+
const ruleFullName = [packageName, ruleName].join(".");
|
|
119
|
+
const ruleIsDefault = r.default === true;
|
|
120
|
+
const ruleValue = r.head && r.head.value && !lodash_1.default.isUndefined(r.head.value.value)
|
|
121
|
+
? r.head.value.value
|
|
122
|
+
: true;
|
|
123
|
+
const ruleOptions = {
|
|
124
|
+
name: ruleName,
|
|
125
|
+
fullName: ruleFullName,
|
|
126
|
+
isDefault: ruleIsDefault,
|
|
127
|
+
value: ruleValue,
|
|
128
|
+
expressions: RegoRule.createExpressionsFromRuleBodyData(r.body, parser),
|
|
129
|
+
parser
|
|
130
|
+
};
|
|
131
|
+
const regoRule = new RegoRule(ruleOptions);
|
|
132
|
+
regoRule.evaluate();
|
|
133
|
+
return regoRule;
|
|
134
|
+
}
|
|
135
|
+
static createExpressionsFromRuleBodyData(data, parser) {
|
|
136
|
+
if (!lodash_1.default.isArray(data) || !data.length) {
|
|
137
|
+
throw new Error(`Encountered empty rule body.`);
|
|
138
|
+
}
|
|
139
|
+
return data.map((expData) => RegoExp.parseFromData(expData, parser));
|
|
140
|
+
}
|
|
141
|
+
static randomRuleName(prefix) {
|
|
142
|
+
return (prefix + Math.random()).replace(".", "_");
|
|
143
|
+
}
|
|
144
|
+
static createFromValue(val, parser) {
|
|
145
|
+
const ruleName = RegoRule.randomRuleName("fixed_value_rule_");
|
|
146
|
+
return new RegoRule({
|
|
147
|
+
isDefault: false,
|
|
148
|
+
name: ruleName,
|
|
149
|
+
fullName: ruleName,
|
|
150
|
+
isCompleteEvaluated: true,
|
|
151
|
+
expressions: [],
|
|
152
|
+
value: val,
|
|
153
|
+
parser
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.RegoRule = RegoRule;
|
|
158
|
+
exports.RegoOperators = {
|
|
159
|
+
eq: "=",
|
|
160
|
+
equal: "=",
|
|
161
|
+
neq: "!=",
|
|
162
|
+
lt: "<",
|
|
163
|
+
gt: ">",
|
|
164
|
+
lte: "<=",
|
|
165
|
+
gte: ">="
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* RegoTerm represent the basic elements that creates an expressions.
|
|
169
|
+
* e.g. An expression `a > 4` is made up of 3 terms
|
|
170
|
+
* - Reference Term: `a`
|
|
171
|
+
* - Operator Term `>`
|
|
172
|
+
* - Value Term: `4`
|
|
173
|
+
*
|
|
174
|
+
* @export
|
|
175
|
+
* @class RegoTerm
|
|
176
|
+
*/
|
|
177
|
+
class RegoTerm {
|
|
178
|
+
constructor(type, value, parser) {
|
|
179
|
+
this.type = type;
|
|
180
|
+
this.value = value;
|
|
181
|
+
this.parser = parser;
|
|
182
|
+
}
|
|
183
|
+
clone() {
|
|
184
|
+
return new RegoTerm(this.type, this.value, this.parser);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* If it's a reference term, return its full string representation
|
|
188
|
+
*
|
|
189
|
+
* @returns
|
|
190
|
+
* @memberof RegoTerm
|
|
191
|
+
*/
|
|
192
|
+
asString() {
|
|
193
|
+
if (this.value instanceof RegoRef)
|
|
194
|
+
return this.value.fullRefString();
|
|
195
|
+
else
|
|
196
|
+
return this.value;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* If it's a reference term. A operator is an Reference term as well
|
|
200
|
+
*
|
|
201
|
+
* @returns {boolean}
|
|
202
|
+
* @memberof RegoTerm
|
|
203
|
+
*/
|
|
204
|
+
isRef() {
|
|
205
|
+
if (this.value instanceof RegoRef)
|
|
206
|
+
return true;
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Return RegoRef instance if this term is a RegoRef.
|
|
211
|
+
*
|
|
212
|
+
* @returns {RegoRef}
|
|
213
|
+
* @memberof RegoTerm
|
|
214
|
+
*/
|
|
215
|
+
getRef() {
|
|
216
|
+
if (this.isRef()) {
|
|
217
|
+
return this.value;
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`Term ${this.asString()} is not a ref`);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* If the term is a reference and it contains any collection lookup
|
|
223
|
+
* e.g.
|
|
224
|
+
* - objectA.propB.collectionC[_]
|
|
225
|
+
* - objectA.propB.collectionC[_].ABC[_].name
|
|
226
|
+
* - objectA.propB.collectionC[_].id
|
|
227
|
+
*
|
|
228
|
+
* @returns {boolean}
|
|
229
|
+
* @memberof RegoTerm
|
|
230
|
+
*/
|
|
231
|
+
hasCollectionLookup() {
|
|
232
|
+
if (!this.isRef())
|
|
233
|
+
return false;
|
|
234
|
+
const ref = this.getRef();
|
|
235
|
+
return ref.hasCollectionLookup();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The term is not only a Reference but a reference contains simple collection lookup
|
|
239
|
+
* i.e. only contains one collection lookup and the whole ref ends with the only collection lookup
|
|
240
|
+
* e.g. objectA.propB.collectionC[_]
|
|
241
|
+
* Note: objectA.propB.collectionC[_].name is not a simple collection lookup as it resolve to single value (`name` property)
|
|
242
|
+
* rather than a collection
|
|
243
|
+
*
|
|
244
|
+
* @returns {boolean}
|
|
245
|
+
* @memberof RegoTerm
|
|
246
|
+
*/
|
|
247
|
+
isSimpleCollectionLookup() {
|
|
248
|
+
if (!this.isRef())
|
|
249
|
+
return false;
|
|
250
|
+
const ref = this.getRef();
|
|
251
|
+
return ref.isSimpleCollectionLookup();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
*
|
|
255
|
+
*
|
|
256
|
+
* @returns {boolean}
|
|
257
|
+
* @memberof RegoTerm
|
|
258
|
+
*/
|
|
259
|
+
isResolveAsCollectionValue() {
|
|
260
|
+
if (!this.isRef())
|
|
261
|
+
return false;
|
|
262
|
+
const ref = this.getRef();
|
|
263
|
+
return ref.isResolveAsCollectionValue();
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* If it's a reference term, return its full string representation
|
|
267
|
+
* Otherwise, throw exception
|
|
268
|
+
*
|
|
269
|
+
* @param {string[]} [removalPrefixs=[]]
|
|
270
|
+
* @returns {string}
|
|
271
|
+
* @memberof RegoTerm
|
|
272
|
+
*/
|
|
273
|
+
fullRefString(removalPrefixs = []) {
|
|
274
|
+
if (this.value instanceof RegoRef) {
|
|
275
|
+
return this.value.fullRefString(removalPrefixs);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
throw new Error("Tried to call `fullRefString` on non Ref term.");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* If it's a reference term, return its string representation (not include ending [_])
|
|
283
|
+
* Otherwise, throw exception
|
|
284
|
+
*
|
|
285
|
+
* @param {string[]} [removalPrefixs=[]]
|
|
286
|
+
* @returns {string}
|
|
287
|
+
* @memberof RegoTerm
|
|
288
|
+
*/
|
|
289
|
+
refString(removalPrefixs = []) {
|
|
290
|
+
if (this.value instanceof RegoRef) {
|
|
291
|
+
return this.value.refString(removalPrefixs);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
throw new Error("Tried to call `refString` on non Ref term.");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Return term as operator string e.g. `=`, `>=` etc.
|
|
299
|
+
*
|
|
300
|
+
* @returns {string}
|
|
301
|
+
* @memberof RegoTerm
|
|
302
|
+
*/
|
|
303
|
+
asOperator() {
|
|
304
|
+
if (this.value instanceof RegoRef) {
|
|
305
|
+
return this.value.asOperator();
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
throw new Error("Tried to call `asOperator` on non Ref term.");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* If it's a operator term
|
|
313
|
+
*
|
|
314
|
+
* @returns {boolean}
|
|
315
|
+
* @memberof RegoTerm
|
|
316
|
+
*/
|
|
317
|
+
isOperator() {
|
|
318
|
+
if (this.value instanceof RegoRef) {
|
|
319
|
+
return this.value.isOperator();
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Tried to determine the value of the term
|
|
327
|
+
*
|
|
328
|
+
* @returns {RegoValue}
|
|
329
|
+
* @memberof RegoTerm
|
|
330
|
+
*/
|
|
331
|
+
getValue() {
|
|
332
|
+
if (!this.isRef()) {
|
|
333
|
+
return this.value;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
if (this.isOperator()) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const fullName = this.fullRefString();
|
|
341
|
+
const result = this.parser.completeRuleResults[fullName];
|
|
342
|
+
if (lodash_1.default.isUndefined(result))
|
|
343
|
+
return undefined;
|
|
344
|
+
return result.value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
static parseFromData(data, parser) {
|
|
349
|
+
if (data.type === "ref") {
|
|
350
|
+
return new RegoTerm(data.type, RegoRef.parseFromData(data), parser);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
return new RegoTerm(data.type, data.value, parser);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
exports.RegoTerm = RegoTerm;
|
|
358
|
+
/**
|
|
359
|
+
* Represents Rego expression
|
|
360
|
+
*
|
|
361
|
+
* @export
|
|
362
|
+
* @class RegoExp
|
|
363
|
+
*/
|
|
364
|
+
class RegoExp {
|
|
365
|
+
constructor(terms, isNegated = false, isCompleteEvaluated = false, value = null, parser) {
|
|
366
|
+
/**
|
|
367
|
+
* If it's complete evaluated
|
|
368
|
+
*
|
|
369
|
+
* @type {boolean}
|
|
370
|
+
* @memberof RegoExp
|
|
371
|
+
*/
|
|
372
|
+
this.isCompleteEvaluated = false;
|
|
373
|
+
/**
|
|
374
|
+
* The value of the expression
|
|
375
|
+
*
|
|
376
|
+
* @type {RegoValue}
|
|
377
|
+
* @memberof RegoExp
|
|
378
|
+
*/
|
|
379
|
+
this.value = null;
|
|
380
|
+
this.terms = terms;
|
|
381
|
+
this.isNegated = isNegated;
|
|
382
|
+
this.isCompleteEvaluated = isCompleteEvaluated;
|
|
383
|
+
this.value = value;
|
|
384
|
+
this.parser = parser;
|
|
385
|
+
}
|
|
386
|
+
clone() {
|
|
387
|
+
const regoExp = new RegoExp(this.terms.map((t) => t.clone()), this.isNegated, this.isCompleteEvaluated, this.value, this.parser);
|
|
388
|
+
return regoExp;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* For debug usage, print terms as easy to ready short string
|
|
392
|
+
*
|
|
393
|
+
* @returns
|
|
394
|
+
* @memberof RegoExp
|
|
395
|
+
*/
|
|
396
|
+
termsAsString() {
|
|
397
|
+
return this.terms.map((t) => t.asString());
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Output human readable string
|
|
401
|
+
*
|
|
402
|
+
* @returns {string}
|
|
403
|
+
* @memberof RegoExp
|
|
404
|
+
*/
|
|
405
|
+
toHumanReadableString() {
|
|
406
|
+
if (this.terms.length === 1) {
|
|
407
|
+
const value = this.terms[0].getValue();
|
|
408
|
+
const parts = [];
|
|
409
|
+
if (this.isNegated)
|
|
410
|
+
parts.push("NOT");
|
|
411
|
+
if (!lodash_1.default.isUndefined(value)) {
|
|
412
|
+
parts.push(value2String(value));
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
parts.push(this.terms[0].fullRefString());
|
|
416
|
+
}
|
|
417
|
+
return parts.join(" ");
|
|
418
|
+
}
|
|
419
|
+
else if (this.terms.length === 3) {
|
|
420
|
+
const [operator, operands] = this.toOperatorOperandsArray();
|
|
421
|
+
const parts = [];
|
|
422
|
+
if (operands[0].isRef()) {
|
|
423
|
+
parts.push(operands[0].fullRefString());
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
parts.push(value2String(operands[0].getValue()));
|
|
427
|
+
}
|
|
428
|
+
parts.push(operator);
|
|
429
|
+
if (operands[1].isRef()) {
|
|
430
|
+
parts.push(operands[1].fullRefString());
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
parts.push(value2String(operands[1].getValue()));
|
|
434
|
+
}
|
|
435
|
+
const expStr = parts.join(" ");
|
|
436
|
+
if (this.isNegated)
|
|
437
|
+
return `NOT (${expStr})`;
|
|
438
|
+
return expStr;
|
|
439
|
+
}
|
|
440
|
+
throw new Error(`Invalid rego expression: ${this.termsAsString()}`);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Try to determins its value
|
|
444
|
+
*
|
|
445
|
+
* @returns
|
|
446
|
+
* @memberof RegoExp
|
|
447
|
+
*/
|
|
448
|
+
getValue() {
|
|
449
|
+
this.evaluate();
|
|
450
|
+
if (!this.isCompleteEvaluated)
|
|
451
|
+
return undefined;
|
|
452
|
+
if (this.isNegated) {
|
|
453
|
+
return this.value === false ? true : false;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
// --- undefined is a common value in Rego similar to false
|
|
457
|
+
// --- we set to false here to tell the difference between
|
|
458
|
+
// --- real undefined (not full resolved) and undefined value
|
|
459
|
+
if (lodash_1.default.isUndefined(this.value))
|
|
460
|
+
return false;
|
|
461
|
+
else
|
|
462
|
+
return this.value;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
isMatch() {
|
|
466
|
+
const value = this.getValue();
|
|
467
|
+
if (lodash_1.default.isUndefined(value)) {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
if (value === false || lodash_1.default.isUndefined(value))
|
|
472
|
+
return false;
|
|
473
|
+
// --- 0 is a match
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Convert operator term to string and put rest operands into an array.
|
|
479
|
+
* And then return a [Operator, Operands] structure
|
|
480
|
+
*
|
|
481
|
+
* @returns {[string, RegoTerm[]]}
|
|
482
|
+
* @memberof RegoExp
|
|
483
|
+
*/
|
|
484
|
+
toOperatorOperandsArray() {
|
|
485
|
+
if (this.terms.length !== 3) {
|
|
486
|
+
throw new Error(`Can't get Operator & Operands from non 3 terms expression: ${this.termsAsString()}`);
|
|
487
|
+
}
|
|
488
|
+
const operands = [];
|
|
489
|
+
let operator = null;
|
|
490
|
+
this.terms.forEach((t) => {
|
|
491
|
+
if (t.isOperator()) {
|
|
492
|
+
operator = t.asOperator();
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const value = t.getValue();
|
|
496
|
+
if (!lodash_1.default.isUndefined(value)) {
|
|
497
|
+
operands.push(new RegoTerm(typeof value, value, this.parser));
|
|
498
|
+
}
|
|
499
|
+
else
|
|
500
|
+
operands.push(t);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
if (!operator) {
|
|
504
|
+
throw new Error(`Invalid 3 terms rego expression, can't locate operator: ${this.termsAsString()}`);
|
|
505
|
+
}
|
|
506
|
+
if (operands.length !== 2) {
|
|
507
|
+
throw new Error(`Invalid 3 terms rego expression, the number of operands should be 2: ${this.termsAsString()}`);
|
|
508
|
+
}
|
|
509
|
+
return [operator, operands];
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Try to evaluate the expression
|
|
513
|
+
*
|
|
514
|
+
* @returns
|
|
515
|
+
* @memberof RegoExp
|
|
516
|
+
*/
|
|
517
|
+
evaluate() {
|
|
518
|
+
if (this.terms.length === 0) {
|
|
519
|
+
// --- exp should be considered as matched (true)
|
|
520
|
+
// --- unless isNegated is true
|
|
521
|
+
// --- will try to normalise isNegated here
|
|
522
|
+
this.isCompleteEvaluated = true;
|
|
523
|
+
this.value = this.isNegated ? false : true;
|
|
524
|
+
this.isNegated = false;
|
|
525
|
+
}
|
|
526
|
+
if (this.terms.length === 1) {
|
|
527
|
+
const term = this.terms[0];
|
|
528
|
+
const value = term.getValue();
|
|
529
|
+
if (lodash_1.default.isUndefined(value))
|
|
530
|
+
return this;
|
|
531
|
+
this.value = value;
|
|
532
|
+
this.isCompleteEvaluated = true;
|
|
533
|
+
return this;
|
|
534
|
+
}
|
|
535
|
+
else if (this.terms.length === 3) {
|
|
536
|
+
// --- 3 terms expression e.g. true == true or x >= 3
|
|
537
|
+
// --- we only evalute some redundant expression e.g. true == true or false != true
|
|
538
|
+
const [operator, operands] = this.toOperatorOperandsArray();
|
|
539
|
+
if (operands.findIndex((op) => op.isRef()) !== -1) {
|
|
540
|
+
// --- this expression involve unknown no need to evalute
|
|
541
|
+
return this;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
const operandsValues = operands.map((op) => op.getValue());
|
|
545
|
+
let value = null;
|
|
546
|
+
switch (operator) {
|
|
547
|
+
case "=":
|
|
548
|
+
value = operandsValues[0] === operandsValues[1];
|
|
549
|
+
break;
|
|
550
|
+
case ">":
|
|
551
|
+
value = operandsValues[0] > operandsValues[1];
|
|
552
|
+
break;
|
|
553
|
+
case "<":
|
|
554
|
+
value = operandsValues[0] < operandsValues[1];
|
|
555
|
+
break;
|
|
556
|
+
case ">=":
|
|
557
|
+
value = operandsValues[0] >= operandsValues[1];
|
|
558
|
+
break;
|
|
559
|
+
case "<=":
|
|
560
|
+
value = operandsValues[0] <= operandsValues[1];
|
|
561
|
+
break;
|
|
562
|
+
case "!=":
|
|
563
|
+
value = operandsValues[0] != operandsValues[1];
|
|
564
|
+
break;
|
|
565
|
+
default:
|
|
566
|
+
throw new Error(`Invalid 3 terms rego expression, Unknown operator "${operator}": ${this.termsAsString()}`);
|
|
567
|
+
}
|
|
568
|
+
this.isCompleteEvaluated = true;
|
|
569
|
+
this.value = value;
|
|
570
|
+
return this;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
throw new Error(`Invalid ${this.terms.length} terms rego expression: ${this.termsAsString()}`);
|
|
575
|
+
}
|
|
576
|
+
// --- so far there is no 2 terms expression e.g. ! x
|
|
577
|
+
// --- builtin function should never be included in residual rule
|
|
578
|
+
// --- as we won't apply them on unknowns
|
|
579
|
+
return this;
|
|
580
|
+
}
|
|
581
|
+
static parseFromData(expData, parser) {
|
|
582
|
+
const isNegated = expData.negated === true;
|
|
583
|
+
if (lodash_1.default.isEmpty(expData.terms)) {
|
|
584
|
+
if (isNegated)
|
|
585
|
+
throw new Error("Invalid negated empty term!");
|
|
586
|
+
return new RegoExp([], isNegated, false, null, parser);
|
|
587
|
+
}
|
|
588
|
+
let termsData = [];
|
|
589
|
+
if (lodash_1.default.isArray(expData.terms)) {
|
|
590
|
+
termsData = expData.terms;
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
termsData.push(expData.terms);
|
|
594
|
+
}
|
|
595
|
+
const terms = termsData.map((termData) => RegoTerm.parseFromData(termData, parser));
|
|
596
|
+
const exp = new RegoExp(terms, isNegated, false, null, parser);
|
|
597
|
+
exp.evaluate();
|
|
598
|
+
return exp;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
exports.RegoExp = RegoExp;
|
|
602
|
+
/**
|
|
603
|
+
* Represents a special Rego Term type: reference term
|
|
604
|
+
* You shouldn't use this class directly
|
|
605
|
+
*
|
|
606
|
+
* @export
|
|
607
|
+
* @class RegoRef
|
|
608
|
+
*/
|
|
609
|
+
class RegoRef {
|
|
610
|
+
constructor(parts) {
|
|
611
|
+
this.parts = parts;
|
|
612
|
+
}
|
|
613
|
+
clone() {
|
|
614
|
+
return new RegoRef(this.parts.map((p) => (Object.assign({}, p))));
|
|
615
|
+
}
|
|
616
|
+
static parseFromData(data) {
|
|
617
|
+
if (data.type === "ref") {
|
|
618
|
+
return new RegoRef(data.value);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
static convertToFullRefString(parts) {
|
|
625
|
+
return new RegoRef(parts).fullRefString();
|
|
626
|
+
}
|
|
627
|
+
removeAllPrefixs(str, removalPrefixs = []) {
|
|
628
|
+
if (!removalPrefixs.length)
|
|
629
|
+
return str;
|
|
630
|
+
let result = str;
|
|
631
|
+
removalPrefixs
|
|
632
|
+
// --- starts from longest prefix
|
|
633
|
+
.sort((a, b) => b.length - a.length)
|
|
634
|
+
.forEach((prefix) => {
|
|
635
|
+
if (!prefix)
|
|
636
|
+
return;
|
|
637
|
+
const idx = result.indexOf(prefix);
|
|
638
|
+
if (idx !== 0)
|
|
639
|
+
return;
|
|
640
|
+
result = result.substring(prefix.length);
|
|
641
|
+
});
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
fullRefString(removalPrefixs = []) {
|
|
645
|
+
let isFirstPart = true;
|
|
646
|
+
const str = this.parts
|
|
647
|
+
.map((part) => {
|
|
648
|
+
let partStr = "";
|
|
649
|
+
if (isFirstPart) {
|
|
650
|
+
partStr = part.value;
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
if (part.type == "var") {
|
|
654
|
+
// --- it's a collection lookup
|
|
655
|
+
// --- var name doesn't matter for most cases
|
|
656
|
+
partStr = "[_]";
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
partStr = part.value;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (isFirstPart)
|
|
663
|
+
isFirstPart = false;
|
|
664
|
+
return partStr;
|
|
665
|
+
//--- a.[_].[_] should be a[_][_]
|
|
666
|
+
})
|
|
667
|
+
.join(".")
|
|
668
|
+
.replace(/\.\[/g, "[");
|
|
669
|
+
return this.removeAllPrefixs(str, removalPrefixs);
|
|
670
|
+
}
|
|
671
|
+
refString(removalPrefixs = []) {
|
|
672
|
+
return this.fullRefString(removalPrefixs).replace("\\[_\\]$", "");
|
|
673
|
+
}
|
|
674
|
+
asCollectionRefs(removalPrefixs = []) {
|
|
675
|
+
return this.fullRefString(removalPrefixs)
|
|
676
|
+
.split("[_]")
|
|
677
|
+
.map((refStr) => refStr.replace(/^\./, ""));
|
|
678
|
+
}
|
|
679
|
+
isOperator() {
|
|
680
|
+
return Object.keys(exports.RegoOperators).indexOf(this.fullRefString()) !== -1;
|
|
681
|
+
}
|
|
682
|
+
// --- the first var type won't count as collection lookup
|
|
683
|
+
hasCollectionLookup() {
|
|
684
|
+
if (this.parts.length <= 1)
|
|
685
|
+
return false;
|
|
686
|
+
else {
|
|
687
|
+
return (this.parts.slice(1).findIndex((part) => part.type === "var") !==
|
|
688
|
+
-1);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// -- simple collection only contains 1 level lookup
|
|
692
|
+
// -- we don't need Nested Query to handle it
|
|
693
|
+
isSimpleCollectionLookup() {
|
|
694
|
+
if (this.parts.length <= 1)
|
|
695
|
+
return false;
|
|
696
|
+
else {
|
|
697
|
+
return (this.parts.slice(1).findIndex((part) => part.type === "var") ===
|
|
698
|
+
this.parts.length - 2);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// --- see comment for same name method of RegoTerm
|
|
702
|
+
isResolveAsCollectionValue() {
|
|
703
|
+
const refString = this.fullRefString();
|
|
704
|
+
return refString.lastIndexOf("[_]") === refString.length - 3;
|
|
705
|
+
}
|
|
706
|
+
asOperator() {
|
|
707
|
+
if (this.isOperator())
|
|
708
|
+
return exports.RegoOperators[this.fullRefString()];
|
|
709
|
+
else
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
exports.RegoRef = RegoRef;
|
|
714
|
+
function value2String(value) {
|
|
715
|
+
if (lodash_1.default.isBoolean(value) || lodash_1.default.isNumber(value))
|
|
716
|
+
return value.toString();
|
|
717
|
+
else
|
|
718
|
+
return JSON.stringify(value);
|
|
719
|
+
}
|
|
720
|
+
exports.value2String = value2String;
|
|
721
|
+
/**
|
|
722
|
+
* OPA result Parser
|
|
723
|
+
*
|
|
724
|
+
* @export
|
|
725
|
+
* @class OpaCompileResponseParser
|
|
726
|
+
*/
|
|
727
|
+
class OpaCompileResponseParser {
|
|
728
|
+
constructor() {
|
|
729
|
+
/**
|
|
730
|
+
* If a warning is produced during the parsing
|
|
731
|
+
*
|
|
732
|
+
* @type {boolean}
|
|
733
|
+
* @memberof OpaCompileResponseParser
|
|
734
|
+
*/
|
|
735
|
+
this.hasWarns = false;
|
|
736
|
+
/**
|
|
737
|
+
* Any warnings produced during the parsing
|
|
738
|
+
*
|
|
739
|
+
* @type {string[]}
|
|
740
|
+
* @memberof OpaCompileResponseParser
|
|
741
|
+
*/
|
|
742
|
+
this.warns = [];
|
|
743
|
+
this.data = null;
|
|
744
|
+
/**
|
|
745
|
+
* Inital Rules parsed from result
|
|
746
|
+
* Only for debug purpose
|
|
747
|
+
*
|
|
748
|
+
* @type {RegoRule[]}
|
|
749
|
+
* @memberof OpaCompileResponseParser
|
|
750
|
+
*/
|
|
751
|
+
this.originalRules = [];
|
|
752
|
+
/**
|
|
753
|
+
* Parsed, compressed & evaluated rules
|
|
754
|
+
*
|
|
755
|
+
* @type {RegoRule[]}
|
|
756
|
+
* @memberof OpaCompileResponseParser
|
|
757
|
+
*/
|
|
758
|
+
this.rules = [];
|
|
759
|
+
this.queries = [];
|
|
760
|
+
/**
|
|
761
|
+
* A cache of all resolved rule result
|
|
762
|
+
*
|
|
763
|
+
* @type {{
|
|
764
|
+
* [fullName: string]: CompleteRuleResult;
|
|
765
|
+
* }}
|
|
766
|
+
* @memberof OpaCompileResponseParser
|
|
767
|
+
*/
|
|
768
|
+
this.completeRuleResults = {};
|
|
769
|
+
/**
|
|
770
|
+
* The pseudo query rule name
|
|
771
|
+
* The parser will assign a random pseudo rule name to the query expressions you submit.
|
|
772
|
+
*
|
|
773
|
+
* @type {string}
|
|
774
|
+
* @memberof OpaCompileResponseParser
|
|
775
|
+
*/
|
|
776
|
+
this.pseudoQueryRuleName = RegoRule.randomRuleName("default_rule_");
|
|
777
|
+
}
|
|
778
|
+
setQueryRuleResult(val) {
|
|
779
|
+
this.completeRuleResults[this.pseudoQueryRuleName] = {
|
|
780
|
+
fullName: this.pseudoQueryRuleName,
|
|
781
|
+
name: this.pseudoQueryRuleName,
|
|
782
|
+
value: val,
|
|
783
|
+
isCompleteEvaluated: true
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Parse OPA result Response
|
|
788
|
+
*
|
|
789
|
+
* @param {*} json
|
|
790
|
+
* @returns {RegoRule[]}
|
|
791
|
+
* @memberof OpaCompileResponseParser
|
|
792
|
+
*/
|
|
793
|
+
parse(json) {
|
|
794
|
+
if (lodash_1.default.isString(json)) {
|
|
795
|
+
this.data = JSON.parse(json);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
this.data = json;
|
|
799
|
+
}
|
|
800
|
+
if (!this.data.result) {
|
|
801
|
+
// --- mean no rule matched
|
|
802
|
+
this.setQueryRuleResult(false);
|
|
803
|
+
return [];
|
|
804
|
+
}
|
|
805
|
+
this.data = this.data.result;
|
|
806
|
+
if ((!this.data.queries ||
|
|
807
|
+
!lodash_1.default.isArray(this.data.queries) ||
|
|
808
|
+
!this.data.queries.length) &&
|
|
809
|
+
(!lodash_1.default.isArray(this.data.support) || !this.data.support.length)) {
|
|
810
|
+
// --- mean no rule matched
|
|
811
|
+
this.setQueryRuleResult(false);
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
const queries = this.data.queries;
|
|
815
|
+
if (queries) {
|
|
816
|
+
if (queries.findIndex((ruleBody) => !ruleBody || !ruleBody.length) !== -1) {
|
|
817
|
+
// --- there is an empty array --- indicates unconditional matched
|
|
818
|
+
this.setQueryRuleResult(true);
|
|
819
|
+
return [];
|
|
820
|
+
}
|
|
821
|
+
queries.forEach((ruleBody, i) => {
|
|
822
|
+
const rule = new RegoRule({
|
|
823
|
+
name: this.pseudoQueryRuleName,
|
|
824
|
+
fullName: this.pseudoQueryRuleName,
|
|
825
|
+
expressions: RegoRule.createExpressionsFromRuleBodyData(ruleBody, this),
|
|
826
|
+
isDefault: false,
|
|
827
|
+
isCompleteEvaluated: false,
|
|
828
|
+
value: true,
|
|
829
|
+
parser: this
|
|
830
|
+
});
|
|
831
|
+
rule.evaluate();
|
|
832
|
+
this.originalRules.push(rule);
|
|
833
|
+
this.rules.push(rule);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
const packages = this.data.support;
|
|
837
|
+
if (packages) {
|
|
838
|
+
packages.forEach((p) => {
|
|
839
|
+
if (!lodash_1.default.isArray(p.rules) || !p.rules.length)
|
|
840
|
+
return;
|
|
841
|
+
const packageName = p.package && lodash_1.default.isArray(p.package.path)
|
|
842
|
+
? RegoRef.convertToFullRefString(p.package.path)
|
|
843
|
+
: "";
|
|
844
|
+
const rules = p.rules;
|
|
845
|
+
rules.forEach((r) => {
|
|
846
|
+
const regoRule = RegoRule.parseFromData(r, packageName, this);
|
|
847
|
+
this.originalRules.push(regoRule);
|
|
848
|
+
// --- only save matched rules
|
|
849
|
+
if (!regoRule.isCompleteEvaluated) {
|
|
850
|
+
this.rules.push(regoRule);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
if (regoRule.isMatched) {
|
|
854
|
+
this.rules.push(regoRule);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
this.calculateCompleteRuleResult();
|
|
861
|
+
this.reduceDependencies();
|
|
862
|
+
return this.rules;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Tried to merge rules outcome so that the ref value can be established easier
|
|
866
|
+
* After this step, any rules doesn't involve unknown should be merged to one value
|
|
867
|
+
* This will help to generate more concise query later.
|
|
868
|
+
* `CompleteRule` rule involves no `unknowns`
|
|
869
|
+
*
|
|
870
|
+
* Only for internal usage
|
|
871
|
+
*
|
|
872
|
+
* @private
|
|
873
|
+
* @memberof OpaCompileResponseParser
|
|
874
|
+
*/
|
|
875
|
+
calculateCompleteRuleResult() {
|
|
876
|
+
const fullNames = this.rules.map((r) => r.fullName);
|
|
877
|
+
fullNames.forEach((fullName) => {
|
|
878
|
+
const rules = this.rules.filter((r) => r.fullName === fullName);
|
|
879
|
+
const nonCompletedRules = rules.filter((r) => !r.isCompleteEvaluated);
|
|
880
|
+
const completedRules = rules.filter((r) => r.isCompleteEvaluated);
|
|
881
|
+
const defaultRules = completedRules.filter((r) => r.isDefault);
|
|
882
|
+
const nonDefaultRules = completedRules.filter((r) => !r.isDefault);
|
|
883
|
+
if (nonDefaultRules.length) {
|
|
884
|
+
// --- if a non default complete eveluated rules exist
|
|
885
|
+
// --- it will be the final outcome
|
|
886
|
+
this.completeRuleResults[fullName] = this.createCompleteRuleResult(nonDefaultRules[0]);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (!nonCompletedRules.length) {
|
|
890
|
+
// --- if no unevaluated rule left, default rule value should be used
|
|
891
|
+
if (defaultRules.length) {
|
|
892
|
+
this.completeRuleResults[fullName] = this.createCompleteRuleResult(defaultRules[0]);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
// --- no matched complete non default rule left; Not possible
|
|
897
|
+
throw new Error(`Unexpected empty rule result for ${fullName}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
// --- do nothing
|
|
902
|
+
// --- Some defaultRules might be able to strip out once
|
|
903
|
+
// --- nonCompleteRules are determined later
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Only for internal usage
|
|
910
|
+
*
|
|
911
|
+
* @returns
|
|
912
|
+
* @private
|
|
913
|
+
* @memberof OpaCompileResponseParser
|
|
914
|
+
*/
|
|
915
|
+
reduceDependencies() {
|
|
916
|
+
const rules = this.rules.filter((r) => !r.isCompleteEvaluated);
|
|
917
|
+
if (!rules.length)
|
|
918
|
+
return;
|
|
919
|
+
for (let i = 0; i < rules.length; i++) {
|
|
920
|
+
const rule = rules[i];
|
|
921
|
+
rule.expressions = rule.expressions.map((e) => e.evaluate());
|
|
922
|
+
rule.evaluate();
|
|
923
|
+
}
|
|
924
|
+
// --- unmatched non-default rule can be stripped out
|
|
925
|
+
this.rules = this.rules.filter((r) => !(r.isCompleteEvaluated && !r.isMatched && !r.isDefault));
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Call to evaluate a rule
|
|
929
|
+
*
|
|
930
|
+
* @param {string} fullName
|
|
931
|
+
* @returns {CompleteRuleResult}
|
|
932
|
+
* @memberof OpaCompileResponseParser
|
|
933
|
+
*/
|
|
934
|
+
evaluateRule(fullName) {
|
|
935
|
+
var _a, _b, _c;
|
|
936
|
+
if ((_b = (_a = this.completeRuleResults) === null || _a === void 0 ? void 0 : _a[fullName]) === null || _b === void 0 ? void 0 : _b.isCompleteEvaluated) {
|
|
937
|
+
// --- already evaluated during paring or dependencies removal
|
|
938
|
+
return (_c = this.completeRuleResults) === null || _c === void 0 ? void 0 : _c[fullName];
|
|
939
|
+
}
|
|
940
|
+
let rules = this.rules.filter((r) => r.fullName === fullName);
|
|
941
|
+
const originalRuleName = rules[0].name;
|
|
942
|
+
if (!rules.length) {
|
|
943
|
+
// --- no any rule matched; often (depends on your policy) it means a overall non-matched (false)
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
const defaultRule = rules.find((r) => r.isDefault);
|
|
947
|
+
const defaultValue = lodash_1.default.isUndefined(defaultRule)
|
|
948
|
+
? undefined
|
|
949
|
+
: defaultRule.value;
|
|
950
|
+
if (rules.find((r) => r.isCompleteEvaluated))
|
|
951
|
+
// --- filter out default rules & unmatched
|
|
952
|
+
// --- isMatch is only set when r.isCompleteEvaluated = true
|
|
953
|
+
rules = rules.filter((r) => !(r.isDefault || (r.isCompleteEvaluated && !r.isMatched)));
|
|
954
|
+
if (!rules.length) {
|
|
955
|
+
return {
|
|
956
|
+
fullName,
|
|
957
|
+
name: defaultRule ? defaultRule.name : "",
|
|
958
|
+
value: defaultValue,
|
|
959
|
+
isCompleteEvaluated: true,
|
|
960
|
+
residualRules: []
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
const matchedRule = rules.find((r) => r.isMatched);
|
|
965
|
+
if (matchedRule) {
|
|
966
|
+
return {
|
|
967
|
+
fullName,
|
|
968
|
+
name: originalRuleName,
|
|
969
|
+
value: matchedRule.value,
|
|
970
|
+
isCompleteEvaluated: true,
|
|
971
|
+
residualRules: []
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
const ruleWithEmptyExps = rules.find((r) => !r.expressions.length);
|
|
975
|
+
if (ruleWithEmptyExps) {
|
|
976
|
+
// empty exp / body means unconditional match
|
|
977
|
+
return {
|
|
978
|
+
fullName,
|
|
979
|
+
name: originalRuleName,
|
|
980
|
+
value: ruleWithEmptyExps.value,
|
|
981
|
+
isCompleteEvaluated: true,
|
|
982
|
+
residualRules: []
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
if (rules.length === 1 && rules[0].expressions.length === 1) {
|
|
986
|
+
rules[0].expressions[0].terms.length === 1;
|
|
987
|
+
}
|
|
988
|
+
// if a rules contains one expression only, we will try to resolve any possible rule ref
|
|
989
|
+
rules = lodash_1.default.flatMap(rules, (rule) => {
|
|
990
|
+
if (rules.length === 1 && rules[0].expressions.length === 1) {
|
|
991
|
+
const exp = rules[0].expressions[0];
|
|
992
|
+
if (exp.terms.length === 1 && exp.terms[0].isRef()) {
|
|
993
|
+
const ruleRef = exp.terms[0].fullRefString();
|
|
994
|
+
const result = this.evaluateRule(ruleRef);
|
|
995
|
+
if (result) {
|
|
996
|
+
if (result.isCompleteEvaluated) {
|
|
997
|
+
return [
|
|
998
|
+
RegoRule.createFromValue(result.value, this)
|
|
999
|
+
];
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
return result.residualRules;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
return [rule];
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
else if (exp.terms.length === 3) {
|
|
1010
|
+
const [opStr, [op1, op2]] = exp.toOperatorOperandsArray();
|
|
1011
|
+
if (opStr === "=" &&
|
|
1012
|
+
((op1.isRef() && typeof op2.value === "boolean") ||
|
|
1013
|
+
(op2.isRef() && typeof op1.value === "boolean"))) {
|
|
1014
|
+
const ruleRef = op1.isRef()
|
|
1015
|
+
? op1.fullRefString()
|
|
1016
|
+
: op2.fullRefString();
|
|
1017
|
+
const bVal = typeof op1.value === "boolean"
|
|
1018
|
+
? op1.value
|
|
1019
|
+
: op2.value;
|
|
1020
|
+
const result = this.evaluateRule(ruleRef);
|
|
1021
|
+
if (result) {
|
|
1022
|
+
if (result.isCompleteEvaluated) {
|
|
1023
|
+
if (bVal === false) {
|
|
1024
|
+
return [
|
|
1025
|
+
RegoRule.createFromValue(!result.value, this)
|
|
1026
|
+
];
|
|
1027
|
+
}
|
|
1028
|
+
else {
|
|
1029
|
+
return [
|
|
1030
|
+
RegoRule.createFromValue(result.value, this)
|
|
1031
|
+
];
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
if (bVal === false) {
|
|
1036
|
+
return result.residualRules.map((r) => r.clone({ value: !r.value }));
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
return result.residualRules;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
return [rule];
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
return [rule];
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
return [rule];
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
return [rule];
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
return {
|
|
1060
|
+
fullName,
|
|
1061
|
+
name: rules[0].name,
|
|
1062
|
+
value: undefined,
|
|
1063
|
+
isCompleteEvaluated: false,
|
|
1064
|
+
residualRules: rules
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Shortcut to evalute query result directly
|
|
1070
|
+
*
|
|
1071
|
+
* @returns {CompleteRuleResult}
|
|
1072
|
+
* @memberof OpaCompileResponseParser
|
|
1073
|
+
*/
|
|
1074
|
+
evaluate() {
|
|
1075
|
+
return this.evaluateRule(this.pseudoQueryRuleName);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* evaluate a rule and returned as human readable string
|
|
1079
|
+
*
|
|
1080
|
+
* @param {string} fullName
|
|
1081
|
+
* @returns {string}
|
|
1082
|
+
* @memberof OpaCompileResponseParser
|
|
1083
|
+
*/
|
|
1084
|
+
evaluateRuleAsHumanReadableString(fullName) {
|
|
1085
|
+
const result = this.evaluateRule(fullName);
|
|
1086
|
+
if (result === null)
|
|
1087
|
+
return "null";
|
|
1088
|
+
if (result.isCompleteEvaluated) {
|
|
1089
|
+
return value2String(result.value);
|
|
1090
|
+
}
|
|
1091
|
+
let parts = result.residualRules.map((r) => r.toHumanReadableString());
|
|
1092
|
+
if (parts.length > 1) {
|
|
1093
|
+
parts = parts.map((p) => `( ${p} )`);
|
|
1094
|
+
}
|
|
1095
|
+
return parts.join("\nOR\n");
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Shortcut to evalute query result directly and returned as human readable string
|
|
1099
|
+
*
|
|
1100
|
+
* @returns {string}
|
|
1101
|
+
* @memberof OpaCompileResponseParser
|
|
1102
|
+
*/
|
|
1103
|
+
evaluateAsHumanReadableString() {
|
|
1104
|
+
return this.evaluateRuleAsHumanReadableString(this.pseudoQueryRuleName);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Only for internal usage
|
|
1108
|
+
*
|
|
1109
|
+
* @param {RegoRule} rule
|
|
1110
|
+
* @returns {CompleteRuleResult}
|
|
1111
|
+
* @private
|
|
1112
|
+
* @memberof OpaCompileResponseParser
|
|
1113
|
+
*/
|
|
1114
|
+
createCompleteRuleResult(rule) {
|
|
1115
|
+
return {
|
|
1116
|
+
fullName: rule.fullName,
|
|
1117
|
+
name: rule.name,
|
|
1118
|
+
value: rule.value,
|
|
1119
|
+
isCompleteEvaluated: true,
|
|
1120
|
+
residualRules: []
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
reportWarns(msg) {
|
|
1124
|
+
this.warns.push(msg);
|
|
1125
|
+
this.hasWarns = true;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
exports.default = OpaCompileResponseParser;
|
|
1129
|
+
function unknown2Ref(unknown) {
|
|
1130
|
+
const prefix = unknown.replace(/^input\./, "");
|
|
1131
|
+
return `data.partial.${prefix}`;
|
|
1132
|
+
}
|
|
1133
|
+
exports.unknown2Ref = unknown2Ref;
|
|
1134
|
+
//# sourceMappingURL=OpaCompileResponseParser.js.map
|