config.go 4.7 KB

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