Skip to content

Commit df0283d

Browse files
committed
fix(core): Ensure normalizedRequest on sdkProcessingMetadata is merged
1 parent bac6c93 commit df0283d

File tree

5 files changed

+223
-11
lines changed

5 files changed

+223
-11
lines changed

packages/core/src/scope.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
import { dateTimestampInSeconds, generatePropagationContext, isPlainObject, logger, uuid4 } from '@sentry/utils';
2525

2626
import { updateSession } from './session';
27+
import { mergeSdkProcessingMetadata } from './utils/applyScopeDataToEvent';
2728
import { _getSpanForScope, _setSpanForScope } from './utils/spanOnScope';
2829

2930
/**
@@ -479,8 +480,7 @@ class ScopeClass implements ScopeInterface {
479480
* @inheritDoc
480481
*/
481482
public setSDKProcessingMetadata(newData: { [key: string]: unknown }): this {
482-
this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
483-
483+
this._sdkProcessingMetadata = mergeSdkProcessingMetadata(this._sdkProcessingMetadata, newData);
484484
return this;
485485
}
486486

packages/core/src/utils/applyScopeDataToEvent.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ export function mergeScopeData(data: ScopeData, mergeData: ScopeData): void {
4646
mergeAndOverwriteScopeData(data, 'tags', tags);
4747
mergeAndOverwriteScopeData(data, 'user', user);
4848
mergeAndOverwriteScopeData(data, 'contexts', contexts);
49-
mergeAndOverwriteScopeData(data, 'sdkProcessingMetadata', sdkProcessingMetadata);
49+
50+
data.sdkProcessingMetadata = mergeSdkProcessingMetadata(data.sdkProcessingMetadata, sdkProcessingMetadata);
5051

5152
if (level) {
5253
data.level = level;
@@ -115,6 +116,35 @@ export function mergeArray<Prop extends 'breadcrumbs' | 'fingerprint'>(
115116
event[prop] = merged.length ? merged : undefined;
116117
}
117118

119+
/**
120+
* Merge new SDK processing metadata into existing data.
121+
* New data will overwrite existing data.
122+
* `normalizedRequest` is special handled and will also be merged.
123+
*/
124+
export function mergeSdkProcessingMetadata(
125+
sdkProcessingMetadata: ScopeData['sdkProcessingMetadata'],
126+
newSdkProcessingMetadata: ScopeData['sdkProcessingMetadata'],
127+
): ScopeData['sdkProcessingMetadata'] {
128+
// We want to merge `normalizedRequest` to avoid some partial entry on the scope
129+
// overwriting potentially more complete data on the isolation scope
130+
const normalizedRequestBefore = sdkProcessingMetadata['normalizedRequest'];
131+
const normalizedRequest = newSdkProcessingMetadata['normalizedRequest'];
132+
133+
const newData = {
134+
...sdkProcessingMetadata,
135+
...newSdkProcessingMetadata,
136+
};
137+
138+
if (normalizedRequestBefore || normalizedRequest) {
139+
newData['normalizedRequest'] = {
140+
...(normalizedRequestBefore || {}),
141+
...(normalizedRequest || {}),
142+
};
143+
}
144+
145+
return newData;
146+
}
147+
118148
function applyDataToEvent(event: Event, data: ScopeData): void {
119149
const { extra, tags, user, contexts, level, transactionName } = data;
120150

packages/core/test/lib/scope.test.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,47 @@ describe('Scope', () => {
204204
expect(scope['_user']).toEqual({});
205205
});
206206

207-
test('setProcessingMetadata', () => {
208-
const scope = new Scope();
209-
scope.setSDKProcessingMetadata({ dogs: 'are great!' });
210-
expect(scope['_sdkProcessingMetadata'].dogs).toEqual('are great!');
207+
describe('setProcessingMetadata', () => {
208+
test('it works with no initial data', () => {
209+
const scope = new Scope();
210+
scope.setSDKProcessingMetadata({ dogs: 'are great!' });
211+
expect(scope['_sdkProcessingMetadata'].dogs).toEqual('are great!');
212+
});
213+
214+
test('it overwrites arbitrary data', () => {
215+
const scope = new Scope();
216+
scope.setSDKProcessingMetadata({ dogs: 'are great!' });
217+
scope.setSDKProcessingMetadata({ dogs: 'are really great!' });
218+
scope.setSDKProcessingMetadata({ cats: 'are also great!' });
219+
scope.setSDKProcessingMetadata({ obj: { nested: 'value1' } });
220+
scope.setSDKProcessingMetadata({ obj: { nested2: 'value2' } });
221+
222+
expect(scope['_sdkProcessingMetadata']).toEqual({
223+
dogs: 'are really great!',
224+
cats: 'are also great!',
225+
obj: { nested2: 'value2' },
226+
});
227+
});
228+
229+
test('it merges normalizedRequest data', () => {
230+
const scope = new Scope();
231+
scope.setSDKProcessingMetadata({
232+
normalizedRequest: {
233+
url: 'value1',
234+
method: 'value1',
235+
},
236+
});
237+
scope.setSDKProcessingMetadata({
238+
normalizedRequest: {
239+
url: 'value2',
240+
headers: {},
241+
},
242+
});
243+
244+
expect(scope['_sdkProcessingMetadata']).toEqual({
245+
normalizedRequest: { url: 'value2', method: 'value1', headers: {} },
246+
});
247+
});
211248
});
212249

213250
test('set and get propagation context', () => {

packages/core/test/lib/utils/applyScopeDataToEvent.test.ts

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
mergeAndOverwriteScopeData,
66
mergeArray,
77
mergeScopeData,
8+
mergeSdkProcessingMetadata,
89
} from '../../../src/utils/applyScopeDataToEvent';
910

1011
describe('mergeArray', () => {
@@ -134,7 +135,15 @@ describe('mergeScopeData', () => {
134135
contexts: { os: { name: 'os1' }, culture: { display_name: 'name1' } },
135136
attachments: [attachment1],
136137
propagationContext: { spanId: '1', traceId: '1' },
137-
sdkProcessingMetadata: { aa: 'aa', bb: 'aa' },
138+
sdkProcessingMetadata: {
139+
aa: 'aa',
140+
bb: 'aa',
141+
obj: { key: 'value' },
142+
normalizedRequest: {
143+
url: 'oldUrl',
144+
method: 'oldMethod',
145+
},
146+
},
138147
fingerprint: ['aa', 'bb'],
139148
};
140149
const data2: ScopeData = {
@@ -146,7 +155,17 @@ describe('mergeScopeData', () => {
146155
contexts: { os: { name: 'os2' } },
147156
attachments: [attachment2, attachment3],
148157
propagationContext: { spanId: '2', traceId: '2' },
149-
sdkProcessingMetadata: { bb: 'bb', cc: 'bb' },
158+
sdkProcessingMetadata: {
159+
bb: 'bb',
160+
cc: 'bb',
161+
// Regular objects are not deep merged
162+
obj: { key2: 'value2' },
163+
// Only normalizedRequest is deep merged
164+
normalizedRequest: {
165+
url: 'newUrl',
166+
headers: {},
167+
},
168+
},
150169
fingerprint: ['cc'],
151170
};
152171
mergeScopeData(data1, data2);
@@ -159,12 +178,136 @@ describe('mergeScopeData', () => {
159178
contexts: { os: { name: 'os2' }, culture: { display_name: 'name1' } },
160179
attachments: [attachment1, attachment2, attachment3],
161180
propagationContext: { spanId: '2', traceId: '2' },
162-
sdkProcessingMetadata: { aa: 'aa', bb: 'bb', cc: 'bb' },
181+
sdkProcessingMetadata: {
182+
aa: 'aa',
183+
bb: 'bb',
184+
cc: 'bb',
185+
obj: { key2: 'value2' },
186+
normalizedRequest: {
187+
url: 'newUrl',
188+
method: 'oldMethod',
189+
headers: {},
190+
},
191+
},
163192
fingerprint: ['aa', 'bb', 'cc'],
164193
});
165194
});
166195
});
167196

197+
describe('mergeSdkProcessingMetadata', () => {
198+
it('works with empty objects', () => {
199+
const oldData = {};
200+
const newData = {};
201+
202+
const actual = mergeSdkProcessingMetadata(oldData, newData);
203+
204+
expect(actual).toEqual({});
205+
expect(actual).not.toBe(oldData);
206+
expect(actual).not.toBe(newData);
207+
});
208+
209+
it('works with arbitrary data', () => {
210+
const oldData = {
211+
old1: 'old1',
212+
old2: 'old2',
213+
obj: { key: 'value' },
214+
};
215+
const newData = {
216+
new1: 'new1',
217+
old2: 'new2',
218+
obj: { key2: 'value2' },
219+
};
220+
221+
const actual = mergeSdkProcessingMetadata(oldData, newData);
222+
223+
expect(actual).toEqual({
224+
old1: 'old1',
225+
old2: 'new2',
226+
new1: 'new1',
227+
obj: { key2: 'value2' },
228+
});
229+
expect(actual).not.toBe(oldData);
230+
expect(actual).not.toBe(newData);
231+
});
232+
233+
it('merges normalizedRequest', () => {
234+
const oldData = {
235+
old1: 'old1',
236+
normalizedRequest: {
237+
url: 'oldUrl',
238+
method: 'oldMethod',
239+
},
240+
};
241+
const newData = {
242+
new1: 'new1',
243+
normalizedRequest: {
244+
url: 'newUrl',
245+
headers: {},
246+
},
247+
};
248+
249+
const actual = mergeSdkProcessingMetadata(oldData, newData);
250+
251+
expect(actual).toEqual({
252+
old1: 'old1',
253+
new1: 'new1',
254+
normalizedRequest: {
255+
url: 'newUrl',
256+
method: 'oldMethod',
257+
headers: {},
258+
},
259+
});
260+
});
261+
262+
it('keeps existing normalizedRequest', () => {
263+
const oldData = {
264+
old1: 'old1',
265+
normalizedRequest: {
266+
url: 'oldUrl',
267+
method: 'oldMethod',
268+
},
269+
};
270+
const newData = {
271+
new1: 'new1',
272+
};
273+
274+
const actual = mergeSdkProcessingMetadata(oldData, newData);
275+
276+
expect(actual).toEqual({
277+
old1: 'old1',
278+
new1: 'new1',
279+
normalizedRequest: {
280+
url: 'oldUrl',
281+
method: 'oldMethod',
282+
},
283+
});
284+
});
285+
286+
it('adds new normalizedRequest', () => {
287+
const oldData = {
288+
old1: 'old1',
289+
};
290+
const newData = {
291+
new1: 'new1',
292+
normalizedRequest: {
293+
url: 'newUrl',
294+
method: 'newMethod',
295+
},
296+
};
297+
298+
const actual = mergeSdkProcessingMetadata(oldData, newData);
299+
300+
expect(actual).toEqual({
301+
old1: 'old1',
302+
new1: 'new1',
303+
normalizedRequest: {
304+
url: 'newUrl',
305+
method: 'newMethod',
306+
},
307+
});
308+
});
309+
});
310+
168311
describe('applyScopeDataToEvent', () => {
169312
it("doesn't apply scope.transactionName to transaction events", () => {
170313
const data: ScopeData = {

packages/types/src/scope.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ export interface Scope {
217217
clearAttachments(): this;
218218

219219
/**
220-
* Add data which will be accessible during event processing but won't get sent to Sentry
220+
* Add data which will be accessible during event processing but won't get sent to Sentry.
221+
*
222+
* TODO(v9): We should type this stricter, so that e.g. `normalizedRequest` is strictly typed.
221223
*/
222224
setSDKProcessingMetadata(newData: { [key: string]: unknown }): this;
223225

0 commit comments

Comments
 (0)