@@ -195,7 +195,14 @@ func (c *Config) GetPackages(ctx context.Context) ([]string, error) {
195
195
return packageList , nil
196
196
}
197
197
198
+ // getPackageConfigMap returns the map for the particular package, which includes
199
+ // (but is not limited to) both the `configs` section and the `interfaces` section.
200
+ // Note this does NOT return the `configs` section for the package. It returns the
201
+ // entire mapping for the package.
198
202
func (c * Config ) getPackageConfigMap (ctx context.Context , packageName string ) (map [string ]any , error ) {
203
+ log := zerolog .Ctx (ctx )
204
+ log .Trace ().Msg ("getting package config map" )
205
+
199
206
cfgMap , err := c .CfgAsMap (ctx )
200
207
if err != nil {
201
208
return nil , err
@@ -207,13 +214,27 @@ func (c *Config) getPackageConfigMap(ctx context.Context, packageName string) (m
207
214
}
208
215
configAsMap , isMap := configUnmerged .(map [string ]any )
209
216
if isMap {
217
+ log .Trace ().Msg ("package's value is a map, returning" )
210
218
return configAsMap , nil
211
219
}
212
- return map [string ]any {}, nil
220
+ log .Trace ().Msg ("package's value is not a map" )
221
+
222
+ // Package is something other than map, so set its value to an
223
+ // empty map.
224
+ emptyMap := map [string ]any {}
225
+ packageSection [packageName ] = emptyMap
226
+ return emptyMap , nil
213
227
214
228
}
229
+
230
+ // GetPackageConfig returns a struct representation of the package's config
231
+ // as provided in yaml. If the package did not specify a config section,
232
+ // this method will inject the top-level config into the package's config.
233
+ // This is especially useful as it allows us to lazily evaluate a package's
234
+ // config. If the package does specify config, this method takes care to merge
235
+ // the top-level config with the values specified for this package.
215
236
func (c * Config ) GetPackageConfig (ctx context.Context , packageName string ) (* Config , error ) {
216
- log := zerolog .Ctx (ctx )
237
+ log := zerolog .Ctx (ctx ). With (). Str ( "package-path" , packageName ). Logger ()
217
238
218
239
if c .pkgConfigCache == nil {
219
240
log .Debug ().Msg ("package cache is nil" )
@@ -223,11 +244,10 @@ func (c *Config) GetPackageConfig(ctx context.Context, packageName string) (*Con
223
244
return pkgConf , nil
224
245
}
225
246
226
- pkgConfig := reflect . New ( reflect . ValueOf ( c ). Elem (). Type ()). Interface ()
247
+ pkgConfig := & Config {}
227
248
if err := copier .Copy (pkgConfig , c ); err != nil {
228
249
return nil , fmt .Errorf ("failed to copy config: %w" , err )
229
250
}
230
- pkgConfigTyped := pkgConfig .(* Config )
231
251
232
252
configMap , err := c .getPackageConfigMap (ctx , packageName )
233
253
if err != nil {
@@ -237,18 +257,24 @@ func (c *Config) GetPackageConfig(ctx context.Context, packageName string) (*Con
237
257
configSection , ok := configMap ["config" ]
238
258
if ! ok {
239
259
log .Debug ().Msg ("config section not provided for package" )
240
- return pkgConfigTyped , nil
260
+ configMap ["config" ] = map [string ]any {}
261
+ c .pkgConfigCache [packageName ] = pkgConfig
262
+ return pkgConfig , nil
241
263
}
242
264
243
- decoder , err := c .getDecoder (pkgConfigTyped )
265
+ // We know that the package specified config that is overriding the top-level
266
+ // config. We use a mapstructure decoder to decode the values in the yaml
267
+ // into the pkgConfig struct. This has the effect of merging top-level
268
+ // config with package-level config.
269
+ decoder , err := c .getDecoder (pkgConfig )
244
270
if err != nil {
245
271
return nil , stackerr .NewStackErrf (err , "failed to get decoder" )
246
272
}
247
273
if err := decoder .Decode (configSection ); err != nil {
248
274
return nil , err
249
275
}
250
- c .pkgConfigCache [packageName ] = pkgConfigTyped
251
- return pkgConfigTyped , nil
276
+ c .pkgConfigCache [packageName ] = pkgConfig
277
+ return pkgConfig , nil
252
278
}
253
279
254
280
func (c * Config ) ExcludePath (path string ) bool {
@@ -347,16 +373,15 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
347
373
}
348
374
349
375
// Copy the package-level config to our interface-level config
350
- pkgConfigCopy := reflect . New ( reflect . ValueOf ( pkgConfig ). Elem (). Type ()). Interface ()
376
+ pkgConfigCopy := & Config {}
351
377
if err := copier .Copy (pkgConfigCopy , pkgConfig ); err != nil {
352
378
return nil , stackerr .NewStackErrf (err , "failed to create a copy of package config" )
353
379
}
354
- baseConfigTyped := pkgConfigCopy .(* Config )
355
380
356
381
interfaceSection , ok := interfacesSection [interfaceName ]
357
382
if ! ok {
358
383
log .Debug ().Msg ("interface not defined in package configuration" )
359
- return []* Config {baseConfigTyped }, nil
384
+ return []* Config {pkgConfigCopy }, nil
360
385
}
361
386
362
387
interfaceSectionTyped , ok := interfaceSection .(map [string ]any )
@@ -365,7 +390,7 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
365
390
// the interface but not provide any additional config beyond what
366
391
// is provided at the package level
367
392
if reflect .ValueOf (& interfaceSection ).Elem ().IsZero () {
368
- return []* Config {baseConfigTyped }, nil
393
+ return []* Config {pkgConfigCopy }, nil
369
394
}
370
395
msgString := "bad type provided for interface config"
371
396
log .Error ().Msgf (msgString )
@@ -376,11 +401,11 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
376
401
if ok {
377
402
log .Debug ().Msg ("config section exists for interface" )
378
403
// if `config` is provided, we'll overwrite the values in our
379
- // baseConfigTyped struct to act as the "new" base config.
404
+ // pkgConfigCopy struct to act as the "new" base config.
380
405
// This will allow us to set the default values for the interface
381
406
// but override them further for each mock defined in the
382
407
// `configs` section.
383
- decoder , err := c .getDecoder (baseConfigTyped )
408
+ decoder , err := c .getDecoder (pkgConfigCopy )
384
409
if err != nil {
385
410
return nil , stackerr .NewStackErrf (err , "unable to create mapstructure decoder" )
386
411
}
@@ -397,8 +422,8 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
397
422
configsSectionTyped := configsSection .([]any )
398
423
for _ , configMap := range configsSectionTyped {
399
424
// Create a copy of the package-level config
400
- currentInterfaceConfig := reflect .New (reflect .ValueOf (baseConfigTyped ).Elem ().Type ()).Interface ()
401
- if err := copier .Copy (currentInterfaceConfig , baseConfigTyped ); err != nil {
425
+ currentInterfaceConfig := reflect .New (reflect .ValueOf (pkgConfigCopy ).Elem ().Type ()).Interface ()
426
+ if err := copier .Copy (currentInterfaceConfig , pkgConfigCopy ); err != nil {
402
427
return nil , stackerr .NewStackErrf (err , "failed to copy package config" )
403
428
}
404
429
@@ -418,7 +443,7 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int
418
443
log .Debug ().Msg ("configs section doesn't exist for interface" )
419
444
420
445
if len (configs ) == 0 {
421
- configs = append (configs , baseConfigTyped )
446
+ configs = append (configs , pkgConfigCopy )
422
447
}
423
448
return configs , nil
424
449
}
@@ -429,31 +454,33 @@ func (c *Config) addSubPkgConfig(ctx context.Context, subPkgPath string, parentP
429
454
log := zerolog .Ctx (ctx ).With ().
430
455
Str ("parent-package" , parentPkgPath ).
431
456
Str ("sub-package" , subPkgPath ).Logger ()
457
+ ctx = log .WithContext (ctx )
432
458
433
- log .Trace ().Msg ("adding sub-package to config map" )
459
+ log .Debug ().Msg ("adding sub-package to config map" )
434
460
parentPkgConfig , err := c .getPackageConfigMap (ctx , parentPkgPath )
435
461
if err != nil {
436
462
log .Err (err ).
437
463
Msg ("failed to get package config for parent package" )
438
464
return fmt .Errorf ("failed to get package config: %w" , err )
439
465
}
440
466
441
- log .Trace ().Msg ("getting config" )
442
- cfg , err := c .CfgAsMap (ctx )
467
+ log .Debug ().Msg ("getting config" )
468
+ topLevelConfig , err := c .CfgAsMap (ctx )
443
469
if err != nil {
444
470
return fmt .Errorf ("failed to get configuration map: %w" , err )
445
471
}
446
472
447
- log .Trace ().Msg ("getting packages section" )
448
- packagesSection := cfg ["packages" ].(map [string ]any )
473
+ log .Debug ().Msg ("getting packages section" )
474
+ packagesSection := topLevelConfig ["packages" ].(map [string ]any )
449
475
450
- // Don't overwrite any config that already exists
451
476
_ , pkgExists := packagesSection [subPkgPath ]
452
477
if ! pkgExists {
453
478
log .Trace ().Msg ("sub-package doesn't exist in config" )
479
+
480
+ // Copy the parent package directly into the subpackage config section
454
481
packagesSection [subPkgPath ] = map [string ]any {}
455
482
newPkgSection := packagesSection [subPkgPath ].(map [string ]any )
456
- newPkgSection ["config" ] = parentPkgConfig ["config" ]
483
+ newPkgSection ["config" ] = deepCopyConfigMap ( parentPkgConfig ["config" ].( map [ string ] any ))
457
484
} else {
458
485
log .Trace ().Msg ("sub-package exists in config" )
459
486
// The sub-package exists in config. Check if it has its
@@ -465,10 +492,15 @@ func (c *Config) addSubPkgConfig(ctx context.Context, subPkgPath string, parentP
465
492
log .Err (err ).Msg ("could not get child package config" )
466
493
return fmt .Errorf ("failed to get sub-package config: %w" , err )
467
494
}
468
-
469
- for key , val := range parentPkgConfig {
470
- if _ , keyInSubPkg := subPkgConfig [key ]; ! keyInSubPkg {
471
- subPkgConfig [key ] = val
495
+ log .Trace ().Msgf ("sub-package config: %v" , subPkgConfig )
496
+ log .Trace ().Msgf ("parent-package config: %v" , parentPkgConfig )
497
+
498
+ // Merge the parent config with the sub-package config.
499
+ parentConfigSection := parentPkgConfig ["config" ].(map [string ]any )
500
+ subPkgConfigSection := subPkgConfig ["config" ].(map [string ]any )
501
+ for key , val := range parentConfigSection {
502
+ if _ , keyInSubPkg := subPkgConfigSection [key ]; ! keyInSubPkg {
503
+ subPkgConfigSection [key ] = val
472
504
}
473
505
474
506
}
@@ -595,18 +627,24 @@ func (c *Config) subPackages(
595
627
// recursive and recurses the file tree to find all sub-packages.
596
628
func (c * Config ) discoverRecursivePackages (ctx context.Context ) error {
597
629
log := zerolog .Ctx (ctx )
630
+ log .Trace ().Msg ("discovering recursive packages" )
598
631
recursivePackages := map [string ]* Config {}
599
632
packageList , err := c .GetPackages (ctx )
600
633
if err != nil {
601
634
return fmt .Errorf ("failed to get packages: %w" , err )
602
635
}
603
636
for _ , pkg := range packageList {
604
637
pkgConfig , err := c .GetPackageConfig (ctx , pkg )
638
+ pkgLog := log .With ().Str ("package" , pkg ).Logger ()
639
+ pkgLog .Trace ().Msg ("iterating over package" )
605
640
if err != nil {
606
641
return fmt .Errorf ("failed to get package config: %w" , err )
607
642
}
608
643
if pkgConfig .Recursive {
644
+ pkgLog .Trace ().Msg ("package marked as recursive" )
609
645
recursivePackages [pkg ] = pkgConfig
646
+ } else {
647
+ pkgLog .Trace ().Msg ("package not marked as recursive" )
610
648
}
611
649
}
612
650
if len (recursivePackages ) == 0 {
@@ -658,6 +696,17 @@ func contains[T comparable](slice []T, elem T) bool {
658
696
return false
659
697
}
660
698
699
+ func deepCopyConfigMap (src map [string ]any ) map [string ]any {
700
+ newMap := map [string ]any {}
701
+ for key , val := range src {
702
+ if contains ([]string {"packages" , "config" , "interfaces" }, key ) {
703
+ continue
704
+ }
705
+ newMap [key ] = val
706
+ }
707
+ return newMap
708
+ }
709
+
661
710
// mergeInConfig takes care of merging inheritable configuration
662
711
// in the config map. For example, it merges default config, then
663
712
// package-level config, then interface-level config.
@@ -677,34 +726,62 @@ func (c *Config) mergeInConfig(ctx context.Context) error {
677
726
}
678
727
for _ , pkgPath := range pkgs {
679
728
pkgLog := log .With ().Str ("package-path" , pkgPath ).Logger ()
729
+ pkgCtx := pkgLog .WithContext (ctx )
730
+
680
731
pkgLog .Trace ().Msg ("merging for package" )
681
- packageConfig , err := c .getPackageConfigMap (ctx , pkgPath )
732
+ packageConfig , err := c .getPackageConfigMap (pkgCtx , pkgPath )
682
733
if err != nil {
683
734
pkgLog .Err (err ).Msg ("failed to get package config" )
684
735
return fmt .Errorf ("failed to get package config: %w" , err )
685
736
}
737
+ pkgLog .Trace ().Msgf ("got package config map: %v" , packageConfig )
738
+
686
739
configSectionUntyped , configExists := packageConfig ["config" ]
687
740
if ! configExists {
688
- packageConfig ["config" ] = defaultCfg
689
- continue
741
+ // The reason why this should never happen is because getPackageConfigMap
742
+ // should be populating the config section with the top-level config if it
743
+ // wasn't defined in the yaml.
744
+ msg := "config section does not exist for package, this should never happen"
745
+ pkgLog .Error ().Msg (msg )
746
+ return fmt .Errorf (msg )
747
+ }
748
+
749
+ pkgLog .Trace ().Msg ("got config section for package" )
750
+ // Sometimes the config section may be provided, but it's nil.
751
+ // We need to account for this fact.
752
+ if configSectionUntyped == nil {
753
+ pkgLog .Trace ().Msg ("config section is nil, converting to empty map" )
754
+ emptyMap := map [string ]any {}
755
+
756
+ // We need to add this to the "global" config mapping so the change
757
+ // gets persisted, and also into configSectionUntyped for the logic
758
+ // further down.
759
+ packageConfig ["config" ] = emptyMap
760
+ configSectionUntyped = emptyMap
761
+ } else {
762
+ pkgLog .Trace ().Msg ("config section is not nil" )
690
763
}
691
- packageConfigSection := configSectionUntyped .(map [string ]any )
764
+
765
+ configSectionTyped := configSectionUntyped .(map [string ]any )
692
766
693
767
for key , value := range defaultCfg {
694
768
if contains ([]string {"packages" , "config" }, key ) {
695
769
continue
696
770
}
697
- _ , keyExists := packageConfigSection [key ]
771
+ keyValLog := pkgLog .With ().Str ("key" , key ).Str ("value" , fmt .Sprintf ("%v" , value )).Logger ()
772
+
773
+ _ , keyExists := configSectionTyped [key ]
698
774
if ! keyExists {
699
- packageConfigSection [key ] = value
775
+ keyValLog .Trace ().Msg ("setting key to value" )
776
+ configSectionTyped [key ] = value
700
777
}
701
778
}
702
- interfaces , err := c .getInterfacesForPackage (ctx , pkgPath )
779
+ interfaces , err := c .getInterfacesForPackage (pkgCtx , pkgPath )
703
780
if err != nil {
704
781
return fmt .Errorf ("failed to get interfaces for package: %w" , err )
705
782
}
706
783
for _ , interfaceName := range interfaces {
707
- interfacesSection , err := c .getInterfacesSection (ctx , pkgPath )
784
+ interfacesSection , err := c .getInterfacesSection (pkgCtx , pkgPath )
708
785
if err != nil {
709
786
return err
710
787
}
@@ -728,7 +805,7 @@ func (c *Config) mergeInConfig(ctx context.Context) error {
728
805
// Assume this interface's value in the map is nil. Just skip it.
729
806
continue
730
807
}
731
- for key , value := range packageConfigSection {
808
+ for key , value := range configSectionTyped {
732
809
if key == "packages" {
733
810
continue
734
811
}
0 commit comments