|
| 1 | +import { DEBUG_BUILD } from './debug-build'; |
| 2 | +import { debug } from './utils/debug-logger'; |
| 3 | + |
1 | 4 | export type RawAttributes<T> = T & ValidatedAttributes<T>; |
2 | 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
3 | 6 | export type RawAttribute<T> = T extends { value: any } | { unit: any } ? AttributeObject : T; |
@@ -74,69 +77,65 @@ export function attributeValueToTypedAttributeValue(rawValue: unknown): TypedAtt |
74 | 77 | return { ...getTypedAttributeValue(value), ...(unit && typeof unit === 'string' ? { unit } : {}) }; |
75 | 78 | } |
76 | 79 |
|
77 | | -// Disallow NaN, differentiate between integer and double |
78 | | -const getNumberType: (num: number) => 'integer' | 'double' | null = item => |
79 | | - Number.isNaN(item) ? null : Number.isInteger(item) ? 'integer' : 'double'; |
80 | | - |
81 | 80 | // Only allow string, boolean, or number types |
82 | | -const getPrimitiveType: (item: unknown) => 'string' | 'boolean' | 'integer' | 'double' | null = item => |
| 81 | +const getPrimitiveType: ( |
| 82 | + item: unknown, |
| 83 | +) => keyof Pick<AttributeTypeMap, 'string' | 'integer' | 'double' | 'boolean'> | null = item => |
83 | 84 | typeof item === 'string' |
84 | 85 | ? 'string' |
85 | 86 | : typeof item === 'boolean' |
86 | 87 | ? 'boolean' |
87 | 88 | : typeof item === 'number' |
88 | | - ? getNumberType(item) |
| 89 | + ? Number.isNaN(item) |
| 90 | + ? null |
| 91 | + : Number.isInteger(item) |
| 92 | + ? 'integer' |
| 93 | + : 'double' |
89 | 94 | : null; |
90 | 95 |
|
91 | | -function getTypedAttributeValue(val: unknown): TypedAttributeValue { |
92 | | - switch (typeof val) { |
93 | | - case 'number': { |
94 | | - const numberType = getNumberType(val); |
95 | | - if (!numberType) { |
96 | | - break; |
97 | | - } |
98 | | - return { |
99 | | - value: val, |
100 | | - type: numberType, |
101 | | - }; |
102 | | - } |
103 | | - case 'boolean': |
104 | | - return { |
105 | | - value: val, |
106 | | - type: 'boolean', |
107 | | - }; |
108 | | - case 'string': |
109 | | - return { |
110 | | - value: val, |
111 | | - type: 'string', |
112 | | - }; |
| 96 | +function getTypedAttributeValue(value: unknown): TypedAttributeValue { |
| 97 | + const primitiveType = getPrimitiveType(value); |
| 98 | + if (primitiveType) { |
| 99 | + // @ts-expect-error - TS complains because {@link TypedAttributeValue} is strictly typed to |
| 100 | + // avoid setting the wrong `type` on the attribute value. |
| 101 | + // In this case, getPrimitiveType already does the check but TS doesn't know that. |
| 102 | + // The "clean" alternative is to return an object per `typeof value` case |
| 103 | + // but that would require more bundle size |
| 104 | + // Therefore, we ignore it. |
| 105 | + return { value, type: primitiveType }; |
113 | 106 | } |
114 | 107 |
|
115 | | - if (Array.isArray(val)) { |
116 | | - const coherentType = val.reduce((acc: 'string' | 'boolean' | 'integer' | 'double' | null, item) => { |
| 108 | + if (Array.isArray(value)) { |
| 109 | + const coherentArrayType = value.reduce((acc: 'string' | 'boolean' | 'integer' | 'double' | null, item) => { |
117 | 110 | if (!acc || getPrimitiveType(item) !== acc) { |
118 | 111 | return null; |
119 | 112 | } |
120 | 113 | return acc; |
121 | | - }, getPrimitiveType(val[0])); |
| 114 | + }, getPrimitiveType(value[0])); |
122 | 115 |
|
123 | | - if (coherentType) { |
124 | | - return { value: val, type: `${coherentType}[]` }; |
| 116 | + if (coherentArrayType) { |
| 117 | + return { value, type: `${coherentArrayType}[]` }; |
125 | 118 | } |
126 | 119 | } |
127 | 120 |
|
128 | 121 | // Fallback: stringify the passed value |
129 | 122 | let fallbackValue = ''; |
130 | 123 | try { |
131 | | - fallbackValue = JSON.stringify(val) ?? String(val); |
| 124 | + fallbackValue = JSON.stringify(value) ?? String(value); |
132 | 125 | } catch { |
133 | 126 | try { |
134 | | - fallbackValue = String(val); |
| 127 | + fallbackValue = String(value); |
135 | 128 | } catch { |
| 129 | + DEBUG_BUILD && debug.warn('Failed to stringify attribute value', value); |
136 | 130 | // ignore |
137 | 131 | } |
138 | 132 | } |
139 | 133 |
|
| 134 | + // This is quite a low-quality message but we cannot safely log the original `value` |
| 135 | + // here due to String() or JSON.stringify() potentially throwing. |
| 136 | + DEBUG_BUILD && |
| 137 | + debug.log(`Stringified attribute value to ${fallbackValue} because it's not a supported attribute value type`); |
| 138 | + |
140 | 139 | return { |
141 | 140 | value: fallbackValue, |
142 | 141 | type: 'string', |
|
0 commit comments