Files
5d3d5ef49a [configoptional] Allow wrapping scalar values (#15175)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

Allow `configoptional.Optional` to wrap scalar values using new
interfaces designed specifically to handle similar wrappers around
scalar values.

Follow up to
https://github.com/open-telemetry/opentelemetry-collector/pull/13524.

---------

Co-authored-by: Jade Guiton <[email protected]>
2026-05-19 13:58:33 +00:00

84 lines
2.6 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
import (
"reflect"
"github.com/go-viper/mapstructure/v2"
encoder "go.opentelemetry.io/collector/confmap/internal/mapstructure"
)
func Encode(rawVal any, set MarshalOptions) (any, error) {
enc := encoder.New(EncoderConfig(rawVal, set))
data, err := enc.Encode(rawVal)
if err != nil {
return nil, err
}
return data, nil
}
// EncoderConfig returns a default encoder.EncoderConfig that includes
// an EncodeHook that handles both TextMarshaler and Marshaler
// interfaces.
func EncoderConfig(rawVal any, opts MarshalOptions) *encoder.EncoderConfig {
hooks := []mapstructure.DecodeHookFunc{
encoder.YamlMarshalerHookFunc(),
}
if opts.OpaqueUnredacted {
hooks = append(hooks, encoder.StringTextUnredactedHookFunc())
}
hooks = append(hooks,
encoder.TextMarshalerHookFunc(),
// This must come before unmarshalerHookFunc; the two may both want to trigger
// their corresponding interface for structs implementing both, and the scalar
// interfaces are the ones that will sometimes defer to the non-scalar interfaces.
scalarMarshalerHookFunc(),
marshalerHookFunc(rawVal),
)
return &encoder.EncoderConfig{
EncodeHook: mapstructure.ComposeDecodeHookFunc(hooks...),
}
}
// marshalerHookFunc returns a DecodeHookFuncValue that checks structs that aren't
// the original to see if they implement the Marshaler interface.
func marshalerHookFunc(orig any) mapstructure.DecodeHookFuncValue {
origType := reflect.TypeOf(orig)
return safeWrapDecodeHookFunc(func(from, _ reflect.Value) (any, error) {
if from.Kind() != reflect.Struct {
return from.Interface(), nil
}
// ignore original to avoid infinite loop.
if from.Type() == origType && reflect.DeepEqual(from.Interface(), orig) {
return from.Interface(), nil
}
marshaler, ok := from.Interface().(Marshaler)
if !ok {
return from.Interface(), nil
}
conf := NewFromStringMap(nil)
if err := marshaler.Marshal(conf); err != nil {
return nil, err
}
stringMap := conf.ToStringMap()
if stringMap == nil {
// If conf is still nil after marshaling, we want to encode it as an untyped nil
// instead of a map-typed nil. This ensures the value is a proper null value
// in the final marshaled output instead of an empty map. We hit this case
// when marshaling wrapper structs that have no direct representation
// in the marshaled output that aren't tagged with "squash" on the fields
// they're used on.
return nil, nil
}
return stringMap, nil
})
}