Skip to content

Commit

Permalink
Simplify unmarshal logic by adding more supported hooks
Browse files Browse the repository at this point in the history
* Add hook that supports "String -> encoding.TextUnmarshaler", e.g. zapcore.Level no longer need special unmarshaling
* Add hook that supports "String -> ComponentID"
* Add a special hook for map[string]interface{} -> map[ComponentID]interface{} to determine duplicates after space trimming, not sure if this error needs this special treatment.

Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>
  • Loading branch information
bogdandrutu committed Oct 20, 2021
1 parent 964c857 commit 60b010d
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 198 deletions.
14 changes: 7 additions & 7 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ type Service struct {

// ServiceTelemetry defines the configurable settings for service telemetry.
type ServiceTelemetry struct {
Logs ServiceTelemetryLogs
Logs ServiceTelemetryLogs `mapstructure:"logs"`
}

func (srvT *ServiceTelemetry) validate() error {
Expand All @@ -171,15 +171,15 @@ func (srvT *ServiceTelemetry) validate() error {
// the collector uses mapstructure and not yaml tags.
type ServiceTelemetryLogs struct {
// Level is the minimum enabled logging level.
Level zapcore.Level
Level zapcore.Level `mapstructure:"level"`

// Development puts the logger in development mode, which changes the
// behavior of DPanicLevel and takes stacktraces more liberally.
Development bool
Development bool `mapstructure:"development"`

// Encoding sets the logger's encoding.
// Valid values are "json" and "console".
Encoding string
Encoding string `mapstructure:"encoding"`
}

func (srvTL *ServiceTelemetryLogs) validate() error {
Expand Down Expand Up @@ -226,9 +226,9 @@ const (
type Pipeline struct {
Name string
InputType DataType
Receivers []ComponentID
Processors []ComponentID
Exporters []ComponentID
Receivers []ComponentID `mapstructure:"receivers"`
Processors []ComponentID `mapstructure:"processors"`
Exporters []ComponentID `mapstructure:"exporters"`
}

// Pipelines is a map of names to Pipelines.
Expand Down
61 changes: 58 additions & 3 deletions config/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ func (l *Map) Unmarshal(rawVal interface{}) error {
}

// UnmarshalExact unmarshalls the config into a struct, erroring if a field is nonexistent.
func (l *Map) UnmarshalExact(intoCfg interface{}) error {
dc := decoderConfig(intoCfg)
func (l *Map) UnmarshalExact(rawVal interface{}) error {
dc := decoderConfig(rawVal)
dc.ErrorUnused = true
decoder, err := mapstructure.NewDecoder(dc)
if err != nil {
Expand Down Expand Up @@ -165,8 +165,11 @@ func decoderConfig(result interface{}) *mapstructure.DecoderConfig {
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
expandNilStructPointers(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapStringToMapComponentIDHookFunc(),
stringToComponentIDHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.TextUnmarshallerHookFunc(),
),
}
}
Expand Down Expand Up @@ -208,3 +211,55 @@ func expandNilStructPointers() mapstructure.DecodeHookFunc {
return from.Interface(), nil
}
}

var componentIDType = reflect.TypeOf(NewComponentID("foo"))

// mapStringToMapComponentIDHookFunc returns a DecodeHookFunc that converts a map[string]interface{} to map[ComponentID]{}.
// This is needed in combination with stringToComponentIDHookFunc since the NewComponentIDFromString may produce
// equal IDs for different strings, and an error needs to be returned in that case, otherwise the last equivalent ID
// overwrites the previous one.
func mapStringToMapComponentIDHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {

if f.Kind() != reflect.Map || f.Key().Kind() != reflect.String {
return data, nil
}

if t.Kind() != reflect.Map || t.Key() != componentIDType {
return data, nil
}

m := make(map[ComponentID]interface{})
for k, v := range data.(map[string]interface{}) {
id, err := NewComponentIDFromString(k)
if err != nil {
return nil, err
}
if _, ok := m[id]; ok {
return nil, fmt.Errorf("duplicate name %q after trimming spaces %v", k, id)
}
m[id] = v
}
return m, nil
}
}

// stringToComponentIDHookFunc returns a DecodeHookFunc that converts strings to ComponentID.
// TODO: Consider to implement encoding.TextUnmarshaler interface on the ID. Does it make it non immutable?
func stringToComponentIDHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != componentIDType {
return data, nil
}
return NewComponentIDFromString(data.(string))
}
}

0 comments on commit 60b010d

Please sign in to comment.