config.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. const distanceBetweenUpperAndLower = 32
  14. var loaders = map[string]func([]byte, interface{}) error{
  15. ".json": LoadFromJsonBytes,
  16. ".toml": LoadFromTomlBytes,
  17. ".yaml": LoadFromYamlBytes,
  18. ".yml": LoadFromYamlBytes,
  19. }
  20. type fieldInfo struct {
  21. name string
  22. kind reflect.Kind
  23. children map[string]fieldInfo
  24. }
  25. // Load loads config into v from file, .json, .yaml and .yml are acceptable.
  26. func Load(file string, v interface{}, opts ...Option) error {
  27. content, err := os.ReadFile(file)
  28. if err != nil {
  29. return err
  30. }
  31. loader, ok := loaders[strings.ToLower(path.Ext(file))]
  32. if !ok {
  33. return fmt.Errorf("unrecognized file type: %s", file)
  34. }
  35. var opt options
  36. for _, o := range opts {
  37. o(&opt)
  38. }
  39. if opt.env {
  40. return loader([]byte(os.ExpandEnv(string(content))), v)
  41. }
  42. return loader(content, v)
  43. }
  44. // LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
  45. // Deprecated: use Load instead.
  46. func LoadConfig(file string, v interface{}, opts ...Option) error {
  47. return Load(file, v, opts...)
  48. }
  49. // LoadFromJsonBytes loads config into v from content json bytes.
  50. func LoadFromJsonBytes(content []byte, v interface{}) error {
  51. var m map[string]interface{}
  52. if err := jsonx.Unmarshal(content, &m); err != nil {
  53. return err
  54. }
  55. finfo := buildFieldsInfo(reflect.TypeOf(v))
  56. camelCaseKeyMap := toCamelCaseKeyMap(m, finfo)
  57. return mapping.UnmarshalJsonMap(camelCaseKeyMap, v, mapping.WithCanonicalKeyFunc(toCamelCase))
  58. }
  59. // LoadConfigFromJsonBytes loads config into v from content json bytes.
  60. // Deprecated: use LoadFromJsonBytes instead.
  61. func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
  62. return LoadFromJsonBytes(content, v)
  63. }
  64. // LoadFromTomlBytes loads config into v from content toml bytes.
  65. func LoadFromTomlBytes(content []byte, v interface{}) error {
  66. b, err := encoding.TomlToJson(content)
  67. if err != nil {
  68. return err
  69. }
  70. return LoadFromJsonBytes(b, v)
  71. }
  72. // LoadFromYamlBytes loads config into v from content yaml bytes.
  73. func LoadFromYamlBytes(content []byte, v interface{}) error {
  74. b, err := encoding.YamlToJson(content)
  75. if err != nil {
  76. return err
  77. }
  78. return LoadFromJsonBytes(b, v)
  79. }
  80. // LoadConfigFromYamlBytes loads config into v from content yaml bytes.
  81. // Deprecated: use LoadFromYamlBytes instead.
  82. func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
  83. return LoadFromYamlBytes(content, v)
  84. }
  85. // MustLoad loads config into v from path, exits on error.
  86. func MustLoad(path string, v interface{}, opts ...Option) {
  87. if err := Load(path, v, opts...); err != nil {
  88. log.Fatalf("error: config file %s, %s", path, err.Error())
  89. }
  90. }
  91. func buildFieldsInfo(tp reflect.Type) map[string]fieldInfo {
  92. tp = mapping.Deref(tp)
  93. switch tp.Kind() {
  94. case reflect.Struct:
  95. return buildStructFieldsInfo(tp)
  96. case reflect.Array, reflect.Slice:
  97. return buildFieldsInfo(mapping.Deref(tp.Elem()))
  98. default:
  99. return nil
  100. }
  101. }
  102. func buildStructFieldsInfo(tp reflect.Type) map[string]fieldInfo {
  103. info := make(map[string]fieldInfo)
  104. for i := 0; i < tp.NumField(); i++ {
  105. field := tp.Field(i)
  106. name := field.Name
  107. ccName := toCamelCase(name)
  108. ft := mapping.Deref(field.Type)
  109. // flatten anonymous fields
  110. if field.Anonymous {
  111. if ft.Kind() == reflect.Struct {
  112. fields := buildFieldsInfo(ft)
  113. for k, v := range fields {
  114. info[k] = v
  115. }
  116. } else {
  117. info[ccName] = fieldInfo{
  118. name: name,
  119. kind: ft.Kind(),
  120. }
  121. }
  122. continue
  123. }
  124. var fields map[string]fieldInfo
  125. switch ft.Kind() {
  126. case reflect.Struct:
  127. fields = buildFieldsInfo(ft)
  128. case reflect.Array, reflect.Slice:
  129. fields = buildFieldsInfo(ft.Elem())
  130. case reflect.Map:
  131. fields = buildFieldsInfo(ft.Elem())
  132. }
  133. info[ccName] = fieldInfo{
  134. name: name,
  135. kind: ft.Kind(),
  136. children: fields,
  137. }
  138. }
  139. return info
  140. }
  141. func toCamelCase(s string) string {
  142. var buf strings.Builder
  143. buf.Grow(len(s))
  144. var capNext bool
  145. boundary := true
  146. for _, v := range s {
  147. isCap := v >= 'A' && v <= 'Z'
  148. isLow := v >= 'a' && v <= 'z'
  149. if boundary && (isCap || isLow) {
  150. if capNext {
  151. if isLow {
  152. v -= distanceBetweenUpperAndLower
  153. }
  154. } else {
  155. if isCap {
  156. v += distanceBetweenUpperAndLower
  157. }
  158. }
  159. boundary = false
  160. }
  161. if isCap || isLow {
  162. buf.WriteRune(v)
  163. capNext = false
  164. continue
  165. }
  166. switch v {
  167. // '.' is used for chained keys, e.g. "grand.parent.child"
  168. case ' ', '.', '\t':
  169. buf.WriteRune(v)
  170. capNext = false
  171. boundary = true
  172. case '_':
  173. capNext = true
  174. boundary = true
  175. default:
  176. buf.WriteRune(v)
  177. capNext = true
  178. }
  179. }
  180. return buf.String()
  181. }
  182. func toCamelCaseInterface(v interface{}, info map[string]fieldInfo) interface{} {
  183. switch vv := v.(type) {
  184. case map[string]interface{}:
  185. return toCamelCaseKeyMap(vv, info)
  186. case []interface{}:
  187. var arr []interface{}
  188. for _, vvv := range vv {
  189. arr = append(arr, toCamelCaseInterface(vvv, info))
  190. }
  191. return arr
  192. default:
  193. return v
  194. }
  195. }
  196. func toCamelCaseKeyMap(m map[string]interface{}, info map[string]fieldInfo) map[string]interface{} {
  197. res := make(map[string]interface{})
  198. for k, v := range m {
  199. ti, ok := info[k]
  200. if ok {
  201. res[k] = toCamelCaseInterface(v, ti.children)
  202. continue
  203. }
  204. cck := toCamelCase(k)
  205. if ti, ok = info[cck]; ok {
  206. res[toCamelCase(k)] = toCamelCaseInterface(v, ti.children)
  207. } else {
  208. res[k] = v
  209. }
  210. }
  211. return res
  212. }