config.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package conf
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "path"
  7. "reflect"
  8. "strings"
  9. "github.com/zeromicro/go-zero/core/jsonx"
  10. "github.com/zeromicro/go-zero/core/mapping"
  11. "github.com/zeromicro/go-zero/internal/encoding"
  12. )
  13. var loaders = map[string]func([]byte, any) error{
  14. ".json": LoadFromJsonBytes,
  15. ".toml": LoadFromTomlBytes,
  16. ".yaml": LoadFromYamlBytes,
  17. ".yml": LoadFromYamlBytes,
  18. }
  19. type fieldInfo struct {
  20. name string
  21. children map[string]fieldInfo
  22. }
  23. // Load loads config into v from file, .json, .yaml and .yml are acceptable.
  24. func Load(file string, v any, opts ...Option) error {
  25. content, err := os.ReadFile(file)
  26. if err != nil {
  27. return err
  28. }
  29. loader, ok := loaders[strings.ToLower(path.Ext(file))]
  30. if !ok {
  31. return fmt.Errorf("unrecognized file type: %s", file)
  32. }
  33. var opt options
  34. for _, o := range opts {
  35. o(&opt)
  36. }
  37. if opt.env {
  38. return loader([]byte(os.ExpandEnv(string(content))), v)
  39. }
  40. return loader(content, v)
  41. }
  42. // LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
  43. // Deprecated: use Load instead.
  44. func LoadConfig(file string, v any, opts ...Option) error {
  45. return Load(file, v, opts...)
  46. }
  47. // LoadFromJsonBytes loads config into v from content json bytes.
  48. func LoadFromJsonBytes(content []byte, v any) error {
  49. var m map[string]any
  50. if err := jsonx.Unmarshal(content, &m); err != nil {
  51. return err
  52. }
  53. finfo := buildFieldsInfo(reflect.TypeOf(v))
  54. lowerCaseKeyMap := toLowerCaseKeyMap(m, finfo)
  55. return mapping.UnmarshalJsonMap(lowerCaseKeyMap, v, mapping.WithCanonicalKeyFunc(toLowerCase))
  56. }
  57. // LoadConfigFromJsonBytes loads config into v from content json bytes.
  58. // Deprecated: use LoadFromJsonBytes instead.
  59. func LoadConfigFromJsonBytes(content []byte, v any) error {
  60. return LoadFromJsonBytes(content, v)
  61. }
  62. // LoadFromTomlBytes loads config into v from content toml bytes.
  63. func LoadFromTomlBytes(content []byte, v any) error {
  64. b, err := encoding.TomlToJson(content)
  65. if err != nil {
  66. return err
  67. }
  68. return LoadFromJsonBytes(b, v)
  69. }
  70. // LoadFromYamlBytes loads config into v from content yaml bytes.
  71. func LoadFromYamlBytes(content []byte, v any) error {
  72. b, err := encoding.YamlToJson(content)
  73. if err != nil {
  74. return err
  75. }
  76. return LoadFromJsonBytes(b, v)
  77. }
  78. // LoadConfigFromYamlBytes loads config into v from content yaml bytes.
  79. // Deprecated: use LoadFromYamlBytes instead.
  80. func LoadConfigFromYamlBytes(content []byte, v any) error {
  81. return LoadFromYamlBytes(content, v)
  82. }
  83. // MustLoad loads config into v from path, exits on error.
  84. func MustLoad(path string, v any, opts ...Option) {
  85. if err := Load(path, v, opts...); err != nil {
  86. log.Fatalf("error: config file %s, %s", path, err.Error())
  87. }
  88. }
  89. func buildFieldsInfo(tp reflect.Type) map[string]fieldInfo {
  90. tp = mapping.Deref(tp)
  91. switch tp.Kind() {
  92. case reflect.Struct:
  93. return buildStructFieldsInfo(tp)
  94. case reflect.Array, reflect.Slice:
  95. return buildFieldsInfo(mapping.Deref(tp.Elem()))
  96. default:
  97. return nil
  98. }
  99. }
  100. func buildStructFieldsInfo(tp reflect.Type) map[string]fieldInfo {
  101. info := make(map[string]fieldInfo)
  102. for i := 0; i < tp.NumField(); i++ {
  103. field := tp.Field(i)
  104. name := field.Name
  105. lowerCaseName := toLowerCase(name)
  106. ft := mapping.Deref(field.Type)
  107. // flatten anonymous fields
  108. if field.Anonymous {
  109. if ft.Kind() == reflect.Struct {
  110. fields := buildFieldsInfo(ft)
  111. for k, v := range fields {
  112. info[k] = v
  113. }
  114. } else {
  115. info[lowerCaseName] = fieldInfo{
  116. name: name,
  117. }
  118. }
  119. continue
  120. }
  121. var fields map[string]fieldInfo
  122. switch ft.Kind() {
  123. case reflect.Struct:
  124. fields = buildFieldsInfo(ft)
  125. case reflect.Array, reflect.Slice:
  126. fields = buildFieldsInfo(ft.Elem())
  127. case reflect.Map:
  128. fields = buildFieldsInfo(ft.Elem())
  129. }
  130. if prev, ok := info[lowerCaseName]; ok {
  131. // merge fields
  132. for k, v := range fields {
  133. prev.children[k] = v
  134. }
  135. } else {
  136. info[lowerCaseName] = fieldInfo{
  137. name: name,
  138. children: fields,
  139. }
  140. }
  141. }
  142. return info
  143. }
  144. func toLowerCase(s string) string {
  145. return strings.ToLower(s)
  146. }
  147. func toLowerCaseInterface(v any, info map[string]fieldInfo) any {
  148. switch vv := v.(type) {
  149. case map[string]any:
  150. return toLowerCaseKeyMap(vv, info)
  151. case []any:
  152. var arr []any
  153. for _, vvv := range vv {
  154. arr = append(arr, toLowerCaseInterface(vvv, info))
  155. }
  156. return arr
  157. default:
  158. return v
  159. }
  160. }
  161. func toLowerCaseKeyMap(m map[string]any, info map[string]fieldInfo) map[string]any {
  162. res := make(map[string]any)
  163. for k, v := range m {
  164. ti, ok := info[k]
  165. if ok {
  166. res[k] = toLowerCaseInterface(v, ti.children)
  167. continue
  168. }
  169. lk := toLowerCase(k)
  170. if ti, ok = info[lk]; ok {
  171. res[lk] = toLowerCaseInterface(v, ti.children)
  172. } else {
  173. res[k] = v
  174. }
  175. }
  176. return res
  177. }