Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .chloggen/mx-psi_getorinsertdefault.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: configoptional

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add `GetOrInsertDefault` method to `configoptional.Optional`

# One or more tracking issues or pull requests related to the change
issues: [13856]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
This method inserts a default or zero value into a `None`/`Default` `Optional` before `Get`ting its inner value.

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
28 changes: 28 additions & 0 deletions config/configoptional/optional.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,34 @@ func (o *Optional[T]) Get() *T {
return &o.value
}

// GetOrInsertDefault makes the Optional into a Some(val) and returns val.
//
// In particular, if it is Default(val) it turns it into Some(val)
// and if it is None[T]() it turns it into Some(zeroVal) where zeroVal is T's zero value.
// This method is useful for programmatic usage of an optional.
//
// It panics if
// - T is not a struct OR
// - T has a field with the mapstructure tag "enabled".
func (o *Optional[T]) GetOrInsertDefault() *T {
err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
if err != nil {
panic(err)
}

if o.HasValue() {
return o.Get()
}

empty := confmap.NewFromStringMap(map[string]any{})
if err := empty.Unmarshal(o); err != nil {
// This should never happen, if it happens it is a bug, so this panic is not documented.
panic(fmt.Errorf("failed to unmarshal empty map into %T type: %w. Please report this bug", o.value, err))
}

return o.Get()
}

var _ confmap.Unmarshaler = (*Optional[any])(nil)

// Unmarshal the configuration into the Optional value.
Expand Down
114 changes: 112 additions & 2 deletions config/configoptional/optional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ func TestDefaultPanics(t *testing.T) {
_ = None[WithEnabled]()
})

assert.Panics(t, func() {
opt := None[int]()
_ = opt.GetOrInsertDefault()
})

assert.Panics(t, func() {
var opt Optional[WithEnabled]
_ = opt.GetOrInsertDefault()
})

assert.NotPanics(t, func() {
_ = Default(NoMapstructure{})
})
Expand Down Expand Up @@ -100,26 +110,126 @@ func TestNoneZeroVal(t *testing.T) {
var none Optional[Sub]
require.False(t, none.HasValue())
require.Nil(t, none.Get())

var zeroVal Sub
ret := none.GetOrInsertDefault()
require.True(t, none.HasValue())
assert.Equal(t, &zeroVal, ret)
}

func TestNone(t *testing.T) {
none := None[Sub]()
require.False(t, none.HasValue())
require.Nil(t, none.Get())

var zeroVal Sub
ret := none.GetOrInsertDefault()
require.True(t, none.HasValue())
assert.Equal(t, &zeroVal, ret)
}

func ExampleNone() {
type Person struct {
Name string
Age int
}

opt := None[Person]()

// A None has no value.
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())

// GetOrInsertDefault places the zero value
// and returns it, allowing you to modify it.
opt.GetOrInsertDefault().Name = "John Doe"
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())

// Output:
// false
// <nil>
// true
// &{John Doe 0}
}

func TestSome(t *testing.T) {
some := Some(Sub{
Foo: "foobar",
})
require.True(t, some.HasValue())
assert.Equal(t, "foobar", some.Get().Foo)
retGet := some.Get()
assert.Equal(t, "foobar", retGet.Foo)

retGetOrInsertDefault := some.GetOrInsertDefault()
require.True(t, some.HasValue())
assert.Equal(t, retGet, retGetOrInsertDefault)
}

func ExampleSome() {
type Person struct {
Name string
Age int
}

opt := Some(Person{
Name: "John Doe",
Age: 42,
})

// A Some has a value.
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())

// GetOrInsertDefault only returns a reference
// to the inner value without modifying it.
opt.GetOrInsertDefault().Name = "Jane Doe"
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())

// Output:
// true
// &{John Doe 42}
// true
// &{Jane Doe 42}
}

func TestDefault(t *testing.T) {
defaultSub := Default(&subDefault)
defaultSub := Default(subDefault)
require.False(t, defaultSub.HasValue())
require.Nil(t, defaultSub.Get())

ret := defaultSub.GetOrInsertDefault()
require.True(t, defaultSub.HasValue())
assert.Equal(t, &subDefault, ret)
}

func ExampleDefault() {
type Person struct {
Name string
Age int
}

opt := Default(Person{
Name: "John Doe",
Age: 42,
})

// A Default has no value.
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())

// GetOrInsertDefault places the default value
// and returns it, allowing you to modify it.
opt.GetOrInsertDefault().Age = 38
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())

// Output:
// false
// <nil>
// true
// &{John Doe 38}
}

func TestUnmarshalOptional(t *testing.T) {
Expand Down
27 changes: 5 additions & 22 deletions internal/e2e/consume_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@ import (
"testing"
"time"

"github.com/stretchr/testify/require"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/otlpexporter"
Expand All @@ -23,19 +19,6 @@ import (
"go.opentelemetry.io/collector/receiver/otlpreceiver"
)

// GetOrInsertDefault is a helper function to get or insert a default value for a configoptional.Optional type.
func GetOrInsertDefault[T any](t *testing.T, opt *configoptional.Optional[T]) *T {
if opt.HasValue() {
return opt.Get()
}

empty := confmap.NewFromStringMap(map[string]any{})
require.NoError(t, empty.Unmarshal(opt))
val := opt.Get()
require.NotNil(t, "Expected a default value to be set for %T", val)
return val
}

func testExporterConfig(endpoint string) component.Config {
retryConfig := configretry.NewDefaultBackOffConfig()
retryConfig.InitialInterval = time.Millisecond // interval is short for the test purposes
Expand All @@ -51,9 +34,9 @@ func testExporterConfig(endpoint string) component.Config {
}
}

func testReceiverConfig(t *testing.T, endpoint string) component.Config {
func testReceiverConfig(endpoint string) component.Config {
cfg := otlpreceiver.NewFactory().CreateDefaultConfig()
GetOrInsertDefault(t, &cfg.(*otlpreceiver.Config).GRPC).NetAddr.Endpoint = endpoint
cfg.(*otlpreceiver.Config).GRPC.GetOrInsertDefault().NetAddr.Endpoint = endpoint
return cfg
}

Expand All @@ -68,7 +51,7 @@ func TestConsumeContractOtlpLogs(t *testing.T) {
Signal: pipeline.SignalLogs,
ExporterConfig: testExporterConfig(addr),
ReceiverFactory: otlpreceiver.NewFactory(),
ReceiverConfig: testReceiverConfig(t, addr),
ReceiverConfig: testReceiverConfig(addr),
})
}

Expand All @@ -81,7 +64,7 @@ func TestConsumeContractOtlpTraces(t *testing.T) {
ExporterFactory: otlpexporter.NewFactory(),
ExporterConfig: testExporterConfig(addr),
ReceiverFactory: otlpreceiver.NewFactory(),
ReceiverConfig: testReceiverConfig(t, addr),
ReceiverConfig: testReceiverConfig(addr),
})
}

Expand All @@ -94,6 +77,6 @@ func TestConsumeContractOtlpMetrics(t *testing.T) {
Signal: pipeline.SignalMetrics,
ExporterConfig: testExporterConfig(addr),
ReceiverFactory: otlpreceiver.NewFactory(),
ReceiverConfig: testReceiverConfig(t, addr),
ReceiverConfig: testReceiverConfig(addr),
})
}
10 changes: 5 additions & 5 deletions internal/e2e/otlphttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,31 +396,31 @@ func createConfig(baseURL string, defaultCfg component.Config) *otlphttpexporter

func startTracesReceiver(t *testing.T, addr string, next consumer.Traces) {
factory := otlpreceiver.NewFactory()
cfg := createReceiverConfig(t, addr, factory.CreateDefaultConfig())
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
recv, err := factory.CreateTraces(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
require.NoError(t, err)
startAndCleanup(t, recv)
}

func startMetricsReceiver(t *testing.T, addr string, next consumer.Metrics) {
factory := otlpreceiver.NewFactory()
cfg := createReceiverConfig(t, addr, factory.CreateDefaultConfig())
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
recv, err := factory.CreateMetrics(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
require.NoError(t, err)
startAndCleanup(t, recv)
}

func startLogsReceiver(t *testing.T, addr string, next consumer.Logs) {
factory := otlpreceiver.NewFactory()
cfg := createReceiverConfig(t, addr, factory.CreateDefaultConfig())
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
recv, err := factory.CreateLogs(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
require.NoError(t, err)
startAndCleanup(t, recv)
}

func createReceiverConfig(t *testing.T, addr string, defaultCfg component.Config) *otlpreceiver.Config {
func createReceiverConfig(addr string, defaultCfg component.Config) *otlpreceiver.Config {
cfg := defaultCfg.(*otlpreceiver.Config)
GetOrInsertDefault(t, &cfg.HTTP).ServerConfig.Endpoint = addr
cfg.HTTP.GetOrInsertDefault().ServerConfig.Endpoint = addr
return cfg
}

Expand Down
25 changes: 6 additions & 19 deletions receiver/otlpreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,15 @@ import (
"go.opentelemetry.io/collector/confmap/xconfmap"
)

// GetOrInsertDefault is a helper function to get or insert a default value for a configoptional.Optional type.
func GetOrInsertDefault[T any](t *testing.T, opt *configoptional.Optional[T]) *T {
if opt.HasValue() {
return opt.Get()
}

empty := confmap.NewFromStringMap(map[string]any{})
require.NoError(t, empty.Unmarshal(opt))
val := opt.Get()
require.NotNil(t, "Expected a default value to be set for %T", val)
return val
}

func TestUnmarshalDefaultConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "default.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
expectedCfg := factory.CreateDefaultConfig().(*Config)
GetOrInsertDefault(t, &expectedCfg.GRPC)
GetOrInsertDefault(t, &expectedCfg.HTTP)
expectedCfg.GRPC.GetOrInsertDefault()
expectedCfg.HTTP.GetOrInsertDefault()
assert.Equal(t, expectedCfg, cfg)
}

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

defaultOnlyGRPC := factory.CreateDefaultConfig().(*Config)
GetOrInsertDefault(t, &defaultOnlyGRPC.GRPC)
defaultOnlyGRPC.GRPC.GetOrInsertDefault()
assert.Equal(t, defaultOnlyGRPC, cfg)
}

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

defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
GetOrInsertDefault(t, &defaultOnlyHTTP.HTTP)
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
assert.Equal(t, defaultOnlyHTTP, cfg)
}

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

defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
GetOrInsertDefault(t, &defaultOnlyHTTP.HTTP)
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
assert.Equal(t, defaultOnlyHTTP, cfg)
}

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

defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
GetOrInsertDefault(t, &defaultOnlyHTTP.HTTP)
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
assert.Equal(t, defaultOnlyHTTP, cfg)
}

Expand Down
4 changes: 2 additions & 2 deletions receiver/otlpreceiver/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func TestCreateDefaultConfig(t *testing.T) {
func TestCreateSameReceiver(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
GetOrInsertDefault(t, &cfg.GRPC).NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
GetOrInsertDefault(t, &cfg.HTTP).ServerConfig.Endpoint = testutil.GetAvailableLocalAddress(t)
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
cfg.HTTP.GetOrInsertDefault().ServerConfig.Endpoint = testutil.GetAvailableLocalAddress(t)

core, observer := observer.New(zapcore.DebugLevel)
attrs := attribute.NewSet(
Expand Down
Loading