@@ -17,6 +17,7 @@ import (
1717
1818 "go.opentelemetry.io/collector/component"
1919 "go.opentelemetry.io/collector/config/configoptional"
20+ "go.opentelemetry.io/collector/confmap"
2021 "go.opentelemetry.io/collector/confmap/confmaptest"
2122)
2223
@@ -235,3 +236,171 @@ func TestServerConfigLegacyWarningsLogged(t *testing.T) {
235236 assert .Contains (t , entry .Message , "deprecated" )
236237 }
237238}
239+
240+ // TestNamedFieldServerConfigUnmarshal verifies that ServerConfig's custom Unmarshal
241+ // method is invoked when ServerConfig is a named field (e.g. `mapstructure:"http"`)
242+ // in a component config, which is the other common usage pattern.
243+ func TestNamedFieldServerConfigUnmarshal (t * testing.T ) {
244+ type componentConfig struct {
245+ HTTP ServerConfig `mapstructure:"http"`
246+ ComponentID string `mapstructure:"component_id,omitempty"`
247+ }
248+
249+ newDefault := func () componentConfig {
250+ return componentConfig {HTTP : NewDefaultServerConfig ()}
251+ }
252+
253+ tests := []struct {
254+ name string
255+ input map [string ]any
256+ expectError bool
257+ wantKA configoptional.Optional [KeepaliveServerConfig ]
258+ wantID string
259+ }{
260+ {
261+ name : "new keepalive section is applied" ,
262+ input : map [string ]any {
263+ "component_id" : "my-receiver" ,
264+ "http" : map [string ]any {"keepalive" : map [string ]any {"idle_timeout" : "2m" }},
265+ },
266+ wantKA : configoptional .Some (KeepaliveServerConfig {IdleTimeout : 2 * time .Minute }),
267+ wantID : "my-receiver" ,
268+ },
269+ {
270+ name : "keepalive disabled via enabled:false" ,
271+ input : map [string ]any {
272+ "component_id" : "my-receiver" ,
273+ "http" : map [string ]any {"keepalive" : map [string ]any {"enabled" : false }},
274+ },
275+ wantKA : configoptional .None [KeepaliveServerConfig ](),
276+ wantID : "my-receiver" ,
277+ },
278+ {
279+ name : "legacy idle_timeout is translated" ,
280+ input : map [string ]any {
281+ "component_id" : "my-receiver" ,
282+ "http" : map [string ]any {"idle_timeout" : "3m" },
283+ },
284+ wantKA : configoptional .Some (KeepaliveServerConfig {IdleTimeout : 3 * time .Minute }),
285+ wantID : "my-receiver" ,
286+ },
287+ {
288+ name : "legacy field alongside new keepalive section is an error" ,
289+ input : map [string ]any {
290+ "component_id" : "my-receiver" ,
291+ "http" : map [string ]any {
292+ "idle_timeout" : "3m" ,
293+ "keepalive" : map [string ]any {"idle_timeout" : "2m" },
294+ },
295+ },
296+ expectError : true ,
297+ },
298+ }
299+
300+ for _ , tt := range tests {
301+ t .Run (tt .name , func (t * testing.T ) {
302+ // Mirror the production path: conf.Sub(id) → sub.Unmarshal(&cfg).
303+ // The outer map represents the full collector config; "my_component" is
304+ // the component ID whose sub-conf is handed to the component's config struct.
305+ fullConf := confmap .NewFromStringMap (map [string ]any {"my_component" : tt .input })
306+ sub , err := fullConf .Sub ("my_component" )
307+ require .NoError (t , err )
308+
309+ cfg := newDefault ()
310+ err = sub .Unmarshal (& cfg )
311+ if tt .expectError {
312+ require .Error (t , err )
313+ return
314+ }
315+ require .NoError (t , err )
316+ assert .Equal (t , tt .wantKA , cfg .HTTP .Keepalive )
317+ assert .Equal (t , tt .wantID , cfg .ComponentID )
318+ })
319+ }
320+ }
321+
322+ // TestEmbeddedServerConfigUnmarshal verifies that ServerConfig's custom Unmarshal
323+ // method is still invoked when ServerConfig is squash-embedded in a component config,
324+ // which is the typical usage pattern for receivers.
325+ func TestEmbeddedServerConfigUnmarshal (t * testing.T ) {
326+ type componentConfig struct {
327+ ServerConfig `mapstructure:",squash"`
328+ ComponentID string `mapstructure:"component_id,omitempty"`
329+ }
330+
331+ newDefault := func () componentConfig {
332+ return componentConfig {ServerConfig : NewDefaultServerConfig ()}
333+ }
334+
335+ tests := []struct {
336+ name string
337+ input map [string ]any
338+ expectError bool
339+ wantKA configoptional.Optional [KeepaliveServerConfig ]
340+ wantID string
341+ }{
342+ {
343+ name : "new keepalive section is applied" ,
344+ input : map [string ]any {
345+ "component_id" : "my-receiver" ,
346+ "keepalive" : map [string ]any {"idle_timeout" : "2m" },
347+ },
348+ wantKA : configoptional .Some (KeepaliveServerConfig {IdleTimeout : 2 * time .Minute }),
349+ wantID : "my-receiver" ,
350+ },
351+ {
352+ name : "keepalive disabled via enabled:false" ,
353+ input : map [string ]any {
354+ "component_id" : "my-receiver" ,
355+ "keepalive" : map [string ]any {"enabled" : false },
356+ },
357+ wantKA : configoptional .None [KeepaliveServerConfig ](),
358+ wantID : "my-receiver" ,
359+ },
360+ {
361+ name : "legacy idle_timeout is translated" ,
362+ input : map [string ]any {
363+ "component_id" : "my-receiver" ,
364+ "idle_timeout" : "3m" ,
365+ },
366+ wantKA : configoptional .Some (KeepaliveServerConfig {IdleTimeout : 3 * time .Minute }),
367+ wantID : "my-receiver" ,
368+ },
369+ {
370+ name : "legacy keep_alives_enabled:false disables keepalive" ,
371+ input : map [string ]any {
372+ "component_id" : "my-receiver" ,
373+ "keep_alives_enabled" : false ,
374+ },
375+ wantKA : configoptional .None [KeepaliveServerConfig ](),
376+ wantID : "my-receiver" ,
377+ },
378+ {
379+ name : "legacy field alongside new keepalive section is an error" ,
380+ input : map [string ]any {
381+ "component_id" : "my-receiver" ,
382+ "idle_timeout" : "3m" ,
383+ "keepalive" : map [string ]any {"idle_timeout" : "2m" },
384+ },
385+ expectError : true ,
386+ },
387+ }
388+
389+ for _ , tt := range tests {
390+ t .Run (tt .name , func (t * testing.T ) {
391+ fullConf := confmap .NewFromStringMap (map [string ]any {"my_component" : tt .input })
392+ sub , err := fullConf .Sub ("my_component" )
393+ require .NoError (t , err )
394+
395+ cfg := newDefault ()
396+ err = sub .Unmarshal (& cfg )
397+ if tt .expectError {
398+ require .Error (t , err )
399+ return
400+ }
401+ require .NoError (t , err )
402+ assert .Equal (t , tt .wantKA , cfg .Keepalive )
403+ assert .Equal (t , tt .wantID , cfg .ComponentID )
404+ })
405+ }
406+ }
0 commit comments