Skip to content

Commit 3bc86cb

Browse files
committed
adapt attributeValueToTypedAttributeValue
1 parent 9cef5de commit 3bc86cb

File tree

2 files changed

+212
-57
lines changed

2 files changed

+212
-57
lines changed

packages/core/src/attributes.ts

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -51,73 +51,102 @@ export type ValidatedAttributes<T> = {
5151
* Type-guard: The attribute object has the shape the official attribute object (value, type, unit).
5252
* https://develop.sentry.dev/sdk/telemetry/scopes/#setting-attributes
5353
*/
54-
export function isAttributeObject(value: unknown): value is AttributeWithUnit {
55-
if (typeof value !== 'object' || value == null || Array.isArray(value)) {
54+
export function isAttributeObject(maybeObj: unknown): maybeObj is AttributeWithUnit {
55+
if (typeof maybeObj !== 'object' || maybeObj == null || Array.isArray(maybeObj)) {
5656
return false;
5757
}
5858

59-
// MUST have 'value' and 'unit' property
60-
return Object.prototype.hasOwnProperty.call(value, 'value') && Object.prototype.hasOwnProperty.call(value, 'unit');
59+
// MUST have 'value' property
60+
// MAY have 'unit' property
61+
// MUST NOT have other properties
62+
const keys = Object.keys(maybeObj);
63+
64+
// MUST have 'value'
65+
if (!keys.includes('value')) {
66+
return false;
67+
}
68+
69+
// ALLOWED keys: 'value', optionally 'unit'
70+
if (keys.some(k => k !== 'value' && k !== 'unit')) {
71+
return false;
72+
}
73+
74+
// All checks passed
75+
return true;
6176
}
6277

6378
/**
6479
* Converts an attribute value to a typed attribute value.
6580
*
6681
* Does not allow mixed arrays. In case of a mixed array, the value is stringified and the type is 'string'.
82+
* All values besides the supported attribute types (see {@link AttributeTypeMap}) are stringified to a string attribute value.
6783
*
6884
* @param value - The value of the passed attribute.
6985
* @returns The typed attribute.
7086
*/
7187
export function attributeValueToTypedAttributeValue(rawValue: unknown): TypedAttributeValue {
72-
const unit = isAttributeObject(rawValue) ? rawValue.unit : undefined;
73-
const value = isAttributeObject(rawValue) ? rawValue.value : rawValue;
88+
const { value, unit } = isAttributeObject(rawValue) ? rawValue : { value: rawValue, unit: undefined };
89+
return { ...getTypedAttributeValue(value), ...(unit && { unit }) };
90+
}
91+
92+
// Disallow NaN, differentiate between integer and double
93+
const getNumberType: (num: number) => 'integer' | 'double' | null = item =>
94+
Number.isNaN(item) ? null : Number.isInteger(item) ? 'integer' : 'double';
95+
96+
// Only allow string, boolean, or number types
97+
const getPrimitiveType: (item: unknown) => 'string' | 'boolean' | 'integer' | 'double' | null = item =>
98+
typeof item === 'string'
99+
? 'string'
100+
: typeof item === 'boolean'
101+
? 'boolean'
102+
: typeof item === 'number'
103+
? getNumberType(item)
104+
: null;
74105

75-
switch (typeof value) {
106+
function getTypedAttributeValue(val: unknown): TypedAttributeValue {
107+
switch (typeof val) {
76108
case 'number': {
77-
const numberType = getNumberType(value);
109+
const numberType = getNumberType(val);
78110
if (!numberType) {
79111
break;
80112
}
81113
return {
82-
value,
114+
value: val,
83115
type: numberType,
84-
unit,
85116
};
86117
}
87118
case 'boolean':
88119
return {
89-
value,
120+
value: val,
90121
type: 'boolean',
91-
unit,
92122
};
93123
case 'string':
94124
return {
95-
value,
125+
value: val,
96126
type: 'string',
97-
unit,
98127
};
99128
}
100129

101-
if (Array.isArray(value)) {
102-
const coherentType = value.reduce((acc: 'string' | 'boolean' | 'integer' | 'double' | null, item) => {
130+
if (Array.isArray(val)) {
131+
const coherentType = val.reduce((acc: 'string' | 'boolean' | 'integer' | 'double' | null, item) => {
103132
if (!acc || getPrimitiveType(item) !== acc) {
104133
return null;
105134
}
106135
return acc;
107-
}, getPrimitiveType(value[0]));
136+
}, getPrimitiveType(val[0]));
108137

109138
if (coherentType) {
110-
return { value, type: `${coherentType}[]`, unit };
139+
return { value: val, type: `${coherentType}[]` };
111140
}
112141
}
113142

114143
// Fallback: stringify the passed value
115144
let fallbackValue = '';
116145
try {
117-
fallbackValue = JSON.stringify(value) ?? String(value);
146+
fallbackValue = JSON.stringify(val) ?? String(val);
118147
} catch {
119148
try {
120-
fallbackValue = String(value);
149+
fallbackValue = String(val);
121150
} catch {
122151
// ignore
123152
}
@@ -126,20 +155,5 @@ export function attributeValueToTypedAttributeValue(rawValue: unknown): TypedAtt
126155
return {
127156
value: fallbackValue,
128157
type: 'string',
129-
unit,
130158
};
131159
}
132-
133-
// Disallow NaN, differentiate between integer and double
134-
const getNumberType: (num: number) => 'integer' | 'double' | null = item =>
135-
Number.isNaN(item) ? null : Number.isInteger(item) ? 'integer' : 'double';
136-
137-
// Only allow string, boolean, or number types
138-
const getPrimitiveType: (item: unknown) => 'string' | 'boolean' | 'integer' | 'double' | null = item =>
139-
typeof item === 'string'
140-
? 'string'
141-
: typeof item === 'boolean'
142-
? 'boolean'
143-
: typeof item === 'number'
144-
? getNumberType(item)
145-
: null;

0 commit comments

Comments
 (0)