Skip to content

Commit 9a5e6e5

Browse files
yadavshubham01ericallamcoderabbitai[bot]
authored
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>
1 parent c31700a commit 9a5e6e5

File tree

3 files changed

+69
-4
lines changed

3 files changed

+69
-4
lines changed

.changeset/brown-laws-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/core": patch
3+
---
4+
5+
Fix: Handle circular references in flattenAttributes function

packages/core/src/v3/utils/flattenAttributes.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Attributes } from "@opentelemetry/api";
22

33
export const NULL_SENTINEL = "$@null((";
4+
export const CIRCULAR_REFERENCE_SENTINEL = "$@circular((";
45

56
export function flattenAttributes(
67
obj: Record<string, unknown> | Array<unknown> | string | boolean | number | null | undefined,
7-
prefix?: string
8+
prefix?: string ,
9+
seen: WeakSet<object> = new WeakSet()
810
): Attributes {
911
const result: Attributes = {};
1012

@@ -38,13 +40,25 @@ export function flattenAttributes(
3840
return result;
3941
}
4042

43+
// Check for circular reference
44+
if (obj !== null && typeof obj === "object" && seen.has(obj)) {
45+
result[prefix || ""] = CIRCULAR_REFERENCE_SENTINEL;
46+
return result;
47+
}
48+
49+
// Add object to seen set
50+
if (obj !== null && typeof obj === "object") {
51+
seen.add(obj);
52+
}
53+
54+
4155
for (const [key, value] of Object.entries(obj)) {
4256
const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : key}`;
4357
if (Array.isArray(value)) {
4458
for (let i = 0; i < value.length; i++) {
4559
if (typeof value[i] === "object" && value[i] !== null) {
4660
// update null check here as well
47-
Object.assign(result, flattenAttributes(value[i], `${newPrefix}.[${i}]`));
61+
Object.assign(result, flattenAttributes(value[i], `${newPrefix}.[${i}]`,seen));
4862
} else {
4963
if (value[i] === null) {
5064
result[`${newPrefix}.[${i}]`] = NULL_SENTINEL;
@@ -55,7 +69,7 @@ export function flattenAttributes(
5569
}
5670
} else if (isRecord(value)) {
5771
// update null check here
58-
Object.assign(result, flattenAttributes(value, newPrefix));
72+
Object.assign(result, flattenAttributes(value, newPrefix, seen));
5973
} else {
6074
if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
6175
result[newPrefix] = value;
@@ -135,8 +149,10 @@ export function unflattenAttributes(
135149
}
136150

137151
const lastPart = parts[parts.length - 1];
152+
138153
if (lastPart !== undefined) {
139-
current[lastPart] = rehydrateNull(value);
154+
current[lastPart] = rehydrateNull(rehydrateCircular(value));
155+
140156
}
141157
}
142158

@@ -153,6 +169,13 @@ export function unflattenAttributes(
153169
return result;
154170
}
155171

172+
function rehydrateCircular(value: any): any {
173+
if (value === CIRCULAR_REFERENCE_SENTINEL) {
174+
return "[Circular Reference]";
175+
}
176+
return value;
177+
}
178+
156179
export function primitiveValueOrflattenedAttributes(
157180
obj: Record<string, unknown> | Array<unknown> | string | boolean | number | undefined,
158181
prefix: string | undefined

packages/core/test/flattenAttributes.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,29 @@ describe("flattenAttributes", () => {
156156

157157
expect(flattenAttributes(obj, "retry.byStatus")).toEqual(expected);
158158
});
159+
160+
it("handles circular references correctly", () => {
161+
const user = { name: "Alice" };
162+
user["blogPosts"] = [{ title: "Post 1", author: user }]; // Circular reference
163+
164+
const result = flattenAttributes(user);
165+
expect(result).toEqual({
166+
"name": "Alice",
167+
"blogPosts.[0].title": "Post 1",
168+
"blogPosts.[0].author": "$@circular((",
169+
});
170+
});
171+
172+
it("handles nested circular references correctly", () => {
173+
const user = { name: "Bob" };
174+
user["friends"] = [user]; // Circular reference
175+
176+
const result = flattenAttributes(user);
177+
expect(result).toEqual({
178+
"name": "Bob",
179+
"friends.[0]": "$@circular((",
180+
});
181+
});
159182
});
160183

161184
describe("unflattenAttributes", () => {
@@ -223,4 +246,18 @@ describe("unflattenAttributes", () => {
223246
};
224247
expect(unflattenAttributes(flattened)).toEqual(expected);
225248
});
249+
250+
it("rehydrates circular references correctly", () => {
251+
const flattened = {
252+
"name": "Alice",
253+
"blogPosts.[0].title": "Post 1",
254+
"blogPosts.[0].author": "$@circular((",
255+
};
256+
257+
const result = unflattenAttributes(flattened);
258+
expect(result).toEqual({
259+
name: "Alice",
260+
blogPosts: [{ title: "Post 1", author: "[Circular Reference]" }],
261+
});
262+
});
226263
});

0 commit comments

Comments
 (0)