Skip to content

Commit b4ad5e1

Browse files
authored
chore(flag-evaluation): improve SET/NOT_SET operator evaluations (#439)
- Refined evaluation logic for "SET" and "NOT_SET" operators to use strict equality checks. - Added tests to ensure correct behavior for missing context fields with "SET" and "NOT_SET" operators, including handling empty string values.
1 parent 67c230c commit b4ad5e1

File tree

4 files changed

+319
-22
lines changed

4 files changed

+319
-22
lines changed

packages/flag-evaluation/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bucketco/flag-evaluation",
3-
"version": "0.1.4",
3+
"version": "0.1.5",
44
"license": "MIT",
55
"repository": {
66
"type": "git",

packages/flag-evaluation/src/index.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -203,25 +203,43 @@ export interface Rule<T extends RuleValue> {
203203
* @return {Record<string, string>} A flattened JSON object with "stringified" keys and values.
204204
*/
205205
export function flattenJSON(data: object): Record<string, string> {
206-
if (Object.keys(data).length === 0) return {};
207-
const result: Record<string, any> = {};
208-
function recurse(cur: any, prop: string) {
209-
if (Object(cur) !== cur) {
210-
result[prop] = cur;
211-
} else if (Array.isArray(cur)) {
212-
const l = cur.length;
213-
for (let i = 0; i < l; i++)
214-
recurse(cur[i], prop ? prop + "." + i : "" + i);
215-
if (l == 0) result[prop] = [];
206+
const result: Record<string, string> = {};
207+
208+
if (Object.keys(data).length === 0) {
209+
return result;
210+
}
211+
212+
function recurse(value: any, prop: string) {
213+
if (value === undefined) {
214+
return;
215+
}
216+
217+
if (value === null) {
218+
result[prop] = "";
219+
} else if (typeof value !== "object") {
220+
result[prop] = String(value);
221+
} else if (Array.isArray(value)) {
222+
if (value.length === 0) {
223+
result[prop] = "";
224+
}
225+
226+
for (let i = 0; i < value.length; i++) {
227+
recurse(value[i], prop ? prop + "." + i : "" + i);
228+
}
216229
} else {
217230
let isEmpty = true;
218-
for (const p in cur) {
231+
232+
for (const p in value) {
219233
isEmpty = false;
220-
recurse(cur[p], prop ? prop + "." + p : p);
234+
recurse(value[p], prop ? prop + "." + p : p);
235+
}
236+
237+
if (isEmpty) {
238+
result[prop] = "";
221239
}
222-
if (isEmpty) result[prop] = {};
223240
}
224241
}
242+
225243
recurse(data, "");
226244
return result;
227245
}
@@ -325,9 +343,9 @@ export function evaluate(
325343
: fieldValueDate < daysAgo.getTime();
326344
}
327345
case "SET":
328-
return fieldValue != "";
346+
return fieldValue !== "";
329347
case "NOT_SET":
330-
return fieldValue == "";
348+
return fieldValue === "";
331349
case "IS":
332350
return fieldValue === value;
333351
case "IS_NOT":
@@ -357,13 +375,17 @@ function evaluateRecursively(
357375
case "constant":
358376
return filter.value;
359377
case "context":
360-
if (!(filter.field in context)) {
378+
if (
379+
!(filter.field in context) &&
380+
filter.operator !== "SET" &&
381+
filter.operator !== "NOT_SET"
382+
) {
361383
missingContextFieldsSet.add(filter.field);
362384
return false;
363385
}
364386

365387
return evaluate(
366-
context[filter.field],
388+
context[filter.field] ?? "",
367389
filter.operator,
368390
filter.values || [],
369391
filter.valueSet,

0 commit comments

Comments
 (0)