Skip to content

Commit d3c51b0

Browse files
committed
[confmap] confmap honors Unmarshal methods on config embedded structs.
1 parent 6be5fe9 commit d3c51b0

File tree

5 files changed

+122
-28
lines changed

5 files changed

+122
-28
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: confmap
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: confmap honors `Unmarshal` methods on config embedded structs.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [6671]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: []

cmd/mdatagen/loader_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func Test_loadMetadata(t *testing.T) {
245245
},
246246
{
247247
name: "testdata/unknown_value_type.yaml",
248-
wantErr: "1 error(s) decoding:\n\n* error decoding 'metrics[system.cpu.time]': 1 error(s) decoding:\n\n* error decoding 'sum': 1 error(s) decoding:\n\n* error decoding 'value_type': invalid value_type: \"unknown\"",
248+
wantErr: "1 error(s) decoding:\n\n* error decoding 'metrics[system.cpu.time]': 1 error(s) decoding:\n\n* error decoding 'sum': invalid value_type: \"unknown\"",
249249
},
250250
{
251251
name: "testdata/no_aggregation.yaml",
@@ -255,7 +255,7 @@ func Test_loadMetadata(t *testing.T) {
255255
{
256256
name: "testdata/invalid_aggregation.yaml",
257257
want: metadata{},
258-
wantErr: "1 error(s) decoding:\n\n* error decoding 'metrics[default.metric]': 1 error(s) decoding:\n\n* error decoding 'sum': 1 error(s) decoding:\n\n* error decoding 'aggregation_temporality': invalid aggregation: \"invalidaggregation\"",
258+
wantErr: "1 error(s) decoding:\n\n* error decoding 'metrics[default.metric]': 1 error(s) decoding:\n\n* error decoding 'sum': error decoding '': invalid aggregation: \"invalidaggregation\"",
259259
},
260260
{
261261
name: "testdata/invalid_type_attr.yaml",

cmd/mdatagen/metricdata.go

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,22 @@ type MetricData interface {
2828
type AggregationTemporality struct {
2929
// Aggregation describes if the aggregator reports delta changes
3030
// since last report time, or cumulative changes since a fixed start time.
31-
Aggregation pmetric.AggregationTemporality
32-
}
33-
34-
// UnmarshalText implements the encoding.TextUnmarshaler interface.
35-
func (agg *AggregationTemporality) UnmarshalText(text []byte) error {
36-
switch vtStr := string(text); vtStr {
37-
case "cumulative":
38-
agg.Aggregation = pmetric.AggregationTemporalityCumulative
39-
case "delta":
40-
agg.Aggregation = pmetric.AggregationTemporalityDelta
41-
default:
42-
return fmt.Errorf("invalid aggregation: %q", vtStr)
31+
Aggregation pmetric.AggregationTemporality `mapstructure:"aggregation_temporality"`
32+
}
33+
34+
func (agg *AggregationTemporality) Unmarshal(parser *confmap.Conf) error {
35+
v := parser.Get("aggregation_temporality")
36+
if aggValue, ok := v.(pmetric.AggregationTemporality); ok {
37+
agg.Aggregation = aggValue
38+
} else {
39+
switch v {
40+
case "cumulative":
41+
agg.Aggregation = pmetric.AggregationTemporalityCumulative
42+
case "delta":
43+
agg.Aggregation = pmetric.AggregationTemporalityDelta
44+
default:
45+
return fmt.Errorf("invalid aggregation: %q", v)
46+
}
4347
}
4448
return nil
4549
}
@@ -73,22 +77,17 @@ func (mit MetricInputType) String() string {
7377
// MetricValueType defines the metric number type.
7478
type MetricValueType struct {
7579
// ValueType is type of the metric number, options are "double", "int".
76-
ValueType pmetric.NumberDataPointValueType
80+
ValueType pmetric.NumberDataPointValueType `mapstructure:"value_type"`
7781
}
7882

7983
func (mvt *MetricValueType) Unmarshal(parser *confmap.Conf) error {
8084
if !parser.IsSet("value_type") {
8185
return errors.New("missing required field: `value_type`")
8286
}
83-
return nil
84-
}
85-
86-
// UnmarshalText implements the encoding.TextUnmarshaler interface.
87-
func (mvt *MetricValueType) UnmarshalText(text []byte) error {
88-
switch vtStr := string(text); vtStr {
89-
case "int":
87+
switch vtStr := parser.Get("value_type"); vtStr {
88+
case "int", pmetric.NumberDataPointValueTypeInt:
9089
mvt.ValueType = pmetric.NumberDataPointValueTypeInt
91-
case "double":
90+
case "double", pmetric.NumberDataPointValueTypeDouble:
9291
mvt.ValueType = pmetric.NumberDataPointValueTypeDouble
9392
default:
9493
return fmt.Errorf("invalid value_type: %q", vtStr)
@@ -116,7 +115,7 @@ func (mvt MetricValueType) BasicType() string {
116115
}
117116

118117
type gauge struct {
119-
MetricValueType `mapstructure:"value_type"`
118+
MetricValueType `mapstructure:",squash"`
120119
MetricInputType `mapstructure:",squash"`
121120
}
122121

@@ -141,9 +140,9 @@ func (d gauge) HasAggregated() bool {
141140
}
142141

143142
type sum struct {
144-
AggregationTemporality `mapstructure:"aggregation_temporality"`
143+
AggregationTemporality `mapstructure:",squash"`
145144
Mono `mapstructure:",squash"`
146-
MetricValueType `mapstructure:"value_type"`
145+
MetricValueType `mapstructure:",squash"`
147146
MetricInputType `mapstructure:",squash"`
148147
}
149148

confmap/confmap.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ func decodeConfig(m *Conf, result any, errorUnused bool) error {
157157
mapstructure.StringToTimeDurationHookFunc(),
158158
mapstructure.TextUnmarshallerHookFunc(),
159159
unmarshalerHookFunc(result),
160+
embeddedStructsHookFunc(result),
160161
zeroSliceHookFunc(),
161162
),
162163
}
@@ -261,6 +262,45 @@ func mapKeyStringToMapKeyTextUnmarshalerHookFunc() mapstructure.DecodeHookFuncTy
261262
}
262263
}
263264

265+
func embeddedStructsHookFunc(_ any) mapstructure.DecodeHookFuncValue {
266+
return func(from reflect.Value, to reflect.Value) (any, error) {
267+
if to.Type().Kind() != reflect.Struct {
268+
return from.Interface(), nil
269+
}
270+
271+
finalFrom := from.Interface()
272+
273+
for i := 0; i < to.Type().NumField(); i++ {
274+
if to.Type().Field(i).IsExported() && to.Type().Field(i).Anonymous {
275+
f := to.Field(i)
276+
if unmarshaler, ok := f.Addr().Interface().(Unmarshaler); ok {
277+
fromMap, ok := finalFrom.(map[string]any)
278+
if !ok {
279+
conf := New()
280+
if err := conf.Marshal(finalFrom); err != nil {
281+
return nil, err
282+
}
283+
fromMap = conf.ToStringMap()
284+
}
285+
if err := unmarshaler.Unmarshal(NewFromStringMap(fromMap)); err != nil {
286+
return nil, err
287+
}
288+
conf := New()
289+
if err := conf.Marshal(unmarshaler); err != nil {
290+
return nil, err
291+
}
292+
resultMap := conf.ToStringMap()
293+
for k, v := range resultMap {
294+
fromMap[k] = v
295+
}
296+
finalFrom = fromMap
297+
}
298+
}
299+
}
300+
return finalFrom, nil
301+
}
302+
}
303+
264304
// Provides a mechanism for individual structs to define their own unmarshal logic,
265305
// by implementing the Unmarshaler interface.
266306
func unmarshalerHookFunc(result any) mapstructure.DecodeHookFuncValue {

confmap/confmap_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,34 @@ func newConfFromFile(t testing.TB, fileName string) map[string]any {
309309
}
310310

311311
type testConfig struct {
312-
Next *nextConfig `mapstructure:"next"`
313-
Another string `mapstructure:"another"`
312+
Next *nextConfig `mapstructure:"next"`
313+
Another string `mapstructure:"another"`
314+
EmbeddedConfig `mapstructure:",squash"`
315+
EmbeddedConfig2 `mapstructure:",squash"`
316+
}
317+
318+
type EmbeddedConfig struct {
319+
Some string `mapstructure:"some"`
320+
}
321+
322+
func (ec *EmbeddedConfig) Unmarshal(component *Conf) error {
323+
if err := component.Unmarshal(ec, WithIgnoreUnused()); err != nil {
324+
return err
325+
}
326+
ec.Some += " is also called"
327+
return nil
328+
}
329+
330+
type EmbeddedConfig2 struct {
331+
Some2 string `mapstructure:"some_2"`
332+
}
333+
334+
func (ec *EmbeddedConfig2) Unmarshal(component *Conf) error {
335+
if err := component.Unmarshal(ec, WithIgnoreUnused()); err != nil {
336+
return err
337+
}
338+
ec.Some2 += " also called2"
339+
return nil
314340
}
315341

316342
func (tc *testConfig) Unmarshal(component *Conf) error {
@@ -340,12 +366,16 @@ func TestUnmarshaler(t *testing.T) {
340366
"string": "make sure this",
341367
},
342368
"another": "make sure this",
369+
"some": "make sure this",
370+
"some_2": "this better be",
343371
})
344372

345373
tc := &testConfig{}
346374
assert.NoError(t, cfgMap.Unmarshal(tc))
347375
assert.Equal(t, "make sure this", tc.Another)
348376
assert.Equal(t, "make sure this is called", tc.Next.String)
377+
assert.Equal(t, "make sure this is also called", tc.EmbeddedConfig.Some)
378+
assert.Equal(t, "this better be also called2", tc.EmbeddedConfig2.Some2)
349379
}
350380

351381
func TestUnmarshalerKeepAlreadyInitialized(t *testing.T) {

0 commit comments

Comments
 (0)