Skip to content

Commit d1a5e5a

Browse files
authored
[configoptional] Add GetOrInsertDefault method (#13859)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description <!-- Issue number if applicable --> Adds `GetOrInsertDefault` method to `configoptional.Optional`. Uses it in tests. We originally did not do this because we only had use cases for this in tests. However #13856 shows that this is useful in programmatic use cases as well. #### Link to tracking issue Fixes #13856
1 parent a9a93af commit d1a5e5a

8 files changed

Lines changed: 189 additions & 55 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: configoptional
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add `GetOrInsertDefault` method to `configoptional.Optional`
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [13856]
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+
This method inserts a default or zero value into a `None`/`Default` `Optional` before `Get`ting its inner value.
20+
21+
# Optional: The change log or logs in which this entry should be included.
22+
# e.g. '[user]' or '[user, api]'
23+
# Include 'user' if the change is relevant to end users.
24+
# Include 'api' if there is a change to a library API.
25+
# Default: '[user]'
26+
change_logs: [api]

config/configoptional/optional.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ func (o *Optional[T]) Get() *T {
135135
return &o.value
136136
}
137137

138+
// GetOrInsertDefault makes the Optional into a Some(val) and returns val.
139+
//
140+
// In particular, if it is Default(val) it turns it into Some(val)
141+
// and if it is None[T]() it turns it into Some(zeroVal) where zeroVal is T's zero value.
142+
// This method is useful for programmatic usage of an optional.
143+
//
144+
// It panics if
145+
// - T is not a struct OR
146+
// - T has a field with the mapstructure tag "enabled".
147+
func (o *Optional[T]) GetOrInsertDefault() *T {
148+
err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
149+
if err != nil {
150+
panic(err)
151+
}
152+
153+
if o.HasValue() {
154+
return o.Get()
155+
}
156+
157+
empty := confmap.NewFromStringMap(map[string]any{})
158+
if err := empty.Unmarshal(o); err != nil {
159+
// This should never happen, if it happens it is a bug, so this panic is not documented.
160+
panic(fmt.Errorf("failed to unmarshal empty map into %T type: %w. Please report this bug", o.value, err))
161+
}
162+
163+
return o.Get()
164+
}
165+
138166
var _ confmap.Unmarshaler = (*Optional[any])(nil)
139167

140168
// Unmarshal the configuration into the Optional value.

config/configoptional/optional_test.go

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ func TestDefaultPanics(t *testing.T) {
7373
_ = None[WithEnabled]()
7474
})
7575

76+
assert.Panics(t, func() {
77+
opt := None[int]()
78+
_ = opt.GetOrInsertDefault()
79+
})
80+
81+
assert.Panics(t, func() {
82+
var opt Optional[WithEnabled]
83+
_ = opt.GetOrInsertDefault()
84+
})
85+
7686
assert.NotPanics(t, func() {
7787
_ = Default(NoMapstructure{})
7888
})
@@ -100,26 +110,126 @@ func TestNoneZeroVal(t *testing.T) {
100110
var none Optional[Sub]
101111
require.False(t, none.HasValue())
102112
require.Nil(t, none.Get())
113+
114+
var zeroVal Sub
115+
ret := none.GetOrInsertDefault()
116+
require.True(t, none.HasValue())
117+
assert.Equal(t, &zeroVal, ret)
103118
}
104119

105120
func TestNone(t *testing.T) {
106121
none := None[Sub]()
107122
require.False(t, none.HasValue())
108123
require.Nil(t, none.Get())
124+
125+
var zeroVal Sub
126+
ret := none.GetOrInsertDefault()
127+
require.True(t, none.HasValue())
128+
assert.Equal(t, &zeroVal, ret)
129+
}
130+
131+
func ExampleNone() {
132+
type Person struct {
133+
Name string
134+
Age int
135+
}
136+
137+
opt := None[Person]()
138+
139+
// A None has no value.
140+
fmt.Println(opt.HasValue())
141+
fmt.Println(opt.Get())
142+
143+
// GetOrInsertDefault places the zero value
144+
// and returns it, allowing you to modify it.
145+
opt.GetOrInsertDefault().Name = "John Doe"
146+
fmt.Println(opt.HasValue())
147+
fmt.Println(opt.Get())
148+
149+
// Output:
150+
// false
151+
// <nil>
152+
// true
153+
// &{John Doe 0}
109154
}
110155

111156
func TestSome(t *testing.T) {
112157
some := Some(Sub{
113158
Foo: "foobar",
114159
})
115160
require.True(t, some.HasValue())
116-
assert.Equal(t, "foobar", some.Get().Foo)
161+
retGet := some.Get()
162+
assert.Equal(t, "foobar", retGet.Foo)
163+
164+
retGetOrInsertDefault := some.GetOrInsertDefault()
165+
require.True(t, some.HasValue())
166+
assert.Equal(t, retGet, retGetOrInsertDefault)
167+
}
168+
169+
func ExampleSome() {
170+
type Person struct {
171+
Name string
172+
Age int
173+
}
174+
175+
opt := Some(Person{
176+
Name: "John Doe",
177+
Age: 42,
178+
})
179+
180+
// A Some has a value.
181+
fmt.Println(opt.HasValue())
182+
fmt.Println(opt.Get())
183+
184+
// GetOrInsertDefault only returns a reference
185+
// to the inner value without modifying it.
186+
opt.GetOrInsertDefault().Name = "Jane Doe"
187+
fmt.Println(opt.HasValue())
188+
fmt.Println(opt.Get())
189+
190+
// Output:
191+
// true
192+
// &{John Doe 42}
193+
// true
194+
// &{Jane Doe 42}
117195
}
118196

119197
func TestDefault(t *testing.T) {
120-
defaultSub := Default(&subDefault)
198+
defaultSub := Default(subDefault)
121199
require.False(t, defaultSub.HasValue())
122200
require.Nil(t, defaultSub.Get())
201+
202+
ret := defaultSub.GetOrInsertDefault()
203+
require.True(t, defaultSub.HasValue())
204+
assert.Equal(t, &subDefault, ret)
205+
}
206+
207+
func ExampleDefault() {
208+
type Person struct {
209+
Name string
210+
Age int
211+
}
212+
213+
opt := Default(Person{
214+
Name: "John Doe",
215+
Age: 42,
216+
})
217+
218+
// A Default has no value.
219+
fmt.Println(opt.HasValue())
220+
fmt.Println(opt.Get())
221+
222+
// GetOrInsertDefault places the default value
223+
// and returns it, allowing you to modify it.
224+
opt.GetOrInsertDefault().Age = 38
225+
fmt.Println(opt.HasValue())
226+
fmt.Println(opt.Get())
227+
228+
// Output:
229+
// false
230+
// <nil>
231+
// true
232+
// &{John Doe 38}
123233
}
124234

125235
func TestUnmarshalOptional(t *testing.T) {

internal/e2e/consume_contract_test.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,10 @@ import (
77
"testing"
88
"time"
99

10-
"github.com/stretchr/testify/require"
11-
1210
"go.opentelemetry.io/collector/component"
1311
"go.opentelemetry.io/collector/config/configgrpc"
14-
"go.opentelemetry.io/collector/config/configoptional"
1512
"go.opentelemetry.io/collector/config/configretry"
1613
"go.opentelemetry.io/collector/config/configtls"
17-
"go.opentelemetry.io/collector/confmap"
1814
"go.opentelemetry.io/collector/exporter/exporterhelper"
1915
"go.opentelemetry.io/collector/exporter/exportertest"
2016
"go.opentelemetry.io/collector/exporter/otlpexporter"
@@ -23,19 +19,6 @@ import (
2319
"go.opentelemetry.io/collector/receiver/otlpreceiver"
2420
)
2521

26-
// GetOrInsertDefault is a helper function to get or insert a default value for a configoptional.Optional type.
27-
func GetOrInsertDefault[T any](t *testing.T, opt *configoptional.Optional[T]) *T {
28-
if opt.HasValue() {
29-
return opt.Get()
30-
}
31-
32-
empty := confmap.NewFromStringMap(map[string]any{})
33-
require.NoError(t, empty.Unmarshal(opt))
34-
val := opt.Get()
35-
require.NotNil(t, "Expected a default value to be set for %T", val)
36-
return val
37-
}
38-
3922
func testExporterConfig(endpoint string) component.Config {
4023
retryConfig := configretry.NewDefaultBackOffConfig()
4124
retryConfig.InitialInterval = time.Millisecond // interval is short for the test purposes
@@ -51,9 +34,9 @@ func testExporterConfig(endpoint string) component.Config {
5134
}
5235
}
5336

54-
func testReceiverConfig(t *testing.T, endpoint string) component.Config {
37+
func testReceiverConfig(endpoint string) component.Config {
5538
cfg := otlpreceiver.NewFactory().CreateDefaultConfig()
56-
GetOrInsertDefault(t, &cfg.(*otlpreceiver.Config).GRPC).NetAddr.Endpoint = endpoint
39+
cfg.(*otlpreceiver.Config).GRPC.GetOrInsertDefault().NetAddr.Endpoint = endpoint
5740
return cfg
5841
}
5942

@@ -68,7 +51,7 @@ func TestConsumeContractOtlpLogs(t *testing.T) {
6851
Signal: pipeline.SignalLogs,
6952
ExporterConfig: testExporterConfig(addr),
7053
ReceiverFactory: otlpreceiver.NewFactory(),
71-
ReceiverConfig: testReceiverConfig(t, addr),
54+
ReceiverConfig: testReceiverConfig(addr),
7255
})
7356
}
7457

@@ -81,7 +64,7 @@ func TestConsumeContractOtlpTraces(t *testing.T) {
8164
ExporterFactory: otlpexporter.NewFactory(),
8265
ExporterConfig: testExporterConfig(addr),
8366
ReceiverFactory: otlpreceiver.NewFactory(),
84-
ReceiverConfig: testReceiverConfig(t, addr),
67+
ReceiverConfig: testReceiverConfig(addr),
8568
})
8669
}
8770

@@ -94,6 +77,6 @@ func TestConsumeContractOtlpMetrics(t *testing.T) {
9477
Signal: pipeline.SignalMetrics,
9578
ExporterConfig: testExporterConfig(addr),
9679
ReceiverFactory: otlpreceiver.NewFactory(),
97-
ReceiverConfig: testReceiverConfig(t, addr),
80+
ReceiverConfig: testReceiverConfig(addr),
9881
})
9982
}

internal/e2e/otlphttp_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -396,31 +396,31 @@ func createConfig(baseURL string, defaultCfg component.Config) *otlphttpexporter
396396

397397
func startTracesReceiver(t *testing.T, addr string, next consumer.Traces) {
398398
factory := otlpreceiver.NewFactory()
399-
cfg := createReceiverConfig(t, addr, factory.CreateDefaultConfig())
399+
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
400400
recv, err := factory.CreateTraces(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
401401
require.NoError(t, err)
402402
startAndCleanup(t, recv)
403403
}
404404

405405
func startMetricsReceiver(t *testing.T, addr string, next consumer.Metrics) {
406406
factory := otlpreceiver.NewFactory()
407-
cfg := createReceiverConfig(t, addr, factory.CreateDefaultConfig())
407+
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
408408
recv, err := factory.CreateMetrics(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
409409
require.NoError(t, err)
410410
startAndCleanup(t, recv)
411411
}
412412

413413
func startLogsReceiver(t *testing.T, addr string, next consumer.Logs) {
414414
factory := otlpreceiver.NewFactory()
415-
cfg := createReceiverConfig(t, addr, factory.CreateDefaultConfig())
415+
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
416416
recv, err := factory.CreateLogs(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
417417
require.NoError(t, err)
418418
startAndCleanup(t, recv)
419419
}
420420

421-
func createReceiverConfig(t *testing.T, addr string, defaultCfg component.Config) *otlpreceiver.Config {
421+
func createReceiverConfig(addr string, defaultCfg component.Config) *otlpreceiver.Config {
422422
cfg := defaultCfg.(*otlpreceiver.Config)
423-
GetOrInsertDefault(t, &cfg.HTTP).ServerConfig.Endpoint = addr
423+
cfg.HTTP.GetOrInsertDefault().ServerConfig.Endpoint = addr
424424
return cfg
425425
}
426426

receiver/otlpreceiver/config_test.go

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,15 @@ import (
2424
"go.opentelemetry.io/collector/confmap/xconfmap"
2525
)
2626

27-
// GetOrInsertDefault is a helper function to get or insert a default value for a configoptional.Optional type.
28-
func GetOrInsertDefault[T any](t *testing.T, opt *configoptional.Optional[T]) *T {
29-
if opt.HasValue() {
30-
return opt.Get()
31-
}
32-
33-
empty := confmap.NewFromStringMap(map[string]any{})
34-
require.NoError(t, empty.Unmarshal(opt))
35-
val := opt.Get()
36-
require.NotNil(t, "Expected a default value to be set for %T", val)
37-
return val
38-
}
39-
4027
func TestUnmarshalDefaultConfig(t *testing.T) {
4128
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "default.yaml"))
4229
require.NoError(t, err)
4330
factory := NewFactory()
4431
cfg := factory.CreateDefaultConfig()
4532
require.NoError(t, cm.Unmarshal(&cfg))
4633
expectedCfg := factory.CreateDefaultConfig().(*Config)
47-
GetOrInsertDefault(t, &expectedCfg.GRPC)
48-
GetOrInsertDefault(t, &expectedCfg.HTTP)
34+
expectedCfg.GRPC.GetOrInsertDefault()
35+
expectedCfg.HTTP.GetOrInsertDefault()
4936
assert.Equal(t, expectedCfg, cfg)
5037
}
5138

@@ -57,7 +44,7 @@ func TestUnmarshalConfigOnlyGRPC(t *testing.T) {
5744
require.NoError(t, cm.Unmarshal(&cfg))
5845

5946
defaultOnlyGRPC := factory.CreateDefaultConfig().(*Config)
60-
GetOrInsertDefault(t, &defaultOnlyGRPC.GRPC)
47+
defaultOnlyGRPC.GRPC.GetOrInsertDefault()
6148
assert.Equal(t, defaultOnlyGRPC, cfg)
6249
}
6350

@@ -69,7 +56,7 @@ func TestUnmarshalConfigOnlyHTTP(t *testing.T) {
6956
require.NoError(t, cm.Unmarshal(&cfg))
7057

7158
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
72-
GetOrInsertDefault(t, &defaultOnlyHTTP.HTTP)
59+
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
7360
assert.Equal(t, defaultOnlyHTTP, cfg)
7461
}
7562

@@ -81,7 +68,7 @@ func TestUnmarshalConfigOnlyHTTPNull(t *testing.T) {
8168
require.NoError(t, cm.Unmarshal(&cfg))
8269

8370
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
84-
GetOrInsertDefault(t, &defaultOnlyHTTP.HTTP)
71+
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
8572
assert.Equal(t, defaultOnlyHTTP, cfg)
8673
}
8774

@@ -93,7 +80,7 @@ func TestUnmarshalConfigOnlyHTTPEmptyMap(t *testing.T) {
9380
require.NoError(t, cm.Unmarshal(&cfg))
9481

9582
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
96-
GetOrInsertDefault(t, &defaultOnlyHTTP.HTTP)
83+
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
9784
assert.Equal(t, defaultOnlyHTTP, cfg)
9885
}
9986

receiver/otlpreceiver/factory_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ func TestCreateDefaultConfig(t *testing.T) {
4040
func TestCreateSameReceiver(t *testing.T) {
4141
factory := NewFactory()
4242
cfg := factory.CreateDefaultConfig().(*Config)
43-
GetOrInsertDefault(t, &cfg.GRPC).NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
44-
GetOrInsertDefault(t, &cfg.HTTP).ServerConfig.Endpoint = testutil.GetAvailableLocalAddress(t)
43+
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
44+
cfg.HTTP.GetOrInsertDefault().ServerConfig.Endpoint = testutil.GetAvailableLocalAddress(t)
4545

4646
core, observer := observer.New(zapcore.DebugLevel)
4747
attrs := attribute.NewSet(

0 commit comments

Comments
 (0)