1// Copyright The OpenTelemetry Authors
2// SPDX-License-Identifier: Apache-2.0
3
4//go:generate stringer -type=ValueKind -trimprefix=ValueKind
5
6package telemetry
7
8import (
9 "bytes"
10 "cmp"
11 "encoding/base64"
12 "encoding/json"
13 "errors"
14 "fmt"
15 "io"
16 "math"
17 "slices"
18 "strconv"
19 "unsafe"
20)
21
22// A Value represents a structured value.
23// A zero value is valid and represents an empty value.
24type Value struct {
25 // Ensure forward compatibility by explicitly making this not comparable.
26 noCmp [0]func() //nolint: unused // This is indeed used.
27
28 // num holds the value for Int64, Float64, and Bool. It holds the length
29 // for String, Bytes, Slice, Map.
30 num uint64
31 // any holds either the KindBool, KindInt64, KindFloat64, stringptr,
32 // bytesptr, sliceptr, or mapptr. If KindBool, KindInt64, or KindFloat64
33 // then the value of Value is in num as described above. Otherwise, it
34 // contains the value wrapped in the appropriate type.
35 any any
36}
37
38type (
39 // sliceptr represents a value in Value.any for KindString Values.
40 stringptr *byte
41 // bytesptr represents a value in Value.any for KindBytes Values.
42 bytesptr *byte
43 // sliceptr represents a value in Value.any for KindSlice Values.
44 sliceptr *Value
45 // mapptr represents a value in Value.any for KindMap Values.
46 mapptr *Attr
47)
48
49// ValueKind is the kind of a [Value].
50type ValueKind int
51
52// ValueKind values.
53const (
54 ValueKindEmpty ValueKind = iota
55 ValueKindBool
56 ValueKindFloat64
57 ValueKindInt64
58 ValueKindString
59 ValueKindBytes
60 ValueKindSlice
61 ValueKindMap
62)
63
64var valueKindStrings = []string{
65 "Empty",
66 "Bool",
67 "Float64",
68 "Int64",
69 "String",
70 "Bytes",
71 "Slice",
72 "Map",
73}
74
75func (k ValueKind) String() string {
76 if k >= 0 && int(k) < len(valueKindStrings) {
77 return valueKindStrings[k]
78 }
79 return "<unknown telemetry.ValueKind>"
80}
81
82// StringValue returns a new [Value] for a string.
83func StringValue(v string) Value {
84 return Value{
85 num: uint64(len(v)),
86 any: stringptr(unsafe.StringData(v)),
87 }
88}
89
90// IntValue returns a [Value] for an int.
91func IntValue(v int) Value { return Int64Value(int64(v)) }
92
93// Int64Value returns a [Value] for an int64.
94func Int64Value(v int64) Value {
95 return Value{num: uint64(v), any: ValueKindInt64}
96}
97
98// Float64Value returns a [Value] for a float64.
99func Float64Value(v float64) Value {
100 return Value{num: math.Float64bits(v), any: ValueKindFloat64}
101}
102
103// BoolValue returns a [Value] for a bool.
104func BoolValue(v bool) Value { //nolint:revive // Not a control flag.
105 var n uint64
106 if v {
107 n = 1
108 }
109 return Value{num: n, any: ValueKindBool}
110}
111
112// BytesValue returns a [Value] for a byte slice. The passed slice must not be
113// changed after it is passed.
114func BytesValue(v []byte) Value {
115 return Value{
116 num: uint64(len(v)),
117 any: bytesptr(unsafe.SliceData(v)),
118 }
119}
120
121// SliceValue returns a [Value] for a slice of [Value]. The passed slice must
122// not be changed after it is passed.
123func SliceValue(vs ...Value) Value {
124 return Value{
125 num: uint64(len(vs)),
126 any: sliceptr(unsafe.SliceData(vs)),
127 }
128}
129
130// MapValue returns a new [Value] for a slice of key-value pairs. The passed
131// slice must not be changed after it is passed.
132func MapValue(kvs ...Attr) Value {
133 return Value{
134 num: uint64(len(kvs)),
135 any: mapptr(unsafe.SliceData(kvs)),
136 }
137}
138
139// AsString returns the value held by v as a string.
140func (v Value) AsString() string {
141 if sp, ok := v.any.(stringptr); ok {
142 return unsafe.String(sp, v.num)
143 }
144 // TODO: error handle
145 return ""
146}
147
148// asString returns the value held by v as a string. It will panic if the Value
149// is not KindString.
150func (v Value) asString() string {
151 return unsafe.String(v.any.(stringptr), v.num)
152}
153
154// AsInt64 returns the value held by v as an int64.
155func (v Value) AsInt64() int64 {
156 if v.Kind() != ValueKindInt64 {
157 // TODO: error handle
158 return 0
159 }
160 return v.asInt64()
161}
162
163// asInt64 returns the value held by v as an int64. If v is not of KindInt64,
164// this will return garbage.
165func (v Value) asInt64() int64 {
166 // Assumes v.num was a valid int64 (overflow not checked).
167 return int64(v.num) // nolint: gosec
168}
169
170// AsBool returns the value held by v as a bool.
171func (v Value) AsBool() bool {
172 if v.Kind() != ValueKindBool {
173 // TODO: error handle
174 return false
175 }
176 return v.asBool()
177}
178
179// asBool returns the value held by v as a bool. If v is not of KindBool, this
180// will return garbage.
181func (v Value) asBool() bool { return v.num == 1 }
182
183// AsFloat64 returns the value held by v as a float64.
184func (v Value) AsFloat64() float64 {
185 if v.Kind() != ValueKindFloat64 {
186 // TODO: error handle
187 return 0
188 }
189 return v.asFloat64()
190}
191
192// asFloat64 returns the value held by v as a float64. If v is not of
193// KindFloat64, this will return garbage.
194func (v Value) asFloat64() float64 { return math.Float64frombits(v.num) }
195
196// AsBytes returns the value held by v as a []byte.
197func (v Value) AsBytes() []byte {
198 if sp, ok := v.any.(bytesptr); ok {
199 return unsafe.Slice((*byte)(sp), v.num)
200 }
201 // TODO: error handle
202 return nil
203}
204
205// asBytes returns the value held by v as a []byte. It will panic if the Value
206// is not KindBytes.
207func (v Value) asBytes() []byte {
208 return unsafe.Slice((*byte)(v.any.(bytesptr)), v.num)
209}
210
211// AsSlice returns the value held by v as a []Value.
212func (v Value) AsSlice() []Value {
213 if sp, ok := v.any.(sliceptr); ok {
214 return unsafe.Slice((*Value)(sp), v.num)
215 }
216 // TODO: error handle
217 return nil
218}
219
220// asSlice returns the value held by v as a []Value. It will panic if the Value
221// is not KindSlice.
222func (v Value) asSlice() []Value {
223 return unsafe.Slice((*Value)(v.any.(sliceptr)), v.num)
224}
225
226// AsMap returns the value held by v as a []Attr.
227func (v Value) AsMap() []Attr {
228 if sp, ok := v.any.(mapptr); ok {
229 return unsafe.Slice((*Attr)(sp), v.num)
230 }
231 // TODO: error handle
232 return nil
233}
234
235// asMap returns the value held by v as a []Attr. It will panic if the
236// Value is not KindMap.
237func (v Value) asMap() []Attr {
238 return unsafe.Slice((*Attr)(v.any.(mapptr)), v.num)
239}
240
241// Kind returns the Kind of v.
242func (v Value) Kind() ValueKind {
243 switch x := v.any.(type) {
244 case ValueKind:
245 return x
246 case stringptr:
247 return ValueKindString
248 case bytesptr:
249 return ValueKindBytes
250 case sliceptr:
251 return ValueKindSlice
252 case mapptr:
253 return ValueKindMap
254 default:
255 return ValueKindEmpty
256 }
257}
258
259// Empty returns if v does not hold any value.
260func (v Value) Empty() bool { return v.Kind() == ValueKindEmpty }
261
262// Equal returns if v is equal to w.
263func (v Value) Equal(w Value) bool {
264 k1 := v.Kind()
265 k2 := w.Kind()
266 if k1 != k2 {
267 return false
268 }
269 switch k1 {
270 case ValueKindInt64, ValueKindBool:
271 return v.num == w.num
272 case ValueKindString:
273 return v.asString() == w.asString()
274 case ValueKindFloat64:
275 return v.asFloat64() == w.asFloat64()
276 case ValueKindSlice:
277 return slices.EqualFunc(v.asSlice(), w.asSlice(), Value.Equal)
278 case ValueKindMap:
279 sv := sortMap(v.asMap())
280 sw := sortMap(w.asMap())
281 return slices.EqualFunc(sv, sw, Attr.Equal)
282 case ValueKindBytes:
283 return bytes.Equal(v.asBytes(), w.asBytes())
284 case ValueKindEmpty:
285 return true
286 default:
287 // TODO: error handle
288 return false
289 }
290}
291
292func sortMap(m []Attr) []Attr {
293 sm := make([]Attr, len(m))
294 copy(sm, m)
295 slices.SortFunc(sm, func(a, b Attr) int {
296 return cmp.Compare(a.Key, b.Key)
297 })
298
299 return sm
300}
301
302// String returns Value's value as a string, formatted like [fmt.Sprint].
303//
304// The returned string is meant for debugging;
305// the string representation is not stable.
306func (v Value) String() string {
307 switch v.Kind() {
308 case ValueKindString:
309 return v.asString()
310 case ValueKindInt64:
311 // Assumes v.num was a valid int64 (overflow not checked).
312 return strconv.FormatInt(int64(v.num), 10) // nolint: gosec
313 case ValueKindFloat64:
314 return strconv.FormatFloat(v.asFloat64(), 'g', -1, 64)
315 case ValueKindBool:
316 return strconv.FormatBool(v.asBool())
317 case ValueKindBytes:
318 return fmt.Sprint(v.asBytes())
319 case ValueKindMap:
320 return fmt.Sprint(v.asMap())
321 case ValueKindSlice:
322 return fmt.Sprint(v.asSlice())
323 case ValueKindEmpty:
324 return "<nil>"
325 default:
326 // Try to handle this as gracefully as possible.
327 //
328 // Don't panic here. The goal here is to have developers find this
329 // first if a slog.Kind is is not handled. It is
330 // preferable to have user's open issue asking why their attributes
331 // have a "unhandled: " prefix than say that their code is panicking.
332 return fmt.Sprintf("<unhandled telemetry.ValueKind: %s>", v.Kind())
333 }
334}
335
336// MarshalJSON encodes v into OTLP formatted JSON.
337func (v *Value) MarshalJSON() ([]byte, error) {
338 switch v.Kind() {
339 case ValueKindString:
340 return json.Marshal(struct {
341 Value string `json:"stringValue"`
342 }{v.asString()})
343 case ValueKindInt64:
344 return json.Marshal(struct {
345 Value string `json:"intValue"`
346 }{strconv.FormatInt(int64(v.num), 10)})
347 case ValueKindFloat64:
348 return json.Marshal(struct {
349 Value float64 `json:"doubleValue"`
350 }{v.asFloat64()})
351 case ValueKindBool:
352 return json.Marshal(struct {
353 Value bool `json:"boolValue"`
354 }{v.asBool()})
355 case ValueKindBytes:
356 return json.Marshal(struct {
357 Value []byte `json:"bytesValue"`
358 }{v.asBytes()})
359 case ValueKindMap:
360 return json.Marshal(struct {
361 Value struct {
362 Values []Attr `json:"values"`
363 } `json:"kvlistValue"`
364 }{struct {
365 Values []Attr `json:"values"`
366 }{v.asMap()}})
367 case ValueKindSlice:
368 return json.Marshal(struct {
369 Value struct {
370 Values []Value `json:"values"`
371 } `json:"arrayValue"`
372 }{struct {
373 Values []Value `json:"values"`
374 }{v.asSlice()}})
375 case ValueKindEmpty:
376 return nil, nil
377 default:
378 return nil, fmt.Errorf("unknown Value kind: %s", v.Kind().String())
379 }
380}
381
382// UnmarshalJSON decodes the OTLP formatted JSON contained in data into v.
383func (v *Value) UnmarshalJSON(data []byte) error {
384 decoder := json.NewDecoder(bytes.NewReader(data))
385
386 t, err := decoder.Token()
387 if err != nil {
388 return err
389 }
390 if t != json.Delim('{') {
391 return errors.New("invalid Value type")
392 }
393
394 for decoder.More() {
395 keyIface, err := decoder.Token()
396 if err != nil {
397 if errors.Is(err, io.EOF) {
398 // Empty.
399 return nil
400 }
401 return err
402 }
403
404 key, ok := keyIface.(string)
405 if !ok {
406 return fmt.Errorf("invalid Value key: %#v", keyIface)
407 }
408
409 switch key {
410 case "stringValue", "string_value":
411 var val string
412 err = decoder.Decode(&val)
413 *v = StringValue(val)
414 case "boolValue", "bool_value":
415 var val bool
416 err = decoder.Decode(&val)
417 *v = BoolValue(val)
418 case "intValue", "int_value":
419 var val protoInt64
420 err = decoder.Decode(&val)
421 *v = Int64Value(val.Int64())
422 case "doubleValue", "double_value":
423 var val float64
424 err = decoder.Decode(&val)
425 *v = Float64Value(val)
426 case "bytesValue", "bytes_value":
427 var val64 string
428 if err := decoder.Decode(&val64); err != nil {
429 return err
430 }
431 var val []byte
432 val, err = base64.StdEncoding.DecodeString(val64)
433 *v = BytesValue(val)
434 case "arrayValue", "array_value":
435 var val struct{ Values []Value }
436 err = decoder.Decode(&val)
437 *v = SliceValue(val.Values...)
438 case "kvlistValue", "kvlist_value":
439 var val struct{ Values []Attr }
440 err = decoder.Decode(&val)
441 *v = MapValue(val.Values...)
442 default:
443 // Skip unknown.
444 continue
445 }
446 // Use first valid. Ignore the rest.
447 return err
448 }
449
450 // Only unknown fields. Return nil without unmarshaling any value.
451 return nil
452}