Skip to content

Commit d733268

Browse files
committed
[test] Add configoptional
1 parent e9f3dec commit d733268

File tree

13 files changed

+396
-117
lines changed

13 files changed

+396
-117
lines changed

config/configoptional/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../../Makefile.Common

config/configoptional/go.mod

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module go.opentelemetry.io/collector/config/configoptional
2+
3+
go 1.23.0
4+
5+
require (
6+
github.com/stretchr/testify v1.10.0
7+
go.opentelemetry.io/collector/confmap v1.31.0
8+
go.uber.org/goleak v1.3.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
14+
github.com/gobwas/glob v0.2.3 // indirect
15+
github.com/hashicorp/go-version v1.7.0 // indirect
16+
github.com/knadh/koanf/maps v0.1.2 // indirect
17+
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
18+
github.com/knadh/koanf/v2 v2.2.0 // indirect
19+
github.com/mitchellh/copystructure v1.2.0 // indirect
20+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
21+
github.com/pmezard/go-difflib v1.0.0 // indirect
22+
go.opentelemetry.io/collector/featuregate v1.31.0 // indirect
23+
go.uber.org/multierr v1.11.0 // indirect
24+
go.uber.org/zap v1.27.0 // indirect
25+
gopkg.in/yaml.v3 v3.0.1 // indirect
26+
sigs.k8s.io/yaml v1.4.0 // indirect
27+
)
28+
29+
replace go.opentelemetry.io/collector/confmap => ../../confmap

config/configoptional/go.sum

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/configoptional/optional.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package configoptional // import "go.opentelemetry.io/collector/config/configoptional"
5+
6+
import (
7+
"go.opentelemetry.io/collector/confmap"
8+
)
9+
10+
// Optional is a type that can be used to represent a value that may or may not be present.
11+
// It supports three flavors: Some(value), None(), and WithDefault(defaultValue).
12+
type Optional[T any] struct {
13+
hasValue bool
14+
value T
15+
16+
defaultFn DefaultFunc[T]
17+
}
18+
19+
type DefaultFunc[T any] func() T
20+
21+
var _ confmap.Unmarshaler = (*Optional[any])(nil)
22+
23+
// Some creates an Optional with a value.
24+
func Some[T any](value T) Optional[T] {
25+
return Optional[T]{value: value, hasValue: true}
26+
}
27+
28+
// None creates an Optional with no value.
29+
func None[T any]() Optional[T] {
30+
return Optional[T]{}
31+
}
32+
33+
// WithDefault creates an Optional which has no value
34+
// unless user config provides some, in which case
35+
// the defaultFn is used to create the initial value,
36+
// which may be overridden by the user provided config.
37+
//
38+
// The reason we pass a function instead of T directly
39+
// is to allow this to work with T being a pointer,
40+
// because we wouldn't want to copy the value of a pointer
41+
// since it might reuse (and override) some shared state.
42+
//
43+
// On unmarshal, the defaultFn is removed.
44+
func WithDefault[T any](defaultFn DefaultFunc[T]) Optional[T] {
45+
return Optional[T]{defaultFn: defaultFn}
46+
}
47+
48+
func (o Optional[T]) HasValue() bool {
49+
return o.hasValue
50+
}
51+
52+
func (o Optional[T]) Value() T {
53+
return o.value
54+
}
55+
56+
func (o Optional[T]) Ref() *T {
57+
return &o.value
58+
}
59+
60+
func (o *Optional[T]) Unmarshal(conf *confmap.Conf) error {
61+
if o.defaultFn != nil {
62+
o.value = o.defaultFn()
63+
o.hasValue = true
64+
o.defaultFn = nil
65+
}
66+
if err := conf.Unmarshal(&o.value); err != nil {
67+
return err
68+
}
69+
o.hasValue = true
70+
return nil
71+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package configoptional
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"go.opentelemetry.io/collector/confmap"
12+
)
13+
14+
type Config struct {
15+
Sub1 Optional[Sub] `mapstructure:"sub"`
16+
}
17+
18+
type Sub struct {
19+
Foo string `mapstructure:"foo"`
20+
}
21+
22+
func TestOptional(t *testing.T) {
23+
tests := []struct {
24+
name string
25+
config map[string]any
26+
defaultCfg Config
27+
expectedSub bool
28+
expectedFoo string
29+
}{
30+
// {
31+
// name: "no_default_no_config",
32+
// defaultCfg: Config{
33+
// Sub1: None[Sub](),
34+
// },
35+
// expectedSub: false,
36+
// },
37+
// {
38+
// name: "no_default_with_config",
39+
// config: map[string]any{
40+
// "sub": map[string]any{
41+
// "foo": "bar",
42+
// },
43+
// },
44+
// defaultCfg: Config{
45+
// Sub1: None[Sub](),
46+
// },
47+
// expectedSub: true,
48+
// expectedFoo: "bar",
49+
// },
50+
// {
51+
// name: "with_default_no_config",
52+
// defaultCfg: Config{
53+
// Sub1: WithDefault(func() Sub {
54+
// return Sub{
55+
// Foo: "foobar",
56+
// }
57+
// }),
58+
// },
59+
// expectedSub: false,
60+
// },
61+
// {
62+
// name: "with_default_with_config",
63+
// config: map[string]any{
64+
// "sub": map[string]any{
65+
// "foo": "bar",
66+
// },
67+
// },
68+
// defaultCfg: Config{
69+
// Sub1: WithDefault(func() Sub {
70+
// return Sub{
71+
// Foo: "foobar",
72+
// }
73+
// }),
74+
// },
75+
// expectedSub: true,
76+
// expectedFoo: "bar", // input overrides default
77+
// },
78+
{
79+
// this test fails, because "sub:" is considered null value by mapstructure
80+
// and no additional processing happens for it, including custom unmarshaler.
81+
// https://github.com/go-viper/mapstructure/blob/0382e5b7e3987443c91311b7fdb60b92c69a47bf/mapstructure.go#L445
82+
name: "with_default_with_config_no_foo",
83+
config: map[string]any{
84+
"sub": nil,
85+
},
86+
defaultCfg: Config{
87+
Sub1: WithDefault(func() Sub {
88+
return Sub{
89+
Foo: "foobar",
90+
}
91+
}),
92+
},
93+
expectedSub: true,
94+
expectedFoo: "foobar", // default applies
95+
},
96+
}
97+
98+
for _, test := range tests {
99+
t.Run(test.name, func(t *testing.T) {
100+
cfg := test.defaultCfg
101+
conf := confmap.NewFromStringMap(test.config)
102+
require.NoError(t, conf.Unmarshal(&cfg))
103+
require.Equal(t, test.expectedSub, cfg.Sub1.HasValue())
104+
if test.expectedSub {
105+
require.Equal(t, test.expectedFoo, cfg.Sub1.Value().Foo)
106+
}
107+
})
108+
}
109+
}

config/configoptional/package_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package configoptional
5+
6+
import (
7+
"testing"
8+
9+
"go.uber.org/goleak"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
goleak.VerifyTestMain(m)
14+
}

receiver/otlpreceiver/config.go

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,10 @@ import (
1212
"go.opentelemetry.io/collector/component"
1313
"go.opentelemetry.io/collector/config/configgrpc"
1414
"go.opentelemetry.io/collector/config/confighttp"
15+
"go.opentelemetry.io/collector/config/configoptional"
1516
"go.opentelemetry.io/collector/confmap"
1617
)
1718

18-
const (
19-
// Protocol values.
20-
protoGRPC = "protocols::grpc"
21-
protoHTTP = "protocols::http"
22-
)
23-
2419
type HTTPConfig struct {
2520
ServerConfig confighttp.ServerConfig `mapstructure:",squash"`
2621

@@ -39,8 +34,8 @@ type HTTPConfig struct {
3934

4035
// Protocols is the configuration for the supported protocols.
4136
type Protocols struct {
42-
GRPC *configgrpc.ServerConfig `mapstructure:"grpc"`
43-
HTTP *HTTPConfig `mapstructure:"http"`
37+
GRPC configoptional.Optional[configgrpc.ServerConfig] `mapstructure:"grpc"`
38+
HTTP configoptional.Optional[HTTPConfig] `mapstructure:"http"`
4439
// prevent unkeyed literal initialization
4540
_ struct{}
4641
}
@@ -58,7 +53,7 @@ var (
5853

5954
// Validate checks the receiver configuration is valid
6055
func (cfg *Config) Validate() error {
61-
if cfg.GRPC == nil && cfg.HTTP == nil {
56+
if !cfg.GRPC.HasValue() && !cfg.HTTP.HasValue() {
6257
return errors.New("must specify at least one protocol when using the OTLP receiver")
6358
}
6459
return nil
@@ -72,24 +67,20 @@ func (cfg *Config) Unmarshal(conf *confmap.Conf) error {
7267
return err
7368
}
7469

75-
if !conf.IsSet(protoGRPC) {
76-
cfg.GRPC = nil
77-
}
78-
79-
if !conf.IsSet(protoHTTP) {
80-
cfg.HTTP = nil
81-
} else {
70+
if cfg.HTTP.HasValue() {
8271
var err error
72+
httpCfg := cfg.HTTP.Value()
8373

84-
if cfg.HTTP.TracesURLPath, err = sanitizeURLPath(cfg.HTTP.TracesURLPath); err != nil {
74+
if httpCfg.TracesURLPath, err = sanitizeURLPath(httpCfg.TracesURLPath); err != nil {
8575
return err
8676
}
87-
if cfg.HTTP.MetricsURLPath, err = sanitizeURLPath(cfg.HTTP.MetricsURLPath); err != nil {
77+
if httpCfg.MetricsURLPath, err = sanitizeURLPath(httpCfg.MetricsURLPath); err != nil {
8878
return err
8979
}
90-
if cfg.HTTP.LogsURLPath, err = sanitizeURLPath(cfg.HTTP.LogsURLPath); err != nil {
80+
if httpCfg.LogsURLPath, err = sanitizeURLPath(httpCfg.LogsURLPath); err != nil {
9181
return err
9282
}
83+
cfg.HTTP = configoptional.Some(httpCfg)
9384
}
9485

9586
return nil

0 commit comments

Comments
 (0)