@cullet/erp-core 1.0.2 → 1.0.4
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/README.md +24 -24
- package/dist/gate-engine-registry.js.map +1 -1
- package/dist/gate-v1-payload.schema.js +5 -0
- package/dist/gate-v1-payload.schema.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/outcome.js.map +1 -1
- package/dist/policies/engines/v1/gate/index.d.ts +1 -0
- package/dist/policies/engines/v1/gate/index.js.map +1 -1
- package/dist/policy-service.js.map +1 -1
- package/dist/validation-code.js.map +1 -1
- package/dist/validation-error.js.map +1 -1
- package/dist/validation-field.js.map +1 -1
- package/meta.json +67 -67
- package/package.json +56 -56
- package/src/core/application/commands/command.ts +3 -3
- package/src/core/application/commands/requested-by.ts +60 -60
- package/src/core/application/index.ts +21 -21
- package/src/core/application/policy-error-mapper.ts +54 -54
- package/src/core/application/ports/index.ts +6 -6
- package/src/core/application/ports/logger.port.ts +4 -4
- package/src/core/application/ports/metrics.port.ts +3 -3
- package/src/core/application/ports/policy-port.ts +7 -7
- package/src/core/application/ports/repository.port.ts +3 -3
- package/src/core/application/ports/temporal-repository.port.ts +4 -4
- package/src/core/application/ports/tracer.port.ts +7 -7
- package/src/core/application/queries/query.ts +13 -13
- package/src/core/application/temporal/index.ts +7 -7
- package/src/core/application/temporal/temporal-context.ts +20 -20
- package/src/core/application/temporal/temporal-use-case.ts +22 -22
- package/src/core/application/use-case.ts +10 -10
- package/src/core/config/core-config.instance.ts +1 -1
- package/src/core/config/core-config.ts +59 -59
- package/src/core/config/index.ts +6 -6
- package/src/core/config/policy-reporter.ts +31 -31
- package/src/core/config/silent-policy-reporter.ts +1 -1
- package/src/core/domain/entity.ts +44 -44
- package/src/core/domain/rulesets/entity-ruleset.contracts.ts +6 -6
- package/src/core/domain/rulesets/ruleset-registry.ts +49 -49
- package/src/core/domain/rulesets/ruleset.contracts.ts +6 -6
- package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +5 -5
- package/src/core/domain/temporal/index.ts +11 -11
- package/src/core/domain/temporal/temporal-range.ts +16 -16
- package/src/core/domain/temporal/temporal-snapshot.ts +24 -24
- package/src/core/domain/temporal/transaction-time.ts +30 -30
- package/src/core/domain/temporal/valid-time.ts +29 -29
- package/src/core/domain/value-object.ts +16 -16
- package/src/core/errors/app-error.ts +64 -64
- package/src/core/errors/authentication-error.ts +134 -134
- package/src/core/errors/authorization-error.ts +154 -154
- package/src/core/errors/business-rule-violation-error.ts +15 -15
- package/src/core/errors/conflict-error.ts +202 -201
- package/src/core/errors/error-codes.ts +48 -48
- package/src/core/errors/idempotency-error.ts +324 -320
- package/src/core/errors/index.ts +50 -50
- package/src/core/errors/integration-error.ts +120 -120
- package/src/core/errors/legacy-incompatible-error.ts +13 -13
- package/src/core/errors/not-found-error.ts +14 -14
- package/src/core/errors/serialization-error.ts +167 -167
- package/src/core/errors/temporal-error.ts +109 -107
- package/src/core/errors/types.ts +35 -35
- package/src/core/errors/unexpected-error.ts +10 -10
- package/src/core/errors/utils/index.ts +2 -2
- package/src/core/errors/utils/json-safe.ts +53 -53
- package/src/core/errors/validation-error.ts +18 -18
- package/src/core/exceptions/business-rule-violation-exception.ts +6 -6
- package/src/core/exceptions/domain-exception.ts +5 -5
- package/src/core/exceptions/entity-not-found-exception.ts +6 -6
- package/src/core/exceptions/invalid-state-transition-exception.ts +8 -8
- package/src/core/exceptions/invariant-violation-exception.ts +3 -3
- package/src/core/exceptions/validation-code.ts +35 -29
- package/src/core/exceptions/validation-exception.ts +19 -19
- package/src/core/exceptions/validation-field.ts +20 -19
- package/src/core/index.ts +7 -7
- package/src/core/policies/asof/asof.ts +47 -47
- package/src/core/policies/catalog/catalog.instance.ts +8 -6
- package/src/core/policies/catalog/index.ts +4 -4
- package/src/core/policies/catalog/policy-catalog-entry.ts +199 -197
- package/src/core/policies/catalog/policy-catalog.ts +101 -101
- package/src/core/policies/catalog/policy-key.ts +51 -51
- package/src/core/policies/context/context-builder.ts +335 -313
- package/src/core/policies/context/context-registry.instance.ts +9 -9
- package/src/core/policies/context/context-registry.ts +61 -61
- package/src/core/policies/context/context-resolver.ts +12 -12
- package/src/core/policies/context/context-seed.ts +19 -15
- package/src/core/policies/context/index.ts +9 -9
- package/src/core/policies/context/path.ts +135 -128
- package/src/core/policies/defs/in-memory-policy-definition-repo.ts +56 -55
- package/src/core/policies/defs/index.ts +10 -10
- package/src/core/policies/defs/policy-definition-repository.ts +3 -3
- package/src/core/policies/defs/policy-definition.ts +143 -142
- package/src/core/policies/defs/policy-scope.ts +21 -19
- package/src/core/policies/defs/policy-semver.ts +51 -51
- package/src/core/policies/engines/compute-engine-registry.ts +43 -43
- package/src/core/policies/engines/compute-evaluator-registry.ts +20 -20
- package/src/core/policies/engines/compute-registry.ts +45 -45
- package/src/core/policies/engines/compute-types.ts +15 -15
- package/src/core/policies/engines/condition-evaluator-reporter.ts +17 -17
- package/src/core/policies/engines/gate-engine-registry.ts +27 -25
- package/src/core/policies/engines/gate-types.ts +28 -28
- package/src/core/policies/engines/index.ts +28 -28
- package/src/core/policies/engines/parse-compute-payload.ts +51 -45
- package/src/core/policies/engines/parse-gate-payload.ts +42 -42
- package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +39 -37
- package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +43 -41
- package/src/core/policies/engines/v1/compute/index.ts +6 -6
- package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +41 -39
- package/src/core/policies/engines/v1/condition-evaluator.ts +557 -504
- package/src/core/policies/engines/v1/condition-schema.ts +35 -35
- package/src/core/policies/engines/v1/condition-types.ts +21 -21
- package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +134 -130
- package/src/core/policies/engines/v1/gate/gate-types-v1.ts +5 -5
- package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +32 -30
- package/src/core/policies/engines/v1/gate/index.ts +4 -4
- package/src/core/policies/engines/v1/index.ts +8 -8
- package/src/core/policies/index.ts +72 -72
- package/src/core/policies/package/policy-package.ts +3 -3
- package/src/core/policies/policy-ids.ts +25 -25
- package/src/core/policies/resolver/policy-resolver.ts +34 -34
- package/src/core/policies/service/index.ts +6 -6
- package/src/core/policies/service/policy-evaluation-error.ts +141 -133
- package/src/core/policies/service/policy-service.ts +432 -419
- package/src/core/policies/utils/date.ts +3 -3
- package/src/core/policies/utils/hash.ts +61 -61
- package/src/core/result/outcome.ts +131 -126
- package/src/core/result/result.ts +130 -127
- package/src/core/shared/aggregate-version.ts +5 -5
- package/src/core/shared/hashing.ts +18 -18
- package/src/core/shared/immutable.ts +29 -29
- package/src/core/shared/temporal-guards.ts +9 -9
- package/src/core/versioning/domain-event-contracts.ts +40 -40
- package/src/core/versioning/version.ts +19 -19
- package/src/examples/rulesets/entity/order-creation-rules-v1.ts +18 -18
- package/src/examples/rulesets/entity/order-invariants-v1.ts +31 -29
- package/src/examples/rulesets/entity/order-invariants-v2.ts +12 -12
- package/src/examples/rulesets/entity/order.ts +127 -127
- package/src/examples/rulesets/value-object/cpf-rules-v1.ts +41 -39
- package/src/examples/rulesets/value-object/cpf-rules-v2.ts +14 -12
- package/src/examples/rulesets/value-object/cpf.ts +24 -24
- package/src/examples/rulesets/value-object/customer.ts +42 -39
- package/src/examples/rulesets/value-object/person-name-rules-v1.ts +27 -27
- package/src/examples/rulesets/value-object/person-name-rules-v2.ts +13 -13
- package/src/examples/rulesets/value-object/person-name.ts +29 -26
- package/src/index.ts +2 -2
- package/src/version.ts +1 -1
|
@@ -3,590 +3,643 @@ import { PolicyDateUtils } from "../../utils";
|
|
|
3
3
|
import { Result } from "../../../result/result";
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
ConditionAndNode,
|
|
7
|
+
ConditionLeafNode,
|
|
8
|
+
ConditionNode,
|
|
9
|
+
ConditionNotNode,
|
|
10
|
+
ConditionOp,
|
|
11
|
+
ConditionOrNode,
|
|
12
12
|
} from "./condition-types";
|
|
13
13
|
import type {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
ConditionEvaluationOptions,
|
|
15
|
+
ConditionEvaluationReport,
|
|
16
16
|
} from "../condition-evaluator-reporter";
|
|
17
17
|
import type { PolicyContext } from "../gate-types";
|
|
18
18
|
|
|
19
19
|
const CONDITION_EVAL_THROWN_TAG = "CONDITION_EVAL_THREW";
|
|
20
20
|
const NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG =
|
|
21
|
-
|
|
21
|
+
"NULLISH_NUMERIC_OPERAND_NOT_ALLOWED";
|
|
22
22
|
const NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG = "NULLISH_DATE_OPERAND_NOT_ALLOWED";
|
|
23
23
|
const INVALID_DATE_OPERAND_TAG = "INVALID_DATE_OPERAND";
|
|
24
24
|
const EMPTY_OR_CONDITION_TAG = "EMPTY_OR_CONDITION";
|
|
25
|
+
const EMPTY_AND_CONDITION_TAG = "EMPTY_AND_CONDITION";
|
|
25
26
|
const ISO_8601_UTC_DATE_PATTERN =
|
|
26
|
-
|
|
27
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
27
28
|
|
|
28
29
|
export interface ConditionEvaluationTrace {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
readonly conditionPath: string;
|
|
31
|
+
readonly decisiveNode: ConditionNode;
|
|
32
|
+
readonly lastEvaluatedLeaf: ConditionLeafNode;
|
|
33
|
+
readonly lastEvaluatedLeafPath: string;
|
|
34
|
+
readonly lastEvaluatedLeafResult: boolean;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export interface TracedConditionEvaluation {
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
readonly matched: boolean;
|
|
39
|
+
readonly trace: ConditionEvaluationTrace;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
type RelationalOperator = Extract<ConditionOp, "gt" | "gte" | "lt" | "lte">;
|
|
42
43
|
|
|
43
44
|
export class ConditionEvaluatorV1 {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
private static isLeafNode(node: ConditionNode): node is ConditionLeafNode {
|
|
50
|
-
return "field" in node && "op" in node;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private static isAndNode(node: ConditionNode): node is ConditionAndNode {
|
|
54
|
-
return "and" in node;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private static isOrNode(node: ConditionNode): node is ConditionOrNode {
|
|
58
|
-
return "or" in node;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private static isNotNode(node: ConditionNode): node is ConditionNotNode {
|
|
62
|
-
return "not" in node;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private static isRelationalOperator(
|
|
66
|
-
op: ConditionOp,
|
|
67
|
-
): op is RelationalOperator {
|
|
68
|
-
return op === "gt" || op === "gte" || op === "lt" || op === "lte";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private static describeError(error: unknown): {
|
|
72
|
-
name: string;
|
|
73
|
-
message: string;
|
|
74
|
-
} {
|
|
75
|
-
if (error instanceof Error) {
|
|
76
|
-
return {
|
|
77
|
-
name: error.name,
|
|
78
|
-
message: error.message,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
45
|
+
constructor(
|
|
46
|
+
private readonly context: PolicyContext,
|
|
47
|
+
private readonly options: ConditionEvaluationOptions,
|
|
48
|
+
) {}
|
|
81
49
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
message: String(error),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private static buildNullishNumericOperandMessage(
|
|
89
|
-
node: ConditionLeafNode,
|
|
90
|
-
actual: null | undefined,
|
|
91
|
-
): string {
|
|
92
|
-
const resolvedValue = actual === null ? "null" : "undefined";
|
|
93
|
-
|
|
94
|
-
return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private static buildNullishDateOperandMessage(
|
|
98
|
-
node: ConditionLeafNode,
|
|
99
|
-
actual: null | undefined,
|
|
100
|
-
): string {
|
|
101
|
-
const resolvedValue = actual === null ? "null" : "undefined";
|
|
102
|
-
|
|
103
|
-
return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private static buildInvalidDateOperandMessage(
|
|
107
|
-
node: ConditionLeafNode,
|
|
108
|
-
): string {
|
|
109
|
-
return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private static buildEmptyOrConditionMessage(): string {
|
|
113
|
-
return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private static parseComparableDate(
|
|
117
|
-
value: unknown,
|
|
118
|
-
): Result<Date, string> | null {
|
|
119
|
-
if (value instanceof Date) {
|
|
120
|
-
if (!PolicyDateUtils.isValid(value)) {
|
|
121
|
-
return Result.err("Invalid Date instance");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return Result.ok(value);
|
|
50
|
+
private static isLeafNode(node: ConditionNode): node is ConditionLeafNode {
|
|
51
|
+
return "field" in node && "op" in node;
|
|
125
52
|
}
|
|
126
53
|
|
|
127
|
-
|
|
128
|
-
|
|
54
|
+
private static isAndNode(node: ConditionNode): node is ConditionAndNode {
|
|
55
|
+
return "and" in node;
|
|
129
56
|
}
|
|
130
57
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return Result.err("Invalid ISO 8601 UTC date string");
|
|
58
|
+
private static isOrNode(node: ConditionNode): node is ConditionOrNode {
|
|
59
|
+
return "or" in node;
|
|
134
60
|
}
|
|
135
61
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
private static evaluateRelationalNumbers(
|
|
140
|
-
actual: number,
|
|
141
|
-
op: RelationalOperator,
|
|
142
|
-
expected: number,
|
|
143
|
-
): boolean {
|
|
144
|
-
switch (op) {
|
|
145
|
-
case "gt":
|
|
146
|
-
return actual > expected;
|
|
147
|
-
case "gte":
|
|
148
|
-
return actual >= expected;
|
|
149
|
-
case "lt":
|
|
150
|
-
return actual < expected;
|
|
151
|
-
case "lte":
|
|
152
|
-
return actual <= expected;
|
|
62
|
+
private static isNotNode(node: ConditionNode): node is ConditionNotNode {
|
|
63
|
+
return "not" in node;
|
|
153
64
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
readonly message: string;
|
|
160
|
-
readonly details: Readonly<Record<string, unknown>>;
|
|
161
|
-
}): ConditionEvaluationReport {
|
|
162
|
-
return {
|
|
163
|
-
occurredAt: new Date(),
|
|
164
|
-
level: params.level,
|
|
165
|
-
tag: params.tag,
|
|
166
|
-
message: params.message,
|
|
167
|
-
engineVersion: this.options.engineVersion,
|
|
168
|
-
details: params.details,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private conditionEvalErr<TValue>(
|
|
173
|
-
node: ConditionNode,
|
|
174
|
-
error: unknown,
|
|
175
|
-
): Result<TValue, string> {
|
|
176
|
-
const cause = ConditionEvaluatorV1.describeError(error);
|
|
177
|
-
const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
|
|
178
|
-
|
|
179
|
-
this.options.reporter.error(
|
|
180
|
-
this.buildReport({
|
|
181
|
-
level: "error",
|
|
182
|
-
tag: CONDITION_EVAL_THROWN_TAG,
|
|
183
|
-
message,
|
|
184
|
-
details: { node, cause },
|
|
185
|
-
}),
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
return Result.err(message);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private reportDateOperandError(
|
|
192
|
-
node: ConditionLeafNode,
|
|
193
|
-
actual: unknown,
|
|
194
|
-
): Result<boolean, string> {
|
|
195
|
-
const message = ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
|
|
196
|
-
|
|
197
|
-
this.options.reporter.error(
|
|
198
|
-
this.buildReport({
|
|
199
|
-
level: "error",
|
|
200
|
-
tag: INVALID_DATE_OPERAND_TAG,
|
|
201
|
-
message,
|
|
202
|
-
details: {
|
|
203
|
-
field: node.field,
|
|
204
|
-
op: node.op,
|
|
205
|
-
actual,
|
|
206
|
-
expected: node.value,
|
|
207
|
-
node,
|
|
208
|
-
},
|
|
209
|
-
}),
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
return Result.err(message);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private evaluateDateRelationalNode(
|
|
216
|
-
node: ConditionLeafNode,
|
|
217
|
-
actual: unknown,
|
|
218
|
-
): Result<boolean, string> | null {
|
|
219
|
-
if (!ConditionEvaluatorV1.isRelationalOperator(node.op)) {
|
|
220
|
-
return null;
|
|
65
|
+
|
|
66
|
+
private static isRelationalOperator(
|
|
67
|
+
op: ConditionOp,
|
|
68
|
+
): op is RelationalOperator {
|
|
69
|
+
return op === "gt" || op === "gte" || op === "lt" || op === "lte";
|
|
221
70
|
}
|
|
222
71
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
72
|
+
private static describeError(error: unknown): {
|
|
73
|
+
name: string;
|
|
74
|
+
message: string;
|
|
75
|
+
} {
|
|
76
|
+
if (error instanceof Error) {
|
|
77
|
+
return {
|
|
78
|
+
name: error.name,
|
|
79
|
+
message: error.message,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
227
82
|
|
|
228
|
-
|
|
229
|
-
|
|
83
|
+
return {
|
|
84
|
+
name: "NonErrorThrown",
|
|
85
|
+
message: String(error),
|
|
86
|
+
};
|
|
230
87
|
}
|
|
231
88
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
|
|
240
|
-
node,
|
|
241
|
-
actual,
|
|
242
|
-
);
|
|
243
|
-
this.options.reporter.error(
|
|
244
|
-
this.buildReport({
|
|
245
|
-
level: "error",
|
|
246
|
-
tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
247
|
-
message,
|
|
248
|
-
details: {
|
|
249
|
-
field: node.field,
|
|
250
|
-
op: node.op,
|
|
251
|
-
actual,
|
|
252
|
-
expected: node.value,
|
|
253
|
-
allowNull: allowsNull,
|
|
254
|
-
node,
|
|
255
|
-
},
|
|
256
|
-
}),
|
|
257
|
-
);
|
|
89
|
+
private static buildNullishNumericOperandMessage(
|
|
90
|
+
node: ConditionLeafNode,
|
|
91
|
+
actual: null | undefined,
|
|
92
|
+
): string {
|
|
93
|
+
const resolvedValue = actual === null ? "null" : "undefined";
|
|
258
94
|
|
|
259
|
-
|
|
95
|
+
return `${NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for numeric operator "${node.op}"`;
|
|
260
96
|
}
|
|
261
97
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.options.reporter.error(
|
|
268
|
-
this.buildReport({
|
|
269
|
-
level: "error",
|
|
270
|
-
tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
271
|
-
message,
|
|
272
|
-
details: {
|
|
273
|
-
field: node.field,
|
|
274
|
-
op: node.op,
|
|
275
|
-
actual,
|
|
276
|
-
expected: node.value,
|
|
277
|
-
allowNull: allowsNull,
|
|
278
|
-
node,
|
|
279
|
-
},
|
|
280
|
-
}),
|
|
281
|
-
);
|
|
98
|
+
private static buildNullishDateOperandMessage(
|
|
99
|
+
node: ConditionLeafNode,
|
|
100
|
+
actual: null | undefined,
|
|
101
|
+
): string {
|
|
102
|
+
const resolvedValue = actual === null ? "null" : "undefined";
|
|
282
103
|
|
|
283
|
-
|
|
104
|
+
return `${NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG}: "${node.field}" resolved to ${resolvedValue} for date comparison operator "${node.op}"`;
|
|
284
105
|
}
|
|
285
106
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
expectedDateResult.isErr()
|
|
291
|
-
) {
|
|
292
|
-
return this.reportDateOperandError(node, actual);
|
|
107
|
+
private static buildInvalidDateOperandMessage(
|
|
108
|
+
node: ConditionLeafNode,
|
|
109
|
+
): string {
|
|
110
|
+
return `${INVALID_DATE_OPERAND_TAG}: "${node.field}" with operator "${node.op}" requires Date or ISO 8601 UTC string operands`;
|
|
293
111
|
}
|
|
294
112
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
actualDateResult.getOrNull()!.getTime(),
|
|
298
|
-
node.op,
|
|
299
|
-
expectedDateResult.getOrNull()!.getTime(),
|
|
300
|
-
),
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private evaluateOperator(
|
|
305
|
-
actual: unknown,
|
|
306
|
-
op: ConditionOp,
|
|
307
|
-
expected: unknown,
|
|
308
|
-
): boolean {
|
|
309
|
-
switch (op) {
|
|
310
|
-
case "eq":
|
|
311
|
-
return actual === expected;
|
|
312
|
-
case "neq":
|
|
313
|
-
return actual !== expected;
|
|
314
|
-
case "gt":
|
|
315
|
-
return (
|
|
316
|
-
typeof actual === "number" &&
|
|
317
|
-
typeof expected === "number" &&
|
|
318
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
|
|
319
|
-
);
|
|
320
|
-
case "gte":
|
|
321
|
-
return (
|
|
322
|
-
typeof actual === "number" &&
|
|
323
|
-
typeof expected === "number" &&
|
|
324
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
|
|
325
|
-
);
|
|
326
|
-
case "lt":
|
|
327
|
-
return (
|
|
328
|
-
typeof actual === "number" &&
|
|
329
|
-
typeof expected === "number" &&
|
|
330
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
|
|
331
|
-
);
|
|
332
|
-
case "lte":
|
|
333
|
-
return (
|
|
334
|
-
typeof actual === "number" &&
|
|
335
|
-
typeof expected === "number" &&
|
|
336
|
-
ConditionEvaluatorV1.evaluateRelationalNumbers(actual, op, expected)
|
|
337
|
-
);
|
|
338
|
-
case "in":
|
|
339
|
-
return Array.isArray(expected) && expected.includes(actual);
|
|
340
|
-
case "notIn":
|
|
341
|
-
return Array.isArray(expected) && !expected.includes(actual);
|
|
342
|
-
case "isNull":
|
|
343
|
-
return actual === null;
|
|
344
|
-
case "isNotNull":
|
|
345
|
-
return actual !== null && actual !== undefined;
|
|
113
|
+
private static buildEmptyOrConditionMessage(): string {
|
|
114
|
+
return `${EMPTY_OR_CONDITION_TAG}: OR nodes must contain at least one child condition`;
|
|
346
115
|
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private evaluateNumericRelationalNode(
|
|
350
|
-
node: ConditionLeafNode,
|
|
351
|
-
actual: unknown,
|
|
352
|
-
): Result<boolean, string> {
|
|
353
|
-
const allowsNull = node.allowNull === true;
|
|
354
|
-
if (actual === undefined || (actual === null && !allowsNull)) {
|
|
355
|
-
const message = ConditionEvaluatorV1.buildNullishNumericOperandMessage(
|
|
356
|
-
node,
|
|
357
|
-
actual as null | undefined,
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
this.options.reporter.error(
|
|
361
|
-
this.buildReport({
|
|
362
|
-
level: "error",
|
|
363
|
-
tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
|
|
364
|
-
message,
|
|
365
|
-
details: {
|
|
366
|
-
field: node.field,
|
|
367
|
-
op: node.op,
|
|
368
|
-
actual,
|
|
369
|
-
expected: node.value,
|
|
370
|
-
allowNull: allowsNull,
|
|
371
|
-
node,
|
|
372
|
-
},
|
|
373
|
-
}),
|
|
374
|
-
);
|
|
375
116
|
|
|
376
|
-
|
|
117
|
+
private static buildEmptyAndConditionMessage(): string {
|
|
118
|
+
return `${EMPTY_AND_CONDITION_TAG}: AND nodes must contain at least one child condition`;
|
|
377
119
|
}
|
|
378
120
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
);
|
|
387
|
-
if (actualResult.isErr()) {
|
|
388
|
-
this.options.reporter.warn(
|
|
389
|
-
this.buildReport({
|
|
390
|
-
level: "warn",
|
|
391
|
-
tag: "MISSING_CONTEXT_FIELD",
|
|
392
|
-
message: `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
|
|
393
|
-
details: { field: node.field, node },
|
|
394
|
-
}),
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
return Result.err(
|
|
398
|
-
`MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
|
|
399
|
-
);
|
|
400
|
-
}
|
|
121
|
+
private static parseComparableDate(
|
|
122
|
+
value: unknown,
|
|
123
|
+
): Result<Date, string> | null {
|
|
124
|
+
if (value instanceof Date) {
|
|
125
|
+
if (!PolicyDateUtils.isValid(value)) {
|
|
126
|
+
return Result.err("Invalid Date instance");
|
|
127
|
+
}
|
|
401
128
|
|
|
402
|
-
|
|
403
|
-
const actual = actualResult.getOrThrow();
|
|
404
|
-
if (ConditionEvaluatorV1.isRelationalOperator(node.op)) {
|
|
405
|
-
const dateResult = this.evaluateDateRelationalNode(node, actual);
|
|
406
|
-
if (dateResult !== null) {
|
|
407
|
-
return dateResult;
|
|
129
|
+
return Result.ok(value);
|
|
408
130
|
}
|
|
409
131
|
|
|
410
|
-
|
|
411
|
-
|
|
132
|
+
if (
|
|
133
|
+
typeof value !== "string" ||
|
|
134
|
+
!ISO_8601_UTC_DATE_PATTERN.test(value)
|
|
135
|
+
) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
412
138
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
139
|
+
const parsed = new Date(value);
|
|
140
|
+
if (
|
|
141
|
+
!PolicyDateUtils.isValid(parsed) ||
|
|
142
|
+
parsed.toISOString() !== value
|
|
143
|
+
) {
|
|
144
|
+
return Result.err("Invalid ISO 8601 UTC date string");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Result.ok(parsed);
|
|
416
148
|
}
|
|
417
|
-
}
|
|
418
149
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
private evaluateWithTraceInternal(
|
|
436
|
-
node: ConditionNode,
|
|
437
|
-
path: string,
|
|
438
|
-
): Result<TracedConditionEvaluation, string> {
|
|
439
|
-
if (ConditionEvaluatorV1.isLeafNode(node)) {
|
|
440
|
-
const leafResult = this.evaluateLeafNode(node);
|
|
441
|
-
if (leafResult.isErr()) {
|
|
442
|
-
return Result.err(leafResult.errorOrNull()!);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const matched = leafResult.getOrNull()!;
|
|
446
|
-
return Result.ok({
|
|
447
|
-
matched,
|
|
448
|
-
trace: this.buildTrace({
|
|
449
|
-
conditionPath: path,
|
|
450
|
-
decisiveNode: node,
|
|
451
|
-
lastEvaluatedLeaf: node,
|
|
452
|
-
lastEvaluatedLeafPath: path,
|
|
453
|
-
lastEvaluatedLeafResult: matched,
|
|
454
|
-
}),
|
|
455
|
-
});
|
|
150
|
+
private static evaluateRelationalNumbers(
|
|
151
|
+
actual: number,
|
|
152
|
+
op: RelationalOperator,
|
|
153
|
+
expected: number,
|
|
154
|
+
): boolean {
|
|
155
|
+
switch (op) {
|
|
156
|
+
case "gt":
|
|
157
|
+
return actual > expected;
|
|
158
|
+
case "gte":
|
|
159
|
+
return actual >= expected;
|
|
160
|
+
case "lt":
|
|
161
|
+
return actual < expected;
|
|
162
|
+
case "lte":
|
|
163
|
+
return actual <= expected;
|
|
164
|
+
}
|
|
456
165
|
}
|
|
457
166
|
|
|
458
|
-
|
|
459
|
-
|
|
167
|
+
private buildReport(params: {
|
|
168
|
+
readonly level: ConditionEvaluationReport["level"];
|
|
169
|
+
readonly tag: ConditionEvaluationReport["tag"];
|
|
170
|
+
readonly message: string;
|
|
171
|
+
readonly details: Readonly<Record<string, unknown>>;
|
|
172
|
+
}): ConditionEvaluationReport {
|
|
173
|
+
return {
|
|
174
|
+
occurredAt: new Date(),
|
|
175
|
+
level: params.level,
|
|
176
|
+
tag: params.tag,
|
|
177
|
+
message: params.message,
|
|
178
|
+
engineVersion: this.options.engineVersion,
|
|
179
|
+
details: params.details,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
460
182
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
183
|
+
private conditionEvalErr<TValue>(
|
|
184
|
+
node: ConditionNode,
|
|
185
|
+
error: unknown,
|
|
186
|
+
): Result<TValue, string> {
|
|
187
|
+
const cause = ConditionEvaluatorV1.describeError(error);
|
|
188
|
+
const message = `${CONDITION_EVAL_THROWN_TAG}: ${cause.name}: ${cause.message}`;
|
|
189
|
+
|
|
190
|
+
this.options.reporter.error(
|
|
191
|
+
this.buildReport({
|
|
192
|
+
level: "error",
|
|
193
|
+
tag: CONDITION_EVAL_THROWN_TAG,
|
|
194
|
+
message,
|
|
195
|
+
details: { node, cause },
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return Result.err(message);
|
|
200
|
+
}
|
|
467
201
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
202
|
+
private reportDateOperandError(
|
|
203
|
+
node: ConditionLeafNode,
|
|
204
|
+
actual: unknown,
|
|
205
|
+
): Result<boolean, string> {
|
|
206
|
+
const message =
|
|
207
|
+
ConditionEvaluatorV1.buildInvalidDateOperandMessage(node);
|
|
208
|
+
|
|
209
|
+
this.options.reporter.error(
|
|
210
|
+
this.buildReport({
|
|
211
|
+
level: "error",
|
|
212
|
+
tag: INVALID_DATE_OPERAND_TAG,
|
|
213
|
+
message,
|
|
214
|
+
details: {
|
|
215
|
+
field: node.field,
|
|
216
|
+
op: node.op,
|
|
217
|
+
actual,
|
|
218
|
+
expected: node.value,
|
|
219
|
+
node,
|
|
220
|
+
},
|
|
480
221
|
}),
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
}
|
|
222
|
+
);
|
|
484
223
|
|
|
485
|
-
|
|
486
|
-
matched: true,
|
|
487
|
-
trace: lastTracedChild!.trace,
|
|
488
|
-
});
|
|
224
|
+
return Result.err(message);
|
|
489
225
|
}
|
|
490
226
|
|
|
491
|
-
|
|
492
|
-
|
|
227
|
+
private evaluateDateRelationalNode(
|
|
228
|
+
node: ConditionLeafNode,
|
|
229
|
+
actual: unknown,
|
|
230
|
+
): Result<boolean, string> | null {
|
|
231
|
+
if (!ConditionEvaluatorV1.isRelationalOperator(node.op)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
493
234
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
235
|
+
const actualDateResult =
|
|
236
|
+
ConditionEvaluatorV1.parseComparableDate(actual);
|
|
237
|
+
const expectedDateResult = ConditionEvaluatorV1.parseComparableDate(
|
|
238
|
+
node.value,
|
|
498
239
|
);
|
|
499
|
-
|
|
500
|
-
|
|
240
|
+
|
|
241
|
+
if (actualDateResult === null && expectedDateResult === null) {
|
|
242
|
+
return null;
|
|
501
243
|
}
|
|
502
244
|
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
245
|
+
const allowsNull = node.allowNull === true;
|
|
246
|
+
|
|
247
|
+
if (actual === null) {
|
|
248
|
+
if (allowsNull) {
|
|
249
|
+
return Result.ok(false);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
|
|
253
|
+
node,
|
|
254
|
+
actual,
|
|
255
|
+
);
|
|
256
|
+
this.options.reporter.error(
|
|
257
|
+
this.buildReport({
|
|
258
|
+
level: "error",
|
|
259
|
+
tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
260
|
+
message,
|
|
261
|
+
details: {
|
|
262
|
+
field: node.field,
|
|
263
|
+
op: node.op,
|
|
264
|
+
actual,
|
|
265
|
+
expected: node.value,
|
|
266
|
+
allowNull: allowsNull,
|
|
267
|
+
node,
|
|
268
|
+
},
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return Result.err(message);
|
|
510
273
|
}
|
|
511
|
-
}
|
|
512
274
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
275
|
+
if (actual === undefined) {
|
|
276
|
+
const message = ConditionEvaluatorV1.buildNullishDateOperandMessage(
|
|
277
|
+
node,
|
|
278
|
+
actual,
|
|
279
|
+
);
|
|
280
|
+
this.options.reporter.error(
|
|
281
|
+
this.buildReport({
|
|
282
|
+
level: "error",
|
|
283
|
+
tag: NULLISH_DATE_OPERAND_NOT_ALLOWED_TAG,
|
|
284
|
+
message,
|
|
285
|
+
details: {
|
|
286
|
+
field: node.field,
|
|
287
|
+
op: node.op,
|
|
288
|
+
actual,
|
|
289
|
+
expected: node.value,
|
|
290
|
+
allowNull: allowsNull,
|
|
291
|
+
node,
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return Result.err(message);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (
|
|
300
|
+
actualDateResult === null ||
|
|
301
|
+
actualDateResult.isErr() ||
|
|
302
|
+
expectedDateResult === null ||
|
|
303
|
+
expectedDateResult.isErr()
|
|
304
|
+
) {
|
|
305
|
+
return this.reportDateOperandError(node, actual);
|
|
306
|
+
}
|
|
516
307
|
|
|
517
|
-
|
|
308
|
+
return Result.ok(
|
|
309
|
+
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
310
|
+
actualDateResult.getOrNull()!.getTime(),
|
|
311
|
+
node.op,
|
|
312
|
+
expectedDateResult.getOrNull()!.getTime(),
|
|
313
|
+
),
|
|
314
|
+
);
|
|
518
315
|
}
|
|
519
316
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
317
|
+
private evaluateOperator(
|
|
318
|
+
actual: unknown,
|
|
319
|
+
op: ConditionOp,
|
|
320
|
+
expected: unknown,
|
|
321
|
+
): boolean {
|
|
322
|
+
switch (op) {
|
|
323
|
+
case "eq":
|
|
324
|
+
return actual === expected;
|
|
325
|
+
case "neq":
|
|
326
|
+
return actual !== expected;
|
|
327
|
+
case "gt":
|
|
328
|
+
return (
|
|
329
|
+
typeof actual === "number" &&
|
|
330
|
+
typeof expected === "number" &&
|
|
331
|
+
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
332
|
+
actual,
|
|
333
|
+
op,
|
|
334
|
+
expected,
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
case "gte":
|
|
338
|
+
return (
|
|
339
|
+
typeof actual === "number" &&
|
|
340
|
+
typeof expected === "number" &&
|
|
341
|
+
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
342
|
+
actual,
|
|
343
|
+
op,
|
|
344
|
+
expected,
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
case "lt":
|
|
348
|
+
return (
|
|
349
|
+
typeof actual === "number" &&
|
|
350
|
+
typeof expected === "number" &&
|
|
351
|
+
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
352
|
+
actual,
|
|
353
|
+
op,
|
|
354
|
+
expected,
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
case "lte":
|
|
358
|
+
return (
|
|
359
|
+
typeof actual === "number" &&
|
|
360
|
+
typeof expected === "number" &&
|
|
361
|
+
ConditionEvaluatorV1.evaluateRelationalNumbers(
|
|
362
|
+
actual,
|
|
363
|
+
op,
|
|
364
|
+
expected,
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
case "in":
|
|
368
|
+
return Array.isArray(expected) && expected.includes(actual);
|
|
369
|
+
case "notIn":
|
|
370
|
+
return Array.isArray(expected) && !expected.includes(actual);
|
|
371
|
+
case "isNull":
|
|
372
|
+
return actual === null;
|
|
373
|
+
case "isNotNull":
|
|
374
|
+
return actual !== null && actual !== undefined;
|
|
375
|
+
}
|
|
538
376
|
}
|
|
539
377
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
378
|
+
private evaluateNumericRelationalNode(
|
|
379
|
+
node: ConditionLeafNode,
|
|
380
|
+
actual: unknown,
|
|
381
|
+
): Result<boolean, string> {
|
|
382
|
+
const allowsNull = node.allowNull === true;
|
|
383
|
+
if (actual === undefined || (actual === null && !allowsNull)) {
|
|
384
|
+
const message =
|
|
385
|
+
ConditionEvaluatorV1.buildNullishNumericOperandMessage(
|
|
386
|
+
node,
|
|
387
|
+
actual as null | undefined,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
this.options.reporter.error(
|
|
391
|
+
this.buildReport({
|
|
392
|
+
level: "error",
|
|
393
|
+
tag: NULLISH_NUMERIC_OPERAND_NOT_ALLOWED_TAG,
|
|
394
|
+
message,
|
|
395
|
+
details: {
|
|
396
|
+
field: node.field,
|
|
397
|
+
op: node.op,
|
|
398
|
+
actual,
|
|
399
|
+
expected: node.value,
|
|
400
|
+
allowNull: allowsNull,
|
|
401
|
+
node,
|
|
402
|
+
},
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
return Result.err(message);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return Result.ok(this.evaluateOperator(actual, node.op, node.value));
|
|
556
410
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
411
|
+
|
|
412
|
+
private evaluateLeafNode(node: ConditionLeafNode): Result<boolean, string> {
|
|
413
|
+
const actualResult = PolicyContextPath.getOrAbsent(
|
|
414
|
+
this.context,
|
|
415
|
+
node.field,
|
|
416
|
+
);
|
|
417
|
+
if (actualResult.isErr()) {
|
|
418
|
+
this.options.reporter.warn(
|
|
419
|
+
this.buildReport({
|
|
420
|
+
level: "warn",
|
|
421
|
+
tag: "MISSING_CONTEXT_FIELD",
|
|
422
|
+
message: `MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
|
|
423
|
+
details: { field: node.field, node },
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
return Result.err(
|
|
428
|
+
`MISSING_CONTEXT_FIELD: "${node.field}" not found in context`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
const actual = actualResult.getOrThrow();
|
|
434
|
+
if (ConditionEvaluatorV1.isRelationalOperator(node.op)) {
|
|
435
|
+
const dateResult = this.evaluateDateRelationalNode(
|
|
436
|
+
node,
|
|
437
|
+
actual,
|
|
438
|
+
);
|
|
439
|
+
if (dateResult !== null) {
|
|
440
|
+
return dateResult;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return this.evaluateNumericRelationalNode(node, actual);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return Result.ok(
|
|
447
|
+
this.evaluateOperator(actual, node.op, node.value),
|
|
448
|
+
);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return this.conditionEvalErr(node, error);
|
|
451
|
+
}
|
|
566
452
|
}
|
|
567
|
-
}
|
|
568
453
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
454
|
+
private buildTrace(params: {
|
|
455
|
+
readonly conditionPath: string;
|
|
456
|
+
readonly decisiveNode: ConditionNode;
|
|
457
|
+
readonly lastEvaluatedLeaf: ConditionLeafNode;
|
|
458
|
+
readonly lastEvaluatedLeafPath: string;
|
|
459
|
+
readonly lastEvaluatedLeafResult: boolean;
|
|
460
|
+
}): ConditionEvaluationTrace {
|
|
461
|
+
return {
|
|
462
|
+
conditionPath: params.conditionPath,
|
|
463
|
+
decisiveNode: params.decisiveNode,
|
|
464
|
+
lastEvaluatedLeaf: params.lastEvaluatedLeaf,
|
|
465
|
+
lastEvaluatedLeafPath: params.lastEvaluatedLeafPath,
|
|
466
|
+
lastEvaluatedLeafResult: params.lastEvaluatedLeafResult,
|
|
467
|
+
};
|
|
572
468
|
}
|
|
573
469
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
470
|
+
private evaluateWithTraceInternal(
|
|
471
|
+
node: ConditionNode,
|
|
472
|
+
path: string,
|
|
473
|
+
): Result<TracedConditionEvaluation, string> {
|
|
474
|
+
if (ConditionEvaluatorV1.isLeafNode(node)) {
|
|
475
|
+
const leafResult = this.evaluateLeafNode(node);
|
|
476
|
+
if (leafResult.isErr()) {
|
|
477
|
+
return Result.err(leafResult.errorOrNull()!);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const matched = leafResult.getOrNull()!;
|
|
481
|
+
return Result.ok({
|
|
482
|
+
matched,
|
|
483
|
+
trace: this.buildTrace({
|
|
484
|
+
conditionPath: path,
|
|
485
|
+
decisiveNode: node,
|
|
486
|
+
lastEvaluatedLeaf: node,
|
|
487
|
+
lastEvaluatedLeafPath: path,
|
|
488
|
+
lastEvaluatedLeafResult: matched,
|
|
489
|
+
}),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (ConditionEvaluatorV1.isAndNode(node)) {
|
|
494
|
+
if (node.and.length === 0) {
|
|
495
|
+
return Result.err(
|
|
496
|
+
ConditionEvaluatorV1.buildEmptyAndConditionMessage(),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let lastTracedChild: TracedConditionEvaluation | null = null;
|
|
501
|
+
|
|
502
|
+
for (const [index, child] of node.and.entries()) {
|
|
503
|
+
const childPath = `${path}.and[${index}]`;
|
|
504
|
+
const childResult = this.evaluateWithTraceInternal(
|
|
505
|
+
child,
|
|
506
|
+
childPath,
|
|
507
|
+
);
|
|
508
|
+
if (childResult.isErr()) {
|
|
509
|
+
return Result.err(childResult.errorOrNull()!);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const tracedChild = childResult.getOrNull()!;
|
|
513
|
+
lastTracedChild = tracedChild;
|
|
514
|
+
if (!tracedChild.matched) {
|
|
515
|
+
return Result.ok({
|
|
516
|
+
matched: false,
|
|
517
|
+
trace: this.buildTrace({
|
|
518
|
+
conditionPath: childPath,
|
|
519
|
+
decisiveNode: child,
|
|
520
|
+
lastEvaluatedLeaf:
|
|
521
|
+
tracedChild.trace.lastEvaluatedLeaf,
|
|
522
|
+
lastEvaluatedLeafPath:
|
|
523
|
+
tracedChild.trace.lastEvaluatedLeafPath,
|
|
524
|
+
lastEvaluatedLeafResult:
|
|
525
|
+
tracedChild.trace.lastEvaluatedLeafResult,
|
|
526
|
+
}),
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return Result.ok({
|
|
532
|
+
matched: true,
|
|
533
|
+
trace: lastTracedChild!.trace,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (ConditionEvaluatorV1.isOrNode(node)) {
|
|
538
|
+
let lastTrace: ConditionEvaluationTrace | null = null;
|
|
539
|
+
|
|
540
|
+
for (const [index, child] of node.or.entries()) {
|
|
541
|
+
const childResult = this.evaluateWithTraceInternal(
|
|
542
|
+
child,
|
|
543
|
+
`${path}.or[${index}]`,
|
|
544
|
+
);
|
|
545
|
+
if (childResult.isErr()) {
|
|
546
|
+
return Result.err(childResult.errorOrNull()!);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const tracedChild = childResult.getOrNull()!;
|
|
550
|
+
lastTrace = tracedChild.trace;
|
|
551
|
+
if (tracedChild.matched) {
|
|
552
|
+
return Result.ok({
|
|
553
|
+
matched: true,
|
|
554
|
+
trace: tracedChild.trace,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (lastTrace === null) {
|
|
560
|
+
return Result.err(
|
|
561
|
+
ConditionEvaluatorV1.buildEmptyOrConditionMessage(),
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return Result.ok({ matched: false, trace: lastTrace! });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (ConditionEvaluatorV1.isNotNode(node)) {
|
|
569
|
+
const childPath = `${path}.not`;
|
|
570
|
+
const childResult = this.evaluateWithTraceInternal(
|
|
571
|
+
node.not,
|
|
572
|
+
childPath,
|
|
573
|
+
);
|
|
574
|
+
if (childResult.isErr()) {
|
|
575
|
+
return Result.err(childResult.errorOrNull()!);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const tracedChild = childResult.getOrNull()!;
|
|
579
|
+
return Result.ok({
|
|
580
|
+
matched: !tracedChild.matched,
|
|
581
|
+
trace: this.buildTrace({
|
|
582
|
+
conditionPath: path,
|
|
583
|
+
decisiveNode: node,
|
|
584
|
+
lastEvaluatedLeaf: tracedChild.trace.lastEvaluatedLeaf,
|
|
585
|
+
lastEvaluatedLeafPath:
|
|
586
|
+
tracedChild.trace.lastEvaluatedLeafPath,
|
|
587
|
+
lastEvaluatedLeafResult:
|
|
588
|
+
tracedChild.trace.lastEvaluatedLeafResult,
|
|
589
|
+
}),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return this.conditionEvalErr(
|
|
594
|
+
node,
|
|
595
|
+
new TypeError("Invalid condition node shape"),
|
|
596
|
+
);
|
|
578
597
|
}
|
|
579
598
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
599
|
+
evaluate(node: ConditionNode): Result<boolean, string> {
|
|
600
|
+
try {
|
|
601
|
+
const result = this.evaluateWithTraceInternal(node, "$");
|
|
602
|
+
if (result.isErr()) {
|
|
603
|
+
return Result.err(result.errorOrNull()!);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return Result.ok(result.getOrNull()!.matched);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
return this.conditionEvalErr(node, error);
|
|
609
|
+
}
|
|
584
610
|
}
|
|
585
611
|
|
|
586
|
-
|
|
587
|
-
|
|
612
|
+
evaluateWithTrace(
|
|
613
|
+
node: ConditionNode,
|
|
614
|
+
): Result<TracedConditionEvaluation, string> {
|
|
615
|
+
try {
|
|
616
|
+
return this.evaluateWithTraceInternal(node, "$");
|
|
617
|
+
} catch (error) {
|
|
618
|
+
return this.conditionEvalErr(node, error);
|
|
619
|
+
}
|
|
588
620
|
}
|
|
589
621
|
|
|
590
|
-
|
|
591
|
-
|
|
622
|
+
static extractFields(node: ConditionNode): string[] {
|
|
623
|
+
if (ConditionEvaluatorV1.isLeafNode(node)) {
|
|
624
|
+
return [node.field];
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (ConditionEvaluatorV1.isAndNode(node)) {
|
|
628
|
+
return node.and.flatMap((child) =>
|
|
629
|
+
ConditionEvaluatorV1.extractFields(child),
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (ConditionEvaluatorV1.isOrNode(node)) {
|
|
634
|
+
return node.or.flatMap((child) =>
|
|
635
|
+
ConditionEvaluatorV1.extractFields(child),
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (ConditionEvaluatorV1.isNotNode(node)) {
|
|
640
|
+
return ConditionEvaluatorV1.extractFields(node.not);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return [];
|
|
644
|
+
}
|
|
592
645
|
}
|