Fix: Handle circular references in flattenAttributes function (#1433)
* Fix: Handle circular references in flattenAttributes function * Update flattenAttributes.test.ts * Update packages/core/src/v3/utils/flattenAttributes.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update packages/core/src/v3/utils/flattenAttributes.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Create brown-laws-rest.md --------- Co-authored-by: Eric Allam <eallam@icloud.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Eric Allam <eric@trigger.dev>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Fix: Handle circular references in flattenAttributes function
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Attributes } from "@opentelemetry/api";
|
||||
|
||||
export const NULL_SENTINEL = "$@null((";
|
||||
export const CIRCULAR_REFERENCE_SENTINEL = "$@circular((";
|
||||
|
||||
export function flattenAttributes(
|
||||
obj: Record<string, unknown> | Array<unknown> | string | boolean | number | null | undefined,
|
||||
prefix?: string
|
||||
prefix?: string ,
|
||||
seen: WeakSet<object> = new WeakSet()
|
||||
): Attributes {
|
||||
const result: Attributes = {};
|
||||
|
||||
@@ -38,13 +40,25 @@ export function flattenAttributes(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for circular reference
|
||||
if (obj !== null && typeof obj === "object" && seen.has(obj)) {
|
||||
result[prefix || ""] = CIRCULAR_REFERENCE_SENTINEL;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add object to seen set
|
||||
if (obj !== null && typeof obj === "object") {
|
||||
seen.add(obj);
|
||||
}
|
||||
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : key}`;
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (typeof value[i] === "object" && value[i] !== null) {
|
||||
// update null check here as well
|
||||
Object.assign(result, flattenAttributes(value[i], `${newPrefix}.[${i}]`));
|
||||
Object.assign(result, flattenAttributes(value[i], `${newPrefix}.[${i}]`,seen));
|
||||
} else {
|
||||
if (value[i] === null) {
|
||||
result[`${newPrefix}.[${i}]`] = NULL_SENTINEL;
|
||||
@@ -55,7 +69,7 @@ export function flattenAttributes(
|
||||
}
|
||||
} else if (isRecord(value)) {
|
||||
// update null check here
|
||||
Object.assign(result, flattenAttributes(value, newPrefix));
|
||||
Object.assign(result, flattenAttributes(value, newPrefix, seen));
|
||||
} else {
|
||||
if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
|
||||
result[newPrefix] = value;
|
||||
@@ -135,8 +149,10 @@ export function unflattenAttributes(
|
||||
}
|
||||
|
||||
const lastPart = parts[parts.length - 1];
|
||||
|
||||
if (lastPart !== undefined) {
|
||||
current[lastPart] = rehydrateNull(value);
|
||||
current[lastPart] = rehydrateNull(rehydrateCircular(value));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +169,13 @@ export function unflattenAttributes(
|
||||
return result;
|
||||
}
|
||||
|
||||
function rehydrateCircular(value: any): any {
|
||||
if (value === CIRCULAR_REFERENCE_SENTINEL) {
|
||||
return "[Circular Reference]";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function primitiveValueOrflattenedAttributes(
|
||||
obj: Record<string, unknown> | Array<unknown> | string | boolean | number | undefined,
|
||||
prefix: string | undefined
|
||||
|
||||
@@ -156,6 +156,29 @@ describe("flattenAttributes", () => {
|
||||
|
||||
expect(flattenAttributes(obj, "retry.byStatus")).toEqual(expected);
|
||||
});
|
||||
|
||||
it("handles circular references correctly", () => {
|
||||
const user = { name: "Alice" };
|
||||
user["blogPosts"] = [{ title: "Post 1", author: user }]; // Circular reference
|
||||
|
||||
const result = flattenAttributes(user);
|
||||
expect(result).toEqual({
|
||||
"name": "Alice",
|
||||
"blogPosts.[0].title": "Post 1",
|
||||
"blogPosts.[0].author": "$@circular((",
|
||||
});
|
||||
});
|
||||
|
||||
it("handles nested circular references correctly", () => {
|
||||
const user = { name: "Bob" };
|
||||
user["friends"] = [user]; // Circular reference
|
||||
|
||||
const result = flattenAttributes(user);
|
||||
expect(result).toEqual({
|
||||
"name": "Bob",
|
||||
"friends.[0]": "$@circular((",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unflattenAttributes", () => {
|
||||
@@ -223,4 +246,18 @@ describe("unflattenAttributes", () => {
|
||||
};
|
||||
expect(unflattenAttributes(flattened)).toEqual(expected);
|
||||
});
|
||||
|
||||
it("rehydrates circular references correctly", () => {
|
||||
const flattened = {
|
||||
"name": "Alice",
|
||||
"blogPosts.[0].title": "Post 1",
|
||||
"blogPosts.[0].author": "$@circular((",
|
||||
};
|
||||
|
||||
const result = unflattenAttributes(flattened);
|
||||
expect(result).toEqual({
|
||||
name: "Alice",
|
||||
blogPosts: [{ title: "Post 1", author: "[Circular Reference]" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user