Skip to content

Commit b3b2f89

Browse files
Runtime rules engine ⚙️ (#249)
* extract var for test values * mark old runtime rules as legacy * succint flag setup to one line * DRY tests with helper - more succint * DRY context creation for runtime * use random string for clarity on wrong values * DRY fallback * runtime rule negative case ❌ * add stopgap positive test case (doesn't fail) * import jsonLogic and evaluate runtime rule ✅ * caseinsensitive to parameters Still need caseinsensitive on rule * case-insensitivity ✅ * add complex rule test-cases ✅ * consistent runtime rule test names * add error handling for garbled jsonlogic * add multi-condition case-insensitive check * pretty * Apply simple suggestions from code review Co-authored-by: teddddd <ted@mixpanel.com> * prevent randomString() from accidental matches * consolidate lowercasing of json objects * assert that variant is returned, not just NOT fallback * dry variant-returned assertion * add test for case-insensitive runtime param keys * add "in" negative case for substring & superstring * prettier * Update test/flags/local_flags.js * move helper func to top of test file * prettier
1 parent 323980c commit b3b2f89

5 files changed

Lines changed: 499 additions & 205 deletions

File tree

lib/flags/local_flags.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
* */
1313

1414
const FeatureFlagsProvider = require("./flags");
15-
const { normalizedHash } = require("./utils");
15+
const {
16+
normalizedHash,
17+
lowercaseAllKeysAndValues,
18+
lowercaseLeafNodes,
19+
} = require("./utils");
20+
const { apply } = require("json-logic-js");
1621

1722
class LocalFeatureFlagsProvider extends FeatureFlagsProvider {
1823
/**
@@ -316,13 +321,39 @@ class LocalFeatureFlagsProvider extends FeatureFlagsProvider {
316321
};
317322
}
318323

324+
_extractRuntimeParameters(context) {
325+
const customProperties = context.custom_properties;
326+
if (!customProperties || typeof customProperties !== "object") {
327+
return null;
328+
}
329+
return lowercaseAllKeysAndValues(customProperties);
330+
}
331+
332+
_isRuntimeRuleSatisfied(rollout, context) {
333+
try {
334+
return apply(
335+
lowercaseLeafNodes(rollout.runtime_evaluation_rule),
336+
this._extractRuntimeParameters(context),
337+
);
338+
} catch (error) {
339+
this.logger?.error(`Error evaluating runtime rule: ${error.message}`);
340+
return false;
341+
}
342+
}
343+
319344
_isRuntimeEvaluationSatisfied(rollout, context) {
320-
if (!rollout.runtime_evaluation_definition) {
345+
if (rollout.runtime_evaluation_rule) {
346+
return this._isRuntimeRuleSatisfied(rollout, context);
347+
} else if (rollout.runtime_evaluation_definition) {
348+
return this._isLegacyRuntimeEvaluationSatisfied(rollout, context);
349+
} else {
321350
return true;
322351
}
352+
}
323353

324-
const customProperties = context.custom_properties;
325-
if (!customProperties || typeof customProperties !== "object") {
354+
_isLegacyRuntimeEvaluationSatisfied(rollout, context) {
355+
const customProperties = this._extractRuntimeParameters(context);
356+
if (!customProperties) {
326357
return false;
327358
}
328359

lib/flags/utils.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,41 @@ function generateTraceparent() {
7070
return `${version}-${traceId}-${parentId}-${traceFlags}`;
7171
}
7272

73+
function lowercaseJson(obj, lowercaseKeys) {
74+
if (obj === null || obj === undefined) {
75+
return obj;
76+
} else if (typeof obj === "string") {
77+
return obj.toLowerCase();
78+
} else if (typeof obj === "object") {
79+
if (Array.isArray(obj)) {
80+
return obj.map((item) => lowercaseJson(item, lowercaseKeys));
81+
} else {
82+
return Object.fromEntries(
83+
Object.entries(obj).map(([k, v]) => [
84+
lowercaseKeys ? k.toLowerCase() : k,
85+
lowercaseJson(v, lowercaseKeys),
86+
]),
87+
);
88+
}
89+
} else {
90+
return obj;
91+
}
92+
}
93+
94+
function lowercaseAllKeysAndValues(obj) {
95+
return lowercaseJson(obj, true);
96+
}
97+
98+
function lowercaseLeafNodes(obj) {
99+
return lowercaseJson(obj, false);
100+
}
101+
73102
module.exports = {
74103
EXPOSURE_EVENT,
75104
REQUEST_HEADERS,
76105
normalizedHash,
77106
prepareCommonQueryParams,
78107
generateTraceparent,
108+
lowercaseAllKeysAndValues,
109+
lowercaseLeafNodes,
79110
};

package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"vitest": "^4.0.8"
4141
},
4242
"dependencies": {
43-
"https-proxy-agent": "7.0.6"
43+
"https-proxy-agent": "7.0.6",
44+
"json-logic-js": "2.0.5"
4445
}
4546
}

0 commit comments

Comments
 (0)