merge.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /*
  2. Copyright 2024 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package clientcmd
  14. import (
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. )
  19. // recursively merges src into dst:
  20. // - non-pointer struct fields with any exported fields are recursively merged
  21. // - non-pointer struct fields with only unexported fields prefer src if the field is non-zero
  22. // - maps are shallow merged with src keys taking priority over dst
  23. // - non-zero src fields encountered during recursion that are not maps or structs overwrite and recursion stops
  24. func merge[T any](dst, src *T) error {
  25. if dst == nil {
  26. return fmt.Errorf("cannot merge into nil pointer")
  27. }
  28. if src == nil {
  29. return nil
  30. }
  31. return mergeValues(nil, reflect.ValueOf(dst).Elem(), reflect.ValueOf(src).Elem())
  32. }
  33. func mergeValues(fieldNames []string, dst, src reflect.Value) error {
  34. dstType := dst.Type()
  35. // no-op if we can't read the src
  36. if !src.IsValid() {
  37. return nil
  38. }
  39. // sanity check types match
  40. if srcType := src.Type(); dstType != srcType {
  41. return fmt.Errorf("cannot merge mismatched types (%s, %s) at %s", dstType, srcType, strings.Join(fieldNames, "."))
  42. }
  43. switch dstType.Kind() {
  44. case reflect.Struct:
  45. if hasExportedField(dstType) {
  46. // recursively merge
  47. for i, n := 0, dstType.NumField(); i < n; i++ {
  48. if err := mergeValues(append(fieldNames, dstType.Field(i).Name), dst.Field(i), src.Field(i)); err != nil {
  49. return err
  50. }
  51. }
  52. } else if dst.CanSet() {
  53. // If all fields are unexported, overwrite with src.
  54. // Using src.IsZero() would make more sense but that's not what mergo did.
  55. dst.Set(src)
  56. }
  57. case reflect.Map:
  58. if dst.CanSet() && !src.IsZero() {
  59. // initialize dst if needed
  60. if dst.IsZero() {
  61. dst.Set(reflect.MakeMap(dstType))
  62. }
  63. // shallow-merge overwriting dst keys with src keys
  64. for _, mapKey := range src.MapKeys() {
  65. dst.SetMapIndex(mapKey, src.MapIndex(mapKey))
  66. }
  67. }
  68. case reflect.Slice:
  69. if dst.CanSet() && src.Len() > 0 {
  70. // overwrite dst with non-empty src slice
  71. dst.Set(src)
  72. }
  73. case reflect.Pointer:
  74. if dst.CanSet() && !src.IsZero() {
  75. // overwrite dst with non-zero values for other types
  76. if dstType.Elem().Kind() == reflect.Struct {
  77. // use struct pointer as-is
  78. dst.Set(src)
  79. } else {
  80. // shallow-copy non-struct pointer (interfaces, primitives, etc)
  81. dst.Set(reflect.New(dstType.Elem()))
  82. dst.Elem().Set(src.Elem())
  83. }
  84. }
  85. default:
  86. if dst.CanSet() && !src.IsZero() {
  87. // overwrite dst with non-zero values for other types
  88. dst.Set(src)
  89. }
  90. }
  91. return nil
  92. }
  93. // hasExportedField returns true if the given type has any exported fields,
  94. // or if it has any anonymous/embedded struct fields with exported fields
  95. func hasExportedField(dstType reflect.Type) bool {
  96. for i, n := 0, dstType.NumField(); i < n; i++ {
  97. field := dstType.Field(i)
  98. if field.Anonymous && field.Type.Kind() == reflect.Struct {
  99. if hasExportedField(dstType.Field(i).Type) {
  100. return true
  101. }
  102. } else if len(field.PkgPath) == 0 {
  103. return true
  104. }
  105. }
  106. return false
  107. }