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