Skip to content

Commit a266909

Browse files
fix(nestjs): Add support for Symbol as event name (#17785)
The `@OnEvent` decorator accepts the following types for its argument: ```typescript string | symbol | Array<string | symbol> ``` If a Symbol is included in an array, the code to get the eventName will throw a TypeError (String(event)) This occurs because JavaScript’s Array.prototype.join internally calls ToString on each array element. Per the specification, ToString(Symbol) is not allowed and results in a TypeError. To avoid this issue, do not rely on String(array) or .join() on arrays containing symbols directly. Instead, explicitly convert each element to a string while handling symbols safely. I couldn't find a way to test adding multiple `@OnEvent` so the second part can be tested. It didn't have any tests before (as far as I can tell), but would be nice to add them. doc: https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.join doc: https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tostring
1 parent 632f0b9 commit a266909

File tree

2 files changed

+68
-5
lines changed

2 files changed

+68
-5
lines changed

packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,18 @@ export class SentryNestEventInstrumentation extends InstrumentationBase {
7474
return decoratorResult(target, propertyKey, descriptor);
7575
}
7676

77+
function eventNameFromEvent(event: unknown): string {
78+
if (typeof event === 'string') {
79+
return event;
80+
} else if (Array.isArray(event)) {
81+
return event.map(eventNameFromEvent).join(',');
82+
} else return String(event);
83+
}
84+
7785
const originalHandler = descriptor.value;
7886
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
7987
const handlerName = originalHandler.name || propertyKey;
80-
let eventName = typeof event === 'string' ? event : String(event);
88+
let eventName = eventNameFromEvent(event);
8189

8290
// Instrument the actual handler
8391
descriptor.value = async function (...args: unknown[]) {
@@ -93,7 +101,7 @@ export class SentryNestEventInstrumentation extends InstrumentationBase {
93101
eventName = eventData
94102
.map((data: unknown) => {
95103
if (data && typeof data === 'object' && 'event' in data && data.event) {
96-
return data.event;
104+
return eventNameFromEvent(data.event);
97105
}
98106
return '';
99107
})

packages/nestjs/test/integrations/nest.test.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,72 @@ describe('Nest', () => {
7575

7676
await descriptor.value();
7777

78-
expect(core.startSpan).toHaveBeenCalled();
78+
expect(core.startSpan).toHaveBeenCalledWith(
79+
expect.objectContaining({
80+
name: 'event test.event',
81+
}),
82+
expect.any(Function),
83+
);
7984
expect(originalHandler).toHaveBeenCalled();
8085
});
8186

82-
it('should wrap array event handlers', async () => {
87+
it('should wrap symbol event handlers', async () => {
88+
const decorated = wrappedOnEvent(Symbol('test.event'));
89+
decorated(mockTarget, 'testMethod', descriptor);
90+
91+
await descriptor.value();
92+
93+
expect(core.startSpan).toHaveBeenCalledWith(
94+
expect.objectContaining({
95+
name: 'event Symbol(test.event)',
96+
}),
97+
expect.any(Function),
98+
);
99+
expect(originalHandler).toHaveBeenCalled();
100+
});
101+
102+
it('should wrap string array event handlers', async () => {
83103
const decorated = wrappedOnEvent(['test.event1', 'test.event2']);
84104
decorated(mockTarget, 'testMethod', descriptor);
85105

86106
await descriptor.value();
87107

88-
expect(core.startSpan).toHaveBeenCalled();
108+
expect(core.startSpan).toHaveBeenCalledWith(
109+
expect.objectContaining({
110+
name: 'event test.event1,test.event2',
111+
}),
112+
expect.any(Function),
113+
);
114+
expect(originalHandler).toHaveBeenCalled();
115+
});
116+
117+
it('should wrap symbol array event handlers', async () => {
118+
const decorated = wrappedOnEvent([Symbol('test.event1'), Symbol('test.event2')]);
119+
decorated(mockTarget, 'testMethod', descriptor);
120+
121+
await descriptor.value();
122+
123+
expect(core.startSpan).toHaveBeenCalledWith(
124+
expect.objectContaining({
125+
name: 'event Symbol(test.event1),Symbol(test.event2)',
126+
}),
127+
expect.any(Function),
128+
);
129+
expect(originalHandler).toHaveBeenCalled();
130+
});
131+
132+
it('should wrap mixed type array event handlers', async () => {
133+
const decorated = wrappedOnEvent([Symbol('test.event1'), 'test.event2', Symbol('test.event3')]);
134+
decorated(mockTarget, 'testMethod', descriptor);
135+
136+
await descriptor.value();
137+
138+
expect(core.startSpan).toHaveBeenCalledWith(
139+
expect.objectContaining({
140+
name: 'event Symbol(test.event1),test.event2,Symbol(test.event3)',
141+
}),
142+
expect.any(Function),
143+
);
89144
expect(originalHandler).toHaveBeenCalled();
90145
});
91146

0 commit comments

Comments
 (0)