diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ed7439c3b..ee886bfd9ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,11 +24,8 @@ - Deprecate `pcommon.New[Trace|Span]ID` in favor direct conversion. (#6008) - Deprecate `MetricDataPointFlagsImmutable` type. (#6017) - Deprecate `*DataPoint.[Set]FlagsImmutable()` funcs in favor of `*DataPoint.[Set]Flags()`. (#6017) - -### 🚩 Deprecations 🚩 - - Deprecate `LogRecord.FlagsStruct()` and `LogRecord.SetFlagsStruct()` in favor of `LogRecord.Flags()` and `LogRecord.SetFlags()`. (#6007) - +- Deprecate `config.Unmarshallable` in favor of `confmap.Unmarshallable`. (#6029) ### 💡 Enhancements 💡 - Add `skip-get-modules` builder flag to support isolated environment executions (#6009) diff --git a/config/common.go b/config/common.go index c02e38264ca..22a19500360 100644 --- a/config/common.go +++ b/config/common.go @@ -26,13 +26,8 @@ type validatable interface { Validate() error } -// Unmarshallable defines an optional interface for custom configuration unmarshalling. -// A configuration struct can implement this interface to override the default unmarshalling. -type Unmarshallable interface { - // Unmarshal is a function that unmarshalls a confmap.Conf into the unmarshable struct in a custom way. - // The confmap.Conf for this specific component may be nil or empty if no config available. - Unmarshal(component *confmap.Conf) error -} +// Deprecated: [v0.60.0] use confmap.Unmarshallable. +type Unmarshallable confmap.Unmarshallable // DataType is a special Type that represents the data types supported by the collector. We currently support // collecting metrics, traces and logs, this can expand in the future. diff --git a/confmap/confmap.go b/confmap/confmap.go index 3aba027573b..542312f8984 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -57,23 +57,13 @@ func (l *Conf) AllKeys() []string { // Unmarshal unmarshalls the config into a struct. // Tags on the fields of the structure must be properly set. -func (l *Conf) Unmarshal(rawVal interface{}) error { - decoder, err := mapstructure.NewDecoder(decoderConfig(rawVal)) - if err != nil { - return err - } - return decoder.Decode(l.ToStringMap()) +func (l *Conf) Unmarshal(result interface{}) error { + return decoderConfig(l, result, false) } // UnmarshalExact unmarshalls the config into a struct, erroring if a field is nonexistent. -func (l *Conf) UnmarshalExact(rawVal interface{}) error { - dc := decoderConfig(rawVal) - dc.ErrorUnused = true - decoder, err := mapstructure.NewDecoder(dc) - if err != nil { - return err - } - return decoder.Decode(l.ToStringMap()) +func (l *Conf) UnmarshalExact(result interface{}) error { + return decoderConfig(l, result, true) } // Get can retrieve any value given the key to use. @@ -119,10 +109,10 @@ func (l *Conf) ToStringMap() map[string]interface{} { // whose values are nil pointer structs resolved to the zero value of the target struct (see // expandNilStructPointers). A decoder created from this mapstructure.DecoderConfig will decode // its contents to the result argument. -func decoderConfig(result interface{}) *mapstructure.DecoderConfig { - return &mapstructure.DecoderConfig{ +func decoderConfig(m *Conf, result interface{}, errorUnused bool) error { + dc := &mapstructure.DecoderConfig{ + ErrorUnused: errorUnused, Result: result, - Metadata: nil, TagName: "mapstructure", WeaklyTypedInput: true, DecodeHook: mapstructure.ComposeDecodeHookFunc( @@ -131,8 +121,14 @@ func decoderConfig(result interface{}) *mapstructure.DecoderConfig { mapKeyStringToMapKeyTextUnmarshalerHookFunc(), mapstructure.StringToTimeDurationHookFunc(), mapstructure.TextUnmarshallerHookFunc(), + unmarshallableHookFunc(result), ), } + decoder, err := mapstructure.NewDecoder(dc) + if err != nil { + return err + } + return decoder.Decode(m.ToStringMap()) } // In cases where a config has a mapping of something to a struct pointers @@ -209,3 +205,38 @@ func mapKeyStringToMapKeyTextUnmarshalerHookFunc() mapstructure.DecodeHookFuncTy return data, nil } } + +// Provides a mechanism for individual structs to define their own unmarshalling logic, +// by implementing the Unmarshallable interface. +func unmarshallableHookFunc(result interface{}) mapstructure.DecodeHookFuncValue { + return func(from reflect.Value, to reflect.Value) (interface{}, error) { + if _, ok := from.Interface().(map[string]interface{}); !ok { + return from.Interface(), nil + } + + toPtr := to.Addr().Interface() + if _, ok := toPtr.(Unmarshallable); !ok { + return from.Interface(), nil + } + + // Need to ignore the top structure to avoid circular dependency. + if toPtr == result { + return from.Interface(), nil + } + + unmarshaller := reflect.New(to.Type()).Interface().(Unmarshallable) + if err := unmarshaller.Unmarshal(NewFromStringMap(from.Interface().(map[string]interface{}))); err != nil { + return nil, err + } + + return unmarshaller, nil + } +} + +// Unmarshallable defines an optional interface for custom configuration unmarshalling. +// A configuration struct can implement this interface to override the default unmarshalling. +type Unmarshallable interface { + // Unmarshal is a function that unmarshalls a confmap.Conf into the unmarshable struct in a custom way. + // The confmap.Conf for this specific component may be nil or empty if no config available. + Unmarshal(component *Conf) error +} diff --git a/confmap/confmap_test.go b/confmap/confmap_test.go index 1a19752c8e0..882130b5458 100644 --- a/confmap/confmap_test.go +++ b/confmap/confmap_test.go @@ -228,3 +228,41 @@ func newConfFromFile(t testing.TB, fileName string) map[string]interface{} { return NewFromStringMap(data).ToStringMap() } + +type testConfig struct { + Next nextConfig `mapstructure:"next"` + Another string `mapstructure:"another"` +} + +func (tc *testConfig) Unmarshal(component *Conf) error { + if err := component.UnmarshalExact(tc); err != nil { + return err + } + tc.Another += " is not called" + return nil +} + +type nextConfig struct { + String string `mapstructure:"string"` +} + +func (nc *nextConfig) Unmarshal(component *Conf) error { + if err := component.UnmarshalExact(nc); err != nil { + return err + } + nc.String += " is called" + return nil +} + +func TestUnmarshallable(t *testing.T) { + cfgMap := NewFromStringMap(map[string]interface{}{ + "next": map[string]interface{}{ + "string": "make sure this", + }, + "another": "make sure this", + }) + tc := &testConfig{} + assert.NoError(t, cfgMap.UnmarshalExact(tc)) + assert.Equal(t, "make sure this", tc.Another) + assert.Equal(t, "make sure this is called", tc.Next.String) +} diff --git a/receiver/otlpreceiver/config.go b/receiver/otlpreceiver/config.go index 6a4552db25a..e78496a362a 100644 --- a/receiver/otlpreceiver/config.go +++ b/receiver/otlpreceiver/config.go @@ -44,7 +44,7 @@ type Config struct { } var _ config.Receiver = (*Config)(nil) -var _ config.Unmarshallable = (*Config)(nil) +var _ confmap.Unmarshallable = (*Config)(nil) // Validate checks the receiver configuration is valid func (cfg *Config) Validate() error {