Skip to content

Commit b92ee6d

Browse files
committed
arrays are now supported in relay?
1 parent 9d7dfd5 commit b92ee6d

7 files changed

Lines changed: 76 additions & 61 deletions

File tree

packages/core/src/attributes.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ type AttributeTypeMap = {
1515
integer: number;
1616
double: number;
1717
boolean: boolean;
18-
'string[]': Array<string>;
19-
'integer[]': Array<number>;
20-
'double[]': Array<number>;
21-
'boolean[]': Array<boolean>;
18+
array: Array<string> | Array<number> | Array<boolean>;
2219
};
2320

2421
/* Generates a type from the AttributeTypeMap like:
@@ -66,9 +63,9 @@ export function isAttributeObject(maybeObj: unknown): maybeObj is AttributeObjec
6663
/**
6764
* Converts an attribute value to a typed attribute value.
6865
*
69-
* For now, we intentionally only support primitive values and attribute objects with primitive values.
70-
* If @param useFallback is true, we stringify non-primitive values to a string attribute value. Otherwise
71-
* we return `undefined` for unsupported values.
66+
* For now, we support primitive values, homogeneous arrays of primitives, and attribute objects
67+
* wrapping either. If @param useFallback is true, we stringify other non-primitive values to a
68+
* string attribute value. Otherwise we return `undefined` for unsupported values.
7269
*
7370
* @param value - The value of the passed attribute.
7471
* @param useFallback - If true, unsupported values will be stringified to a string attribute value.
@@ -170,17 +167,18 @@ function estimatePrimitiveSizeInBytes(value: Primitive): number {
170167
}
171168

172169
/**
173-
* NOTE: We intentionally do not return anything for non-primitive values:
174-
* - array support will come in the future but if we stringify arrays now,
175-
* sending arrays (unstringified) later will be a subtle breaking change.
170+
* NOTE: We return typed attributes for primitives and homogeneous arrays of primitives:
171+
* - Homogeneous primitive arrays ship with `type: 'array'` (Relay's wire tag for arrays).
172+
* - Mixed-type and nested arrays are not supported and return undefined.
176173
* - Objects are not supported yet and product support is still TBD.
177174
* - We still keep the type signature for TypedAttributeValue wider to avoid a
178-
* breaking change once we add support for non-primitive values.
179-
* - Once we go back to supporting arrays and stringifying all other values,
180-
* we already implemented the serialization logic here:
181-
* https://github.com/getsentry/sentry-javascript/pull/18165
175+
* breaking change once we add support for other non-primitive values.
182176
*/
183177
function getTypedAttributeValue(value: unknown): TypedAttributeValue | void {
178+
if (Array.isArray(value) && isHomogeneousPrimitiveArray(value)) {
179+
return { value, type: 'array' };
180+
}
181+
184182
const primitiveType =
185183
typeof value === 'string'
186184
? 'string'
@@ -201,3 +199,10 @@ function getTypedAttributeValue(value: unknown): TypedAttributeValue | void {
201199
return { value, type: primitiveType };
202200
}
203201
}
202+
203+
function isHomogeneousPrimitiveArray(arr: unknown[]): boolean {
204+
if (arr.length === 0) return true;
205+
const t = typeof arr[0];
206+
if (t !== 'string' && t !== 'number' && t !== 'boolean') return false;
207+
return arr.every(v => typeof v === t);
208+
}

packages/core/src/tracing/spans/captureSpan.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function applySdkMetadataToSegmentSpan(segmentSpanJSON: StreamedSpanJSON, client
9797
if (!integrationNames.length) return;
9898

9999
safeSetSpanJSONAttributes(segmentSpanJSON, {
100-
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: JSON.stringify(integrationNames),
100+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: integrationNames,
101101
});
102102
}
103103

packages/core/test/lib/attributes.test.ts

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -76,28 +76,39 @@ describe('attributeValueToTypedAttributeValue', () => {
7676
);
7777
});
7878

79+
describe('homogeneous primitive arrays', () => {
80+
it.each([
81+
[['foo', 'bar']],
82+
[[1, 2, 3]],
83+
[[true, false, true]],
84+
[[] as unknown[]],
85+
])('emits a typed array attribute for raw value %j', value => {
86+
const result = attributeValueToTypedAttributeValue(value);
87+
expect(result).toStrictEqual({ value, type: 'array' });
88+
});
89+
90+
it('emits a typed array attribute for attribute object values', () => {
91+
const result = attributeValueToTypedAttributeValue({ value: ['foo', 'bar'] });
92+
expect(result).toStrictEqual({ value: ['foo', 'bar'], type: 'array' });
93+
});
94+
});
95+
7996
describe('invalid values (non-primitives)', () => {
8097
it.each([
81-
['foo', 'bar'],
82-
[1, 2, 3],
83-
[true, false, true],
84-
[1, 'foo', true],
85-
{ foo: 'bar' },
86-
() => 'test',
87-
Symbol('test'),
98+
[[1, 'foo', true]],
99+
[{ foo: 'bar' }],
100+
[() => 'test'],
101+
[Symbol('test')],
88102
])('returns undefined for non-primitive raw values (%s)', value => {
89103
const result = attributeValueToTypedAttributeValue(value);
90104
expect(result).toBeUndefined();
91105
});
92106

93107
it.each([
94-
['foo', 'bar'],
95-
[1, 2, 3],
96-
[true, false, true],
97-
[1, 'foo', true],
98-
{ foo: 'bar' },
99-
() => 'test',
100-
Symbol('test'),
108+
[[1, 'foo', true]],
109+
[{ foo: 'bar' }],
110+
[() => 'test'],
111+
[Symbol('test')],
101112
])('returns undefined for non-primitive attribute object values (%s)', value => {
102113
const result = attributeValueToTypedAttributeValue({ value });
103114
expect(result).toBeUndefined();
@@ -189,26 +200,10 @@ describe('attributeValueToTypedAttributeValue', () => {
189200
});
190201

191202
describe('invalid values (non-primitives) - stringified fallback', () => {
192-
it('stringifies string arrays', () => {
193-
const result = attributeValueToTypedAttributeValue(['foo', 'bar'], true);
194-
expect(result).toStrictEqual({
195-
value: '["foo","bar"]',
196-
type: 'string',
197-
});
198-
});
199-
200-
it('stringifies number arrays', () => {
201-
const result = attributeValueToTypedAttributeValue([1, 2, 3], true);
203+
it('stringifies mixed-type arrays (not homogeneous)', () => {
204+
const result = attributeValueToTypedAttributeValue(['foo', 1, true], true);
202205
expect(result).toStrictEqual({
203-
value: '[1,2,3]',
204-
type: 'string',
205-
});
206-
});
207-
208-
it('stringifies boolean arrays', () => {
209-
const result = attributeValueToTypedAttributeValue([true, false, true], true);
210-
expect(result).toStrictEqual({
211-
value: '[true,false,true]',
206+
value: '["foo",1,true]',
212207
type: 'string',
213208
});
214209
});
@@ -425,15 +420,17 @@ describe('serializeAttributes', () => {
425420
describe('invalid (non-primitive) values', () => {
426421
it("doesn't fall back to stringification by default", () => {
427422
const result = serializeAttributes({ foo: { some: 'object' }, bar: [1, 2, 3], baz: () => {} });
428-
expect(result).toStrictEqual({});
423+
expect(result).toStrictEqual({
424+
bar: { type: 'array', value: [1, 2, 3] },
425+
});
429426
});
430427

431428
it('falls back to stringification of unsupported non-primitive values if fallback is true', () => {
432429
const result = serializeAttributes({ foo: { some: 'object' }, bar: [1, 2, 3], baz: () => {} }, true);
433430
expect(result).toStrictEqual({
434431
bar: {
435-
type: 'string',
436-
value: '[1,2,3]',
432+
type: 'array',
433+
value: [1, 2, 3],
437434
},
438435
baz: {
439436
type: 'string',
@@ -445,5 +442,12 @@ describe('serializeAttributes', () => {
445442
},
446443
});
447444
});
445+
446+
it('drops mixed-type arrays by default and stringifies them with fallback', () => {
447+
expect(serializeAttributes({ mixed: ['a', 1] })).toStrictEqual({});
448+
expect(serializeAttributes({ mixed: ['a', 1] }, true)).toStrictEqual({
449+
mixed: { type: 'string', value: '["a",1]' },
450+
});
451+
});
448452
});
449453
});

packages/core/test/lib/logs/internal.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ describe('_INTERNAL_captureLog', () => {
191191
scope.setAttribute('scope_2', { value: 38, unit: 'gigabyte' });
192192
scope.setAttributes({
193193
scope_3: true,
194-
// these are invalid since for now we don't support arrays
195194
scope_4: [1, 2, 3],
196195
scope_5: { value: [true, false, true], unit: 'second' },
197196
});
@@ -229,6 +228,15 @@ describe('_INTERNAL_captureLog', () => {
229228
type: 'boolean',
230229
value: true,
231230
},
231+
scope_4: {
232+
type: 'array',
233+
value: [1, 2, 3],
234+
},
235+
scope_5: {
236+
type: 'array',
237+
value: [true, false, true],
238+
unit: 'second',
239+
},
232240
'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' },
233241
});
234242
});

packages/core/test/lib/tracing/spans/captureSpan.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ describe('captureSpan', () => {
292292
});
293293
});
294294

295-
it('adds sentry.sdk.integrations to segment spans as a JSON-stringified string', () => {
295+
it('adds sentry.sdk.integrations to segment spans as an array attribute', () => {
296296
const client = new TestClient(
297297
getDefaultTestClientOptions({
298298
dsn: 'https://dsn@ingest.f00.f00/1',
@@ -342,8 +342,8 @@ describe('captureSpan', () => {
342342
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: { value: 'sentry.javascript.browser', type: 'string' },
343343
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: { value: '9.0.0', type: 'string' },
344344
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: {
345-
type: 'string',
346-
value: '["InboundFilters","BrowserTracing"]',
345+
type: 'array',
346+
value: ['InboundFilters', 'BrowserTracing'],
347347
},
348348
},
349349
_segmentSpan: span,

packages/core/test/lib/tracing/spans/estimateSize.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ describe('estimateSerializedSpanSizeInBytes', () => {
130130
status: 'ok',
131131
is_segment: false,
132132
attributes: {
133-
'item.ids': { type: 'string[]', value: ['id-001', 'id-002', 'id-003', 'id-004', 'id-005'] },
134-
scores: { type: 'double[]', value: [1.1, 2.2, 3.3, 4.4] },
135-
flags: { type: 'boolean[]', value: [true, false, true] },
133+
'item.ids': { type: 'array', value: ['id-001', 'id-002', 'id-003', 'id-004', 'id-005'] },
134+
scores: { type: 'array', value: [1.1, 2.2, 3.3, 4.4] },
135+
flags: { type: 'array', value: [true, false, true] },
136136
},
137137
};
138138

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,9 @@ describe('spanToJSON', () => {
622622
attr1: { type: 'string', value: 'value1' },
623623
attr2: { type: 'integer', value: 2 },
624624
attr3: { type: 'boolean', value: true },
625+
attr4: { type: 'array', value: [1, 2, 3] },
625626
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: { type: 'string', value: 'test op' },
626627
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: { type: 'string', value: 'auto' },
627-
// notice the absence of `attr4`!
628-
// for now, we don't yet serialize array attributes. This test will fail
629-
// once we allow serializing them.
630628
},
631629
links: [
632630
{

0 commit comments

Comments
 (0)