Skip to content

Commit

Permalink
[confmap] Add mapstructure hook function for unmarshalling unmarshall…
Browse files Browse the repository at this point in the history
…able confmaps

This also moves the Unmarshallable interfact from config to confmap in order to avoid
an import cycle. This makes some sense anyways because the interface is concerned
specifically with unmarshalling a confmap.Conf. The config.Unmarshallable interface
is deprecated and changed to an alias of confmap.Unmarshallable.
  • Loading branch information
djaglowski committed Sep 6, 2022
1 parent c512073 commit fc7460e
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 29 deletions.
5 changes: 1 addition & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 2 additions & 7 deletions config/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
65 changes: 48 additions & 17 deletions confmap/confmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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
}
38 changes: 38 additions & 0 deletions confmap/confmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion receiver/otlpreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit fc7460e

Please sign in to comment.