@magda/typescript-common 1.2.1 → 2.0.0-alpha.1
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/OpaCompileResponseParser.d.ts +260 -36
- package/dist/OpaCompileResponseParser.js +697 -297
- package/dist/OpaCompileResponseParser.js.map +1 -1
- package/dist/SQLUtils.d.ts +70 -0
- package/dist/SQLUtils.js +263 -0
- package/dist/SQLUtils.js.map +1 -0
- package/dist/ServerError.d.ts +9 -0
- package/dist/ServerError.js +17 -0
- package/dist/ServerError.js.map +1 -0
- package/dist/authorization-api/authMiddleware.d.ts +60 -1
- package/dist/authorization-api/authMiddleware.js +155 -3
- package/dist/authorization-api/authMiddleware.js.map +1 -1
- package/dist/authorization-api/constants.d.ts +5 -0
- package/dist/authorization-api/constants.js +13 -0
- package/dist/authorization-api/constants.js.map +1 -0
- package/dist/authorization-api/model.d.ts +2 -7
- package/dist/express/getNoCacheHeaders.d.ts +6 -0
- package/dist/express/getNoCacheHeaders.js +9 -0
- package/dist/express/getNoCacheHeaders.js.map +1 -0
- package/dist/express/setResponseNoCache.d.ts +3 -0
- package/dist/express/setResponseNoCache.js +9 -0
- package/dist/express/setResponseNoCache.js.map +1 -0
- package/dist/generated/registry/api.d.ts +47 -2
- package/dist/generated/registry/api.js +197 -2
- package/dist/generated/registry/api.js.map +1 -1
- package/dist/getAbsoluteUrl.d.ts +3 -2
- package/dist/getAbsoluteUrl.js +2 -1
- package/dist/getAbsoluteUrl.js.map +1 -1
- package/dist/getStorageUrl.d.ts +34 -0
- package/dist/getStorageUrl.js +138 -0
- package/dist/getStorageUrl.js.map +1 -0
- package/dist/handleServerError.d.ts +2 -0
- package/dist/handleServerError.js +17 -0
- package/dist/handleServerError.js.map +1 -0
- package/dist/opa/AspectQuery.d.ts +71 -0
- package/dist/opa/AspectQuery.js +219 -0
- package/dist/opa/AspectQuery.js.map +1 -0
- package/dist/opa/AuthDecision.d.ts +51 -0
- package/dist/opa/AuthDecision.js +241 -0
- package/dist/opa/AuthDecision.js.map +1 -0
- package/dist/opa/AuthDecisionQueryClient.d.ts +23 -0
- package/dist/opa/AuthDecisionQueryClient.js +113 -0
- package/dist/opa/AuthDecisionQueryClient.js.map +1 -0
- package/dist/pgTypes.d.ts +1 -0
- package/dist/pgTypes.js +18 -0
- package/dist/pgTypes.js.map +1 -0
- package/dist/registry/AuthorizedRegistryClient.d.ts +1 -0
- package/dist/registry/AuthorizedRegistryClient.js +17 -0
- package/dist/registry/AuthorizedRegistryClient.js.map +1 -1
- package/dist/registry/RegistryClient.d.ts +11 -0
- package/dist/registry/RegistryClient.js +46 -0
- package/dist/registry/RegistryClient.js.map +1 -1
- package/dist/test/getAuthDecision.spec.js +2 -2
- package/dist/test/getAuthDecision.spec.js.map +1 -1
- package/dist/test/getStorageUrl.spec.d.ts +1 -0
- package/dist/test/getStorageUrl.spec.js +95 -0
- package/dist/test/getStorageUrl.spec.js.map +1 -0
- package/dist/test/sampleAuthDecisions/content.json +29 -0
- package/dist/test/sampleAuthDecisions/datasetPermissionWithOrgUnitConstraint.json +79 -0
- package/dist/test/sampleAuthDecisions/extraLargeResponse.json +2519 -0
- package/dist/test/sampleAuthDecisions/simple.json +29 -0
- package/dist/test/sampleAuthDecisions/singleTermAspectRef.json +39 -0
- package/dist/test/sampleAuthDecisions/unconditionalFalseSimple.json +6 -0
- package/dist/test/sampleAuthDecisions/unconditionalNotMacthed.json +6 -0
- package/dist/test/sampleAuthDecisions/unconditionalNotMacthedWithExtraRefs.json +6 -0
- package/dist/test/sampleAuthDecisions/unconditionalTrue.json +6 -0
- package/dist/test/sampleAuthDecisions/unconditionalTrueSimple.json +6 -0
- package/dist/test/sampleAuthDecisions/unconditionalTrueWithDefaultRule.json +6 -0
- package/dist/test/sampleAuthDecisions/withDefaultRule.json +6 -0
- package/dist/test/{sampleOpaResponse.json → sampleOpaResponses/content.json} +0 -0
- package/dist/test/sampleOpaResponses/datasetPermissionWithOrgUnitConstraint.json +341 -0
- package/dist/test/sampleOpaResponses/extraLargeResponse.json +104869 -0
- package/dist/test/{sampleOpaResponseSimple.json → sampleOpaResponses/simple.json} +0 -0
- package/dist/test/sampleOpaResponses/singleTermAspectRef.json +233 -0
- package/dist/test/sampleOpaResponses/unconditionalFalseSimple.json +3 -0
- package/dist/test/sampleOpaResponses/unconditionalNotMacthed.json +73 -0
- package/dist/test/sampleOpaResponses/unconditionalNotMacthedWithExtraRefs.json +155 -0
- package/dist/test/{sampleOpaResponseUnconditionalTrue.json → sampleOpaResponses/unconditionalTrue.json} +0 -0
- package/dist/test/sampleOpaResponses/unconditionalTrueSimple.json +48 -0
- package/dist/test/{sampleOpaResponseUnconditionalTrueWithDefaultRule.json → sampleOpaResponses/unconditionalTrueWithDefaultRule.json} +0 -0
- package/dist/test/{sampleOpaResponseWithDefaultRule.json → sampleOpaResponses/withDefaultRule.json} +0 -0
- package/dist/test/testOpaCompileResponseParser.spec.js +212 -20
- package/dist/test/testOpaCompileResponseParser.spec.js.map +1 -1
- package/package.json +9 -3
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
2
13
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
15
|
};
|
|
5
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.unknown2Ref = exports.value2String = exports.RegoRef = exports.RegoExp = exports.RegoTerm = exports.RegoOperators = exports.RegoRule = void 0;
|
|
17
|
+
exports.unknown2Ref = exports.RegoRuleSet = exports.value2String = exports.RegoRef = exports.RegoExp = exports.RegoTerm = exports.RegoOperators = exports.RegoRule = void 0;
|
|
7
18
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
19
|
/**
|
|
9
20
|
* @class RegoRule
|
|
@@ -19,6 +30,18 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
19
30
|
*/
|
|
20
31
|
class RegoRule {
|
|
21
32
|
constructor(options) {
|
|
33
|
+
var _a;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the rule contains any expressions that has any resolvable references.
|
|
36
|
+
* reference start with `input.` should be considered as non-resolvable in context of partial evaluation.
|
|
37
|
+
* When this field is set to `true`, we should not attempt to evaluate this expression.
|
|
38
|
+
* i.e. evaluate() method should return immediately.
|
|
39
|
+
* This will speed up evaluation process.
|
|
40
|
+
*
|
|
41
|
+
* @type {boolean}
|
|
42
|
+
* @memberof RegoRule
|
|
43
|
+
*/
|
|
44
|
+
this.hasNoResolvableRef = false;
|
|
22
45
|
this.isCompleteEvaluated = false;
|
|
23
46
|
this.name = lodash_1.default.isString(options.name) ? options.name : "";
|
|
24
47
|
this.fullName = lodash_1.default.isString(options.fullName) ? options.fullName : "";
|
|
@@ -42,6 +65,68 @@ class RegoRule {
|
|
|
42
65
|
if (!(this.parser instanceof OpaCompileResponseParser)) {
|
|
43
66
|
throw new Error("Require parser parameter to create a RegoRule");
|
|
44
67
|
}
|
|
68
|
+
this.removeDuplicateExpressions();
|
|
69
|
+
if (((_a = this.expressions) === null || _a === void 0 ? void 0 : _a.length) &&
|
|
70
|
+
this.expressions.findIndex((exp) => !exp.hasNoResolvableRef) === -1) {
|
|
71
|
+
this.hasNoResolvableRef = true;
|
|
72
|
+
}
|
|
73
|
+
this.evaluate();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* OPA PE result might contain duplicate expressions.
|
|
77
|
+
* https://github.com/open-policy-agent/opa/issues/4516
|
|
78
|
+
* This method will remove those duplication by simply string comparison.
|
|
79
|
+
*
|
|
80
|
+
* @return {*}
|
|
81
|
+
* @memberof RegoRule
|
|
82
|
+
*/
|
|
83
|
+
removeDuplicateExpressions() {
|
|
84
|
+
var _a;
|
|
85
|
+
if (!((_a = this === null || this === void 0 ? void 0 : this.expressions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const expSet = new Set();
|
|
89
|
+
this.expressions = this.expressions.filter((exp) => {
|
|
90
|
+
const orgSetLength = expSet.size;
|
|
91
|
+
// exp.toJSON() is faster than exp.toConciseJSON()
|
|
92
|
+
expSet.add(exp.toJSON(0, true));
|
|
93
|
+
return expSet.size > orgSetLength;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Test whether the rule is an "impossible" rule.
|
|
98
|
+
* If so, the rule should be simply discarded.
|
|
99
|
+
* See: https://github.com/open-policy-agent/opa/issues/4516
|
|
100
|
+
*
|
|
101
|
+
* @return {*} {boolean}
|
|
102
|
+
* @memberof RegoRule
|
|
103
|
+
*/
|
|
104
|
+
isImpossible() {
|
|
105
|
+
var _a;
|
|
106
|
+
if (!((_a = this === null || this === void 0 ? void 0 : this.expressions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const nonNegatedExpSet = new Set();
|
|
110
|
+
const negatedExps = this.expressions.filter((exp) => {
|
|
111
|
+
if (exp.isNegated) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// add non negated exp content to set
|
|
116
|
+
// exp.toJSON() is faster than exp.toConciseJSON()
|
|
117
|
+
nonNegatedExpSet.add(exp.toJSON(0, true));
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (!negatedExps.length) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
for (const exp of negatedExps) {
|
|
125
|
+
if (nonNegatedExpSet.has(exp.toJSON(0, true, true))) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
45
130
|
}
|
|
46
131
|
clone(options = {}) {
|
|
47
132
|
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));
|
|
@@ -56,30 +141,56 @@ class RegoRule {
|
|
|
56
141
|
* @memberof RegoRule
|
|
57
142
|
*/
|
|
58
143
|
evaluate() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
144
|
+
var _a;
|
|
145
|
+
if (this.hasNoResolvableRef) {
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
if (this.isCompleteEvaluated) {
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
if (!((_a = this === null || this === void 0 ? void 0 : this.expressions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
152
|
+
// a rule with empty body / no expression is matched
|
|
64
153
|
this.isCompleteEvaluated = true;
|
|
65
|
-
this.isMatched =
|
|
154
|
+
this.isMatched = true;
|
|
155
|
+
return this;
|
|
66
156
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.isMatched = true;
|
|
157
|
+
let unresolvable = false;
|
|
158
|
+
for (let i = 0; i < this.expressions.length; i++) {
|
|
159
|
+
const exp = this.expressions[i];
|
|
160
|
+
exp.evaluate();
|
|
161
|
+
if (!exp.isResolvable()) {
|
|
162
|
+
unresolvable = true;
|
|
163
|
+
continue;
|
|
75
164
|
}
|
|
76
|
-
|
|
77
|
-
// ---
|
|
78
|
-
// ---
|
|
79
|
-
this.
|
|
165
|
+
if (!exp.isMatched()) {
|
|
166
|
+
// --- rule expressions are always evaluated in the context of AND
|
|
167
|
+
// --- any false expression will make the rule not match
|
|
168
|
+
this.isCompleteEvaluated = true;
|
|
169
|
+
this.isMatched = false;
|
|
170
|
+
return this;
|
|
80
171
|
}
|
|
81
172
|
}
|
|
82
|
-
|
|
173
|
+
if (unresolvable) {
|
|
174
|
+
// there is at least one exp is unresolvable now
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
this.isCompleteEvaluated = true;
|
|
179
|
+
this.isMatched = true;
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Whether or not the rule is resolvable (i.e. we can tell whether it's matched or not) now.
|
|
185
|
+
*
|
|
186
|
+
* @return {*} {boolean}
|
|
187
|
+
* @memberof RegoRule
|
|
188
|
+
*/
|
|
189
|
+
isResolvable() {
|
|
190
|
+
if (!this.isCompleteEvaluated) {
|
|
191
|
+
this.evaluate();
|
|
192
|
+
}
|
|
193
|
+
return this.isCompleteEvaluated;
|
|
83
194
|
}
|
|
84
195
|
/**
|
|
85
196
|
* Generate Human Readable string of this rule
|
|
@@ -103,6 +214,30 @@ class RegoRule {
|
|
|
103
214
|
return parts.join(" AND \n");
|
|
104
215
|
}
|
|
105
216
|
}
|
|
217
|
+
toData() {
|
|
218
|
+
return {
|
|
219
|
+
default: this.isDefault,
|
|
220
|
+
value: this.value,
|
|
221
|
+
fullName: this.fullName,
|
|
222
|
+
name: this.name,
|
|
223
|
+
expressions: this.expressions.map((exp, idx) => exp.toData(idx))
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
toJson() {
|
|
227
|
+
return JSON.stringify(this.toData());
|
|
228
|
+
}
|
|
229
|
+
toConciseData() {
|
|
230
|
+
return {
|
|
231
|
+
default: this.isDefault,
|
|
232
|
+
value: this.value,
|
|
233
|
+
fullName: this.fullName,
|
|
234
|
+
name: this.name,
|
|
235
|
+
expressions: this.expressions.map((exp) => exp.toConciseData())
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
toConciseJSON() {
|
|
239
|
+
return JSON.stringify(this.toConciseData());
|
|
240
|
+
}
|
|
106
241
|
/**
|
|
107
242
|
* Create RegoRule from Opa response data
|
|
108
243
|
*
|
|
@@ -129,7 +264,6 @@ class RegoRule {
|
|
|
129
264
|
parser
|
|
130
265
|
};
|
|
131
266
|
const regoRule = new RegoRule(ruleOptions);
|
|
132
|
-
regoRule.evaluate();
|
|
133
267
|
return regoRule;
|
|
134
268
|
}
|
|
135
269
|
static createExpressionsFromRuleBodyData(data, parser) {
|
|
@@ -176,9 +310,23 @@ exports.RegoOperators = {
|
|
|
176
310
|
*/
|
|
177
311
|
class RegoTerm {
|
|
178
312
|
constructor(type, value, parser) {
|
|
313
|
+
/**
|
|
314
|
+
* Whether the expression contains any resolvable references.
|
|
315
|
+
* reference start with `input.` should be considered as non-resolvable in context of partial evaluation.
|
|
316
|
+
* When this field is set to `true`, we should not attempt to evaluate this expression.
|
|
317
|
+
* i.e. evaluate() method should return immediately.
|
|
318
|
+
* This will speed up evaluation process.
|
|
319
|
+
*
|
|
320
|
+
* @type {boolean}
|
|
321
|
+
* @memberof RegoTerm
|
|
322
|
+
*/
|
|
323
|
+
this.hasNoResolvableRef = false;
|
|
179
324
|
this.type = type;
|
|
180
325
|
this.value = value;
|
|
181
326
|
this.parser = parser;
|
|
327
|
+
if (this.value instanceof RegoRef && this.value.hasNoResolvableRef) {
|
|
328
|
+
this.hasNoResolvableRef = true;
|
|
329
|
+
}
|
|
182
330
|
}
|
|
183
331
|
clone() {
|
|
184
332
|
return new RegoTerm(this.type, this.value, this.parser);
|
|
@@ -193,7 +341,7 @@ class RegoTerm {
|
|
|
193
341
|
if (this.value instanceof RegoRef)
|
|
194
342
|
return this.value.fullRefString();
|
|
195
343
|
else
|
|
196
|
-
return this.value;
|
|
344
|
+
return JSON.stringify(this.value);
|
|
197
345
|
}
|
|
198
346
|
/**
|
|
199
347
|
* If it's a reference term. A operator is an Reference term as well
|
|
@@ -329,6 +477,9 @@ class RegoTerm {
|
|
|
329
477
|
* @memberof RegoTerm
|
|
330
478
|
*/
|
|
331
479
|
getValue() {
|
|
480
|
+
if (this.hasNoResolvableRef) {
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
332
483
|
if (!this.isRef()) {
|
|
333
484
|
return this.value;
|
|
334
485
|
}
|
|
@@ -338,13 +489,67 @@ class RegoTerm {
|
|
|
338
489
|
}
|
|
339
490
|
else {
|
|
340
491
|
const fullName = this.fullRefString();
|
|
341
|
-
|
|
342
|
-
if (lodash_1.default.isUndefined(result))
|
|
492
|
+
if (!this.parser.isRefResolvable(fullName)) {
|
|
343
493
|
return undefined;
|
|
344
|
-
|
|
494
|
+
}
|
|
495
|
+
return this.parser.getRefValue(fullName);
|
|
345
496
|
}
|
|
346
497
|
}
|
|
347
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Whether or not the RegoTerm is resolvable
|
|
501
|
+
*
|
|
502
|
+
* @return {*} {boolean}
|
|
503
|
+
* @memberof RegoTerm
|
|
504
|
+
*/
|
|
505
|
+
isValueResolvable() {
|
|
506
|
+
if (this.hasNoResolvableRef) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
if (!this.isRef()) {
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
if (this.isOperator()) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
const fullName = this.fullRefString();
|
|
518
|
+
return this.parser.isRefResolvable(fullName);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
toData() {
|
|
523
|
+
if (this.isRef()) {
|
|
524
|
+
return this.value.toData();
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
return {
|
|
528
|
+
type: this.type,
|
|
529
|
+
value: this.value
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
toJson() {
|
|
534
|
+
return JSON.stringify(this.toData());
|
|
535
|
+
}
|
|
536
|
+
toConciseData() {
|
|
537
|
+
if (this.isRef()) {
|
|
538
|
+
return {
|
|
539
|
+
isRef: true,
|
|
540
|
+
value: this.fullRefString()
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
return {
|
|
545
|
+
isRef: false,
|
|
546
|
+
value: this.value
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
toConciseJSON() {
|
|
551
|
+
return JSON.stringify(this.toConciseData());
|
|
552
|
+
}
|
|
348
553
|
static parseFromData(data, parser) {
|
|
349
554
|
if (data.type === "ref") {
|
|
350
555
|
return new RegoTerm(data.type, RegoRef.parseFromData(data), parser);
|
|
@@ -363,6 +568,18 @@ exports.RegoTerm = RegoTerm;
|
|
|
363
568
|
*/
|
|
364
569
|
class RegoExp {
|
|
365
570
|
constructor(terms, isNegated = false, isCompleteEvaluated = false, value = null, parser) {
|
|
571
|
+
var _a;
|
|
572
|
+
/**
|
|
573
|
+
* Whether the expression contains any resolvable references.
|
|
574
|
+
* reference start with `input.` should be considered as non-resolvable in context of partial evaluation.
|
|
575
|
+
* When this field is set to `true`, we should not attempt to evaluate this expression.
|
|
576
|
+
* i.e. evaluate() method should return immediately.
|
|
577
|
+
* This will speed up evaluation process.
|
|
578
|
+
*
|
|
579
|
+
* @type {boolean}
|
|
580
|
+
* @memberof RegoExp
|
|
581
|
+
*/
|
|
582
|
+
this.hasNoResolvableRef = false;
|
|
366
583
|
/**
|
|
367
584
|
* If it's complete evaluated
|
|
368
585
|
*
|
|
@@ -382,6 +599,10 @@ class RegoExp {
|
|
|
382
599
|
this.isCompleteEvaluated = isCompleteEvaluated;
|
|
383
600
|
this.value = value;
|
|
384
601
|
this.parser = parser;
|
|
602
|
+
if (((_a = this === null || this === void 0 ? void 0 : this.terms) === null || _a === void 0 ? void 0 : _a.length) &&
|
|
603
|
+
this.terms.findIndex((item) => !item.hasNoResolvableRef) === -1) {
|
|
604
|
+
this.hasNoResolvableRef = true;
|
|
605
|
+
}
|
|
385
606
|
}
|
|
386
607
|
clone() {
|
|
387
608
|
const regoExp = new RegoExp(this.terms.map((t) => t.clone()), this.isNegated, this.isCompleteEvaluated, this.value, this.parser);
|
|
@@ -394,7 +615,22 @@ class RegoExp {
|
|
|
394
615
|
* @memberof RegoExp
|
|
395
616
|
*/
|
|
396
617
|
termsAsString() {
|
|
397
|
-
return this.terms.map((t) => t.asString());
|
|
618
|
+
return this.terms.map((t) => t.asString()).join(" ");
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Print concise format expression string presentation.
|
|
622
|
+
* Can be used for debugging
|
|
623
|
+
*
|
|
624
|
+
* @return {*}
|
|
625
|
+
* @memberof RegoExp
|
|
626
|
+
*/
|
|
627
|
+
asString() {
|
|
628
|
+
if (this.isNegated) {
|
|
629
|
+
return "NOT " + this.termsAsString();
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
return this.termsAsString();
|
|
633
|
+
}
|
|
398
634
|
}
|
|
399
635
|
/**
|
|
400
636
|
* Output human readable string
|
|
@@ -462,18 +698,41 @@ class RegoExp {
|
|
|
462
698
|
return this.value;
|
|
463
699
|
}
|
|
464
700
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
701
|
+
/**
|
|
702
|
+
* Whether or not a expression should be considered as "matched".
|
|
703
|
+
* If all expressions of a rule are "matched", the rule will be considered as "matched".
|
|
704
|
+
* Thus, the rule has a value.
|
|
705
|
+
*
|
|
706
|
+
* Please note: if an expression's value is `0`, empty string "", null etc, the expression is considered as "matched".
|
|
707
|
+
* We only consider an expression as "Not Matched" when the expression has value `false` or is undefined.
|
|
708
|
+
*
|
|
709
|
+
* @return {boolean}
|
|
710
|
+
* @memberof RegoExp
|
|
711
|
+
*/
|
|
712
|
+
isMatched() {
|
|
713
|
+
if (!this.isResolvable()) {
|
|
468
714
|
return undefined;
|
|
469
715
|
}
|
|
716
|
+
const isMatched = this.value === false || lodash_1.default.isUndefined(this.value) ? false : true;
|
|
717
|
+
if (this.isNegated) {
|
|
718
|
+
return !isMatched;
|
|
719
|
+
}
|
|
470
720
|
else {
|
|
471
|
-
|
|
472
|
-
return false;
|
|
473
|
-
// --- 0 is a match
|
|
474
|
-
return true;
|
|
721
|
+
return isMatched;
|
|
475
722
|
}
|
|
476
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Whether or not the expression is resolvable now.
|
|
726
|
+
*
|
|
727
|
+
* @return {boolean}
|
|
728
|
+
* @memberof RegoExp
|
|
729
|
+
*/
|
|
730
|
+
isResolvable() {
|
|
731
|
+
if (!this.isCompleteEvaluated) {
|
|
732
|
+
this.evaluate();
|
|
733
|
+
}
|
|
734
|
+
return this.isCompleteEvaluated;
|
|
735
|
+
}
|
|
477
736
|
/**
|
|
478
737
|
* Convert operator term to string and put rest operands into an array.
|
|
479
738
|
* And then return a [Operator, Operands] structure
|
|
@@ -492,12 +751,7 @@ class RegoExp {
|
|
|
492
751
|
operator = t.asOperator();
|
|
493
752
|
}
|
|
494
753
|
else {
|
|
495
|
-
|
|
496
|
-
if (!lodash_1.default.isUndefined(value)) {
|
|
497
|
-
operands.push(new RegoTerm(typeof value, value, this.parser));
|
|
498
|
-
}
|
|
499
|
-
else
|
|
500
|
-
operands.push(t);
|
|
754
|
+
operands.push(t);
|
|
501
755
|
}
|
|
502
756
|
});
|
|
503
757
|
if (!operator) {
|
|
@@ -515,19 +769,27 @@ class RegoExp {
|
|
|
515
769
|
* @memberof RegoExp
|
|
516
770
|
*/
|
|
517
771
|
evaluate() {
|
|
772
|
+
if (this.hasNoResolvableRef) {
|
|
773
|
+
return this;
|
|
774
|
+
}
|
|
775
|
+
if (this.isCompleteEvaluated) {
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
// --- so far there is no 2 terms expression e.g. ! x
|
|
779
|
+
// --- builtin function should never be included in residual rule
|
|
780
|
+
// --- as we won't apply them on unknowns
|
|
518
781
|
if (this.terms.length === 0) {
|
|
519
782
|
// --- exp should be considered as matched (true)
|
|
520
|
-
// --- unless isNegated is true
|
|
521
|
-
// --- will try to normalise isNegated here
|
|
522
783
|
this.isCompleteEvaluated = true;
|
|
523
|
-
this.value =
|
|
524
|
-
this
|
|
784
|
+
this.value = true;
|
|
785
|
+
return this;
|
|
525
786
|
}
|
|
526
|
-
if (this.terms.length === 1) {
|
|
787
|
+
else if (this.terms.length === 1) {
|
|
527
788
|
const term = this.terms[0];
|
|
528
|
-
|
|
529
|
-
if (lodash_1.default.isUndefined(value))
|
|
789
|
+
if (!term.isValueResolvable()) {
|
|
530
790
|
return this;
|
|
791
|
+
}
|
|
792
|
+
const value = term.getValue();
|
|
531
793
|
this.value = value;
|
|
532
794
|
this.isCompleteEvaluated = true;
|
|
533
795
|
return this;
|
|
@@ -536,47 +798,101 @@ class RegoExp {
|
|
|
536
798
|
// --- 3 terms expression e.g. true == true or x >= 3
|
|
537
799
|
// --- we only evalute some redundant expression e.g. true == true or false != true
|
|
538
800
|
const [operator, operands] = this.toOperatorOperandsArray();
|
|
539
|
-
if (operands.
|
|
540
|
-
|
|
801
|
+
if (!operands[0].isValueResolvable() ||
|
|
802
|
+
!operands[1].isValueResolvable()) {
|
|
803
|
+
// if one of the term value is resolvable now, we can't evaluate further.
|
|
541
804
|
return this;
|
|
542
805
|
}
|
|
543
|
-
|
|
544
|
-
|
|
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;
|
|
806
|
+
const operandsValues = operands.map((op) => op.getValue());
|
|
807
|
+
if (operandsValues.findIndex((v) => typeof v === "undefined")) {
|
|
571
808
|
}
|
|
809
|
+
let value = null;
|
|
810
|
+
switch (operator) {
|
|
811
|
+
case "=":
|
|
812
|
+
value = operandsValues[0] === operandsValues[1];
|
|
813
|
+
break;
|
|
814
|
+
case ">":
|
|
815
|
+
value = operandsValues[0] > operandsValues[1];
|
|
816
|
+
break;
|
|
817
|
+
case "<":
|
|
818
|
+
value = operandsValues[0] < operandsValues[1];
|
|
819
|
+
break;
|
|
820
|
+
case ">=":
|
|
821
|
+
value = operandsValues[0] >= operandsValues[1];
|
|
822
|
+
break;
|
|
823
|
+
case "<=":
|
|
824
|
+
value = operandsValues[0] <= operandsValues[1];
|
|
825
|
+
break;
|
|
826
|
+
case "!=":
|
|
827
|
+
value = operandsValues[0] != operandsValues[1];
|
|
828
|
+
break;
|
|
829
|
+
default:
|
|
830
|
+
throw new Error(`Invalid 3 terms rego expression, Unknown operator "${operator}": ${this.termsAsString()}`);
|
|
831
|
+
}
|
|
832
|
+
this.isCompleteEvaluated = true;
|
|
833
|
+
this.value = value;
|
|
834
|
+
return this;
|
|
572
835
|
}
|
|
573
836
|
else {
|
|
574
837
|
throw new Error(`Invalid ${this.terms.length} terms rego expression: ${this.termsAsString()}`);
|
|
575
838
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
839
|
+
}
|
|
840
|
+
toData(index = 0, ignoreIndex = false, ignoreNegated = false) {
|
|
841
|
+
const terms = this.terms.map((term) => term.toData());
|
|
842
|
+
if (this.isNegated && !ignoreNegated) {
|
|
843
|
+
if (ignoreIndex) {
|
|
844
|
+
return {
|
|
845
|
+
negated: true,
|
|
846
|
+
terms
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
return {
|
|
851
|
+
negated: true,
|
|
852
|
+
index,
|
|
853
|
+
terms
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
else {
|
|
858
|
+
if (ignoreIndex) {
|
|
859
|
+
return {
|
|
860
|
+
terms
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
return {
|
|
865
|
+
index,
|
|
866
|
+
terms
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
toJSON(index = 0, ignoreIndex = false, ignoreNegated = false) {
|
|
872
|
+
return JSON.stringify(this.toData(index, ignoreIndex, ignoreNegated));
|
|
873
|
+
}
|
|
874
|
+
toConciseData() {
|
|
875
|
+
let data;
|
|
876
|
+
if (this.terms.length === 1) {
|
|
877
|
+
const term = this.terms[0];
|
|
878
|
+
data = {
|
|
879
|
+
negated: this.isNegated,
|
|
880
|
+
operator: null,
|
|
881
|
+
operands: [term.toConciseData()]
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
const [operator, operands] = this.toOperatorOperandsArray();
|
|
886
|
+
data = {
|
|
887
|
+
negated: this.isNegated,
|
|
888
|
+
operator,
|
|
889
|
+
operands: operands.map((item) => item.toConciseData())
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
return data;
|
|
893
|
+
}
|
|
894
|
+
toConciseJSON() {
|
|
895
|
+
return JSON.stringify(this.toConciseData());
|
|
580
896
|
}
|
|
581
897
|
static parseFromData(expData, parser) {
|
|
582
898
|
const isNegated = expData.negated === true;
|
|
@@ -608,11 +924,38 @@ exports.RegoExp = RegoExp;
|
|
|
608
924
|
*/
|
|
609
925
|
class RegoRef {
|
|
610
926
|
constructor(parts) {
|
|
927
|
+
var _a;
|
|
928
|
+
/**
|
|
929
|
+
* Whether the expression contains any resolvable references.
|
|
930
|
+
* reference start with `input.` should be considered as non-resolvable in context of partial evaluation.
|
|
931
|
+
* When this field is set to `true`, we should not attempt to evaluate this expression.
|
|
932
|
+
* i.e. evaluate() method should return immediately.
|
|
933
|
+
* This will speed up evaluation process.
|
|
934
|
+
*
|
|
935
|
+
* @type {boolean}
|
|
936
|
+
* @memberof RegoRef
|
|
937
|
+
*/
|
|
938
|
+
this.hasNoResolvableRef = false;
|
|
611
939
|
this.parts = parts;
|
|
940
|
+
if (
|
|
941
|
+
// `input.` ref should be considered not resolvable in partial evaluate context.
|
|
942
|
+
(this.parts.length && ((_a = this.parts[0]) === null || _a === void 0 ? void 0 : _a.value) === "input") ||
|
|
943
|
+
this.isOperator()) {
|
|
944
|
+
this.hasNoResolvableRef = true;
|
|
945
|
+
}
|
|
612
946
|
}
|
|
613
947
|
clone() {
|
|
614
948
|
return new RegoRef(this.parts.map((p) => (Object.assign({}, p))));
|
|
615
949
|
}
|
|
950
|
+
toData() {
|
|
951
|
+
return {
|
|
952
|
+
type: "ref",
|
|
953
|
+
value: this.parts
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
toJson() {
|
|
957
|
+
return JSON.stringify(this.toData());
|
|
958
|
+
}
|
|
616
959
|
static parseFromData(data) {
|
|
617
960
|
if (data.type === "ref") {
|
|
618
961
|
return new RegoRef(data.value);
|
|
@@ -662,10 +1005,8 @@ class RegoRef {
|
|
|
662
1005
|
if (isFirstPart)
|
|
663
1006
|
isFirstPart = false;
|
|
664
1007
|
return partStr;
|
|
665
|
-
//--- a.[_].[_] should be a[_][_]
|
|
666
1008
|
})
|
|
667
|
-
.join(".")
|
|
668
|
-
.replace(/\.\[/g, "[");
|
|
1009
|
+
.join(".");
|
|
669
1010
|
return this.removeAllPrefixs(str, removalPrefixs);
|
|
670
1011
|
}
|
|
671
1012
|
refString(removalPrefixs = []) {
|
|
@@ -718,6 +1059,189 @@ function value2String(value) {
|
|
|
718
1059
|
return JSON.stringify(value);
|
|
719
1060
|
}
|
|
720
1061
|
exports.value2String = value2String;
|
|
1062
|
+
class RegoRuleSet {
|
|
1063
|
+
constructor(parser, rules, fullName = "", name = "") {
|
|
1064
|
+
var _a, _b;
|
|
1065
|
+
this.fullName = "";
|
|
1066
|
+
this.name = "";
|
|
1067
|
+
this.rules = [];
|
|
1068
|
+
this.defaultRule = null;
|
|
1069
|
+
this.isCompleteEvaluated = false;
|
|
1070
|
+
/**
|
|
1071
|
+
* Whether the ruleSet contains any rules that has any resolvable references.
|
|
1072
|
+
* reference start with `input.` should be considered as non-resolvable in context of partial evaluation.
|
|
1073
|
+
* When this field is set to `true`, we should not attempt to evaluate this expression.
|
|
1074
|
+
* i.e. evaluate() method should return immediately.
|
|
1075
|
+
* This will speed up evaluation process.
|
|
1076
|
+
*
|
|
1077
|
+
* @type {boolean}
|
|
1078
|
+
* @memberof RegoRuleSet
|
|
1079
|
+
*/
|
|
1080
|
+
this.hasNoResolvableRef = false;
|
|
1081
|
+
this.parser = parser;
|
|
1082
|
+
if (rules === null || rules === void 0 ? void 0 : rules.length) {
|
|
1083
|
+
const defaultRuleIdx = rules.findIndex((r) => r.isDefault);
|
|
1084
|
+
if (defaultRuleIdx !== -1) {
|
|
1085
|
+
this.defaultRule = rules[defaultRuleIdx];
|
|
1086
|
+
}
|
|
1087
|
+
this.rules = rules.filter((r) => !r.isDefault);
|
|
1088
|
+
}
|
|
1089
|
+
if (fullName) {
|
|
1090
|
+
this.fullName = fullName;
|
|
1091
|
+
}
|
|
1092
|
+
else if ((_a = rules === null || rules === void 0 ? void 0 : rules[0]) === null || _a === void 0 ? void 0 : _a.fullName) {
|
|
1093
|
+
this.fullName = rules[0].fullName;
|
|
1094
|
+
}
|
|
1095
|
+
if (name) {
|
|
1096
|
+
this.name = name;
|
|
1097
|
+
}
|
|
1098
|
+
else if ((_b = rules === null || rules === void 0 ? void 0 : rules[0]) === null || _b === void 0 ? void 0 : _b.name) {
|
|
1099
|
+
this.name = rules[0].name;
|
|
1100
|
+
}
|
|
1101
|
+
if (this.rules.length &&
|
|
1102
|
+
this.rules.findIndex((r) => !r.hasNoResolvableRef) === -1) {
|
|
1103
|
+
this.hasNoResolvableRef = true;
|
|
1104
|
+
}
|
|
1105
|
+
this.evaluate();
|
|
1106
|
+
}
|
|
1107
|
+
evaluate() {
|
|
1108
|
+
var _a;
|
|
1109
|
+
if (this.hasNoResolvableRef) {
|
|
1110
|
+
return this;
|
|
1111
|
+
}
|
|
1112
|
+
if (this.isCompleteEvaluated) {
|
|
1113
|
+
return this;
|
|
1114
|
+
}
|
|
1115
|
+
if (!((_a = this.rules) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
1116
|
+
if (!this.defaultRule) {
|
|
1117
|
+
this.isCompleteEvaluated = true;
|
|
1118
|
+
this.value = undefined;
|
|
1119
|
+
return this;
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
if (this.defaultRule.isResolvable()) {
|
|
1123
|
+
this.isCompleteEvaluated = true;
|
|
1124
|
+
this.value = this.defaultRule.value;
|
|
1125
|
+
return this;
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
return this;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
this.rules.forEach((r) => r.evaluate());
|
|
1133
|
+
const matchedRule = this.rules.find((r) => r.isResolvable() && r.isMatched);
|
|
1134
|
+
if (matchedRule) {
|
|
1135
|
+
this.isCompleteEvaluated = true;
|
|
1136
|
+
this.value = matchedRule.value;
|
|
1137
|
+
return this;
|
|
1138
|
+
}
|
|
1139
|
+
if (this.rules.findIndex((r) => !r.isResolvable()) !== -1) {
|
|
1140
|
+
// still has rule unresolvable
|
|
1141
|
+
return this;
|
|
1142
|
+
}
|
|
1143
|
+
// rest (if any) are all unmatched rules
|
|
1144
|
+
if (!this.defaultRule) {
|
|
1145
|
+
this.isCompleteEvaluated = true;
|
|
1146
|
+
this.value = undefined;
|
|
1147
|
+
return this;
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
if (this.defaultRule.isResolvable()) {
|
|
1151
|
+
this.isCompleteEvaluated = true;
|
|
1152
|
+
this.value = this.defaultRule.value;
|
|
1153
|
+
return this;
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
return this;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
isResolvable() {
|
|
1161
|
+
if (!this.isCompleteEvaluated) {
|
|
1162
|
+
this.evaluate();
|
|
1163
|
+
}
|
|
1164
|
+
return this.isCompleteEvaluated;
|
|
1165
|
+
}
|
|
1166
|
+
getResidualRules() {
|
|
1167
|
+
if (this.isResolvable()) {
|
|
1168
|
+
return [];
|
|
1169
|
+
}
|
|
1170
|
+
let rules = this.defaultRule
|
|
1171
|
+
? [this.defaultRule, ...this.rules]
|
|
1172
|
+
: [...this.rules];
|
|
1173
|
+
rules = this.rules.filter((r) => !r.isResolvable());
|
|
1174
|
+
if (!rules.length) {
|
|
1175
|
+
return [];
|
|
1176
|
+
}
|
|
1177
|
+
rules = lodash_1.default.flatMap(rules, (rule) => {
|
|
1178
|
+
// all resolvable expressions can all be ignored as:
|
|
1179
|
+
// - if the expression is resolved to "matched", it won't impact the result of the rule
|
|
1180
|
+
// - if the expression is resolved to "unmatched", the rule should be resolved to "unmatched" earlier.
|
|
1181
|
+
const unresolvedExpressions = rule.expressions.filter((exp) => !exp.isResolvable());
|
|
1182
|
+
if (unresolvedExpressions.length !== 1) {
|
|
1183
|
+
return [rule];
|
|
1184
|
+
}
|
|
1185
|
+
// For rules with single expression, reduce the layer by replacing it with target reference rules
|
|
1186
|
+
const exp = unresolvedExpressions[0];
|
|
1187
|
+
if (exp.terms.length === 1) {
|
|
1188
|
+
const fullName = exp.terms[0].fullRefString();
|
|
1189
|
+
const ruleSet = this.parser.ruleSets[fullName];
|
|
1190
|
+
if (!ruleSet) {
|
|
1191
|
+
const compressedRule = rule.clone();
|
|
1192
|
+
compressedRule.expressions = [exp];
|
|
1193
|
+
return [compressedRule];
|
|
1194
|
+
}
|
|
1195
|
+
return ruleSet.getResidualRules();
|
|
1196
|
+
}
|
|
1197
|
+
else if (exp.terms.length === 3) {
|
|
1198
|
+
const [operator, [op1, op2]] = exp.toOperatorOperandsArray();
|
|
1199
|
+
if (operator != "=" && operator != "!=") {
|
|
1200
|
+
// For now, we will only further process the ref when operator is = or !=
|
|
1201
|
+
return [rule];
|
|
1202
|
+
}
|
|
1203
|
+
if (!op1.isValueResolvable() && !op2.isValueResolvable()) {
|
|
1204
|
+
// when both op1 & op1 are not resolvable ref, we will not attempt to process further
|
|
1205
|
+
return [rule];
|
|
1206
|
+
}
|
|
1207
|
+
const value = op1.isValueResolvable()
|
|
1208
|
+
? op1.getValue()
|
|
1209
|
+
: op2.getValue();
|
|
1210
|
+
const refTerm = op1.isValueResolvable() ? op2 : op1;
|
|
1211
|
+
const fullName = refTerm.fullRefString();
|
|
1212
|
+
const ruleSet = this.parser.ruleSets[fullName];
|
|
1213
|
+
if (!ruleSet) {
|
|
1214
|
+
const compressedRule = rule.clone();
|
|
1215
|
+
compressedRule.expressions = [exp];
|
|
1216
|
+
return [compressedRule];
|
|
1217
|
+
}
|
|
1218
|
+
let refRules = ruleSet.getResidualRules();
|
|
1219
|
+
// when negated expression, reverse the operator
|
|
1220
|
+
const convertedOperator = exp.isNegated
|
|
1221
|
+
? operator == "="
|
|
1222
|
+
? "!="
|
|
1223
|
+
: "="
|
|
1224
|
+
: operator;
|
|
1225
|
+
if (convertedOperator == "=") {
|
|
1226
|
+
refRules = refRules.filter((r) => r.value == value);
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
refRules = refRules.filter((r) => r.value != value);
|
|
1230
|
+
}
|
|
1231
|
+
if (!refRules.length) {
|
|
1232
|
+
// this means this rule can never matched
|
|
1233
|
+
return [];
|
|
1234
|
+
}
|
|
1235
|
+
return refRules;
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
throw new Error(`Failed to produce residualRules for rule: ${rule.toJson()}`);
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
return rules;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
exports.RegoRuleSet = RegoRuleSet;
|
|
721
1245
|
/**
|
|
722
1246
|
* OPA result Parser
|
|
723
1247
|
*
|
|
@@ -756,6 +1280,13 @@ class OpaCompileResponseParser {
|
|
|
756
1280
|
* @memberof OpaCompileResponseParser
|
|
757
1281
|
*/
|
|
758
1282
|
this.rules = [];
|
|
1283
|
+
/**
|
|
1284
|
+
* Parsed, compressed & evaluated rule sets
|
|
1285
|
+
*
|
|
1286
|
+
* @type {RegoRuleSet[]}
|
|
1287
|
+
* @memberof OpaCompileResponseParser
|
|
1288
|
+
*/
|
|
1289
|
+
this.ruleSets = {};
|
|
759
1290
|
this.queries = [];
|
|
760
1291
|
/**
|
|
761
1292
|
* A cache of all resolved rule result
|
|
@@ -774,6 +1305,7 @@ class OpaCompileResponseParser {
|
|
|
774
1305
|
* @memberof OpaCompileResponseParser
|
|
775
1306
|
*/
|
|
776
1307
|
this.pseudoQueryRuleName = RegoRule.randomRuleName("default_rule_");
|
|
1308
|
+
this.ruleDuplicationCheckCache = {};
|
|
777
1309
|
}
|
|
778
1310
|
setQueryRuleResult(val) {
|
|
779
1311
|
this.completeRuleResults[this.pseudoQueryRuleName] = {
|
|
@@ -797,20 +1329,26 @@ class OpaCompileResponseParser {
|
|
|
797
1329
|
else {
|
|
798
1330
|
this.data = json;
|
|
799
1331
|
}
|
|
800
|
-
|
|
1332
|
+
/**
|
|
1333
|
+
* OPA might output {"result": {}} as unconditional `false` or never matched
|
|
1334
|
+
*/
|
|
1335
|
+
if (!this.data.result || !Object.keys(this.data.result).length) {
|
|
801
1336
|
// --- mean no rule matched
|
|
802
1337
|
this.setQueryRuleResult(false);
|
|
803
1338
|
return [];
|
|
804
1339
|
}
|
|
805
1340
|
this.data = this.data.result;
|
|
806
|
-
if (
|
|
1341
|
+
if (!this.data.queries ||
|
|
807
1342
|
!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
|
|
1343
|
+
!this.data.queries.length) {
|
|
811
1344
|
this.setQueryRuleResult(false);
|
|
812
1345
|
return [];
|
|
813
1346
|
}
|
|
1347
|
+
if (this.data.queries.findIndex((q) => !(q === null || q === void 0 ? void 0 : q.length)) !== -1) {
|
|
1348
|
+
// when query is always true, the "queries" value in the result will contain an empty array
|
|
1349
|
+
this.setQueryRuleResult(true);
|
|
1350
|
+
return [];
|
|
1351
|
+
}
|
|
814
1352
|
const queries = this.data.queries;
|
|
815
1353
|
if (queries) {
|
|
816
1354
|
if (queries.findIndex((ruleBody) => !ruleBody || !ruleBody.length) !== -1) {
|
|
@@ -828,9 +1366,9 @@ class OpaCompileResponseParser {
|
|
|
828
1366
|
value: true,
|
|
829
1367
|
parser: this
|
|
830
1368
|
});
|
|
831
|
-
|
|
832
|
-
this.
|
|
833
|
-
this.
|
|
1369
|
+
// this.originalRules.push(rule);
|
|
1370
|
+
// this.rules.push(rule);
|
|
1371
|
+
this.addRule(rule);
|
|
834
1372
|
});
|
|
835
1373
|
}
|
|
836
1374
|
const packages = this.data.support;
|
|
@@ -844,85 +1382,69 @@ class OpaCompileResponseParser {
|
|
|
844
1382
|
const rules = p.rules;
|
|
845
1383
|
rules.forEach((r) => {
|
|
846
1384
|
const regoRule = RegoRule.parseFromData(r, packageName, this);
|
|
847
|
-
this.originalRules.push(regoRule);
|
|
848
|
-
//
|
|
849
|
-
|
|
850
|
-
this.rules.push(regoRule);
|
|
851
|
-
}
|
|
852
|
-
else {
|
|
853
|
-
if (regoRule.isMatched) {
|
|
854
|
-
this.rules.push(regoRule);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
1385
|
+
//this.originalRules.push(regoRule);
|
|
1386
|
+
//this.rules.push(regoRule);
|
|
1387
|
+
this.addRule(regoRule);
|
|
857
1388
|
});
|
|
858
1389
|
});
|
|
859
1390
|
}
|
|
860
|
-
this.
|
|
861
|
-
this.
|
|
1391
|
+
this.ruleDuplicationCheckCache = {};
|
|
1392
|
+
lodash_1.default.uniq(this.rules.map((r) => r.fullName)).forEach((fullName) => (this.ruleSets[fullName] = new RegoRuleSet(this, this.rules.filter((r) => r.fullName === fullName), fullName)));
|
|
1393
|
+
this.resolveAllRuleSets();
|
|
862
1394
|
return this.rules;
|
|
863
1395
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1396
|
+
addRule(rule) {
|
|
1397
|
+
this.originalRules.push(rule);
|
|
1398
|
+
if (rule.isImpossible()) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
if (!this.ruleDuplicationCheckCache[rule.fullName]) {
|
|
1402
|
+
this.ruleDuplicationCheckCache[rule.fullName] = new Set();
|
|
1403
|
+
}
|
|
1404
|
+
const setData = this.ruleDuplicationCheckCache[rule.fullName];
|
|
1405
|
+
const _a = rule.toData(), { name, fullName } = _a, ruleData = __rest(_a, ["name", "fullName"]);
|
|
1406
|
+
const jsonData = JSON.stringify(ruleData);
|
|
1407
|
+
const size = setData.size;
|
|
1408
|
+
setData.add(jsonData);
|
|
1409
|
+
if (size === setData.size) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
this.rules.push(rule);
|
|
1413
|
+
}
|
|
1414
|
+
isRefResolvable(fullName) {
|
|
1415
|
+
if (this.completeRuleResults[fullName]) {
|
|
1416
|
+
return true;
|
|
1417
|
+
}
|
|
1418
|
+
const ruleSet = this.ruleSets[fullName];
|
|
1419
|
+
if (!ruleSet) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
return ruleSet.isResolvable();
|
|
1423
|
+
}
|
|
1424
|
+
getRefValue(fullName) {
|
|
1425
|
+
const completeResult = this.completeRuleResults[fullName];
|
|
1426
|
+
if (completeResult) {
|
|
1427
|
+
return completeResult.value;
|
|
1428
|
+
}
|
|
1429
|
+
const ruleSet = this.ruleSets[fullName];
|
|
1430
|
+
if (!ruleSet || !ruleSet.isResolvable()) {
|
|
1431
|
+
return undefined;
|
|
1432
|
+
}
|
|
1433
|
+
return ruleSet.value;
|
|
1434
|
+
}
|
|
1435
|
+
resolveAllRuleSets() {
|
|
1436
|
+
while (true) {
|
|
1437
|
+
const unresolvedSetsNum = Object.values(this.ruleSets).filter((rs) => !rs.isResolvable()).length;
|
|
1438
|
+
if (!unresolvedSetsNum) {
|
|
1439
|
+
break;
|
|
899
1440
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1441
|
+
Object.values(this.ruleSets).forEach((rs) => rs.evaluate());
|
|
1442
|
+
const newUnresolvedSetsNum = Object.values(this.ruleSets).filter((rs) => !rs.isResolvable()).length;
|
|
1443
|
+
if (!newUnresolvedSetsNum ||
|
|
1444
|
+
newUnresolvedSetsNum >= unresolvedSetsNum) {
|
|
1445
|
+
break;
|
|
905
1446
|
}
|
|
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
1447
|
}
|
|
924
|
-
// --- unmatched non-default rule can be stripped out
|
|
925
|
-
this.rules = this.rules.filter((r) => !(r.isCompleteEvaluated && !r.isMatched && !r.isDefault));
|
|
926
1448
|
}
|
|
927
1449
|
/**
|
|
928
1450
|
* Call to evaluate a rule
|
|
@@ -932,136 +1454,28 @@ class OpaCompileResponseParser {
|
|
|
932
1454
|
* @memberof OpaCompileResponseParser
|
|
933
1455
|
*/
|
|
934
1456
|
evaluateRule(fullName) {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
// --- already evaluated during paring or dependencies removal
|
|
938
|
-
return (_c = this.completeRuleResults) === null || _c === void 0 ? void 0 : _c[fullName];
|
|
1457
|
+
if (this.completeRuleResults[fullName]) {
|
|
1458
|
+
return this.completeRuleResults[fullName];
|
|
939
1459
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (!rules.length) {
|
|
943
|
-
// --- no any rule matched; often (depends on your policy) it means a overall non-matched (false)
|
|
1460
|
+
const ruleSet = this.ruleSets[fullName];
|
|
1461
|
+
if (!ruleSet) {
|
|
944
1462
|
return null;
|
|
945
1463
|
}
|
|
946
|
-
|
|
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) {
|
|
1464
|
+
if (ruleSet.isResolvable()) {
|
|
955
1465
|
return {
|
|
956
1466
|
fullName,
|
|
957
|
-
name:
|
|
958
|
-
value:
|
|
959
|
-
isCompleteEvaluated: true
|
|
960
|
-
residualRules: []
|
|
1467
|
+
name: ruleSet.name,
|
|
1468
|
+
value: ruleSet.value,
|
|
1469
|
+
isCompleteEvaluated: true
|
|
961
1470
|
};
|
|
962
1471
|
}
|
|
963
1472
|
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
1473
|
return {
|
|
1060
1474
|
fullName,
|
|
1061
|
-
name:
|
|
1475
|
+
name: ruleSet.name,
|
|
1062
1476
|
value: undefined,
|
|
1063
1477
|
isCompleteEvaluated: false,
|
|
1064
|
-
residualRules:
|
|
1478
|
+
residualRules: ruleSet.getResidualRules()
|
|
1065
1479
|
};
|
|
1066
1480
|
}
|
|
1067
1481
|
}
|
|
@@ -1086,6 +1500,9 @@ class OpaCompileResponseParser {
|
|
|
1086
1500
|
if (result === null)
|
|
1087
1501
|
return "null";
|
|
1088
1502
|
if (result.isCompleteEvaluated) {
|
|
1503
|
+
if (typeof result.value === "undefined") {
|
|
1504
|
+
return "undefined";
|
|
1505
|
+
}
|
|
1089
1506
|
return value2String(result.value);
|
|
1090
1507
|
}
|
|
1091
1508
|
let parts = result.residualRules.map((r) => r.toHumanReadableString());
|
|
@@ -1103,23 +1520,6 @@ class OpaCompileResponseParser {
|
|
|
1103
1520
|
evaluateAsHumanReadableString() {
|
|
1104
1521
|
return this.evaluateRuleAsHumanReadableString(this.pseudoQueryRuleName);
|
|
1105
1522
|
}
|
|
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
1523
|
reportWarns(msg) {
|
|
1124
1524
|
this.warns.push(msg);
|
|
1125
1525
|
this.hasWarns = true;
|