123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- package conf
- import (
- "fmt"
- "log"
- "os"
- "path"
- "reflect"
- "strings"
- "github.com/wuntsong-org/go-zero-plus/core/jsonx"
- "github.com/wuntsong-org/go-zero-plus/core/mapping"
- "github.com/wuntsong-org/go-zero-plus/internal/encoding"
- )
- const (
- jsonTagKey = "json"
- jsonTagSep = ','
- )
- var (
- fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
- loaders = map[string]func([]byte, any) error{
- ".json": LoadFromJsonBytes,
- ".toml": LoadFromTomlBytes,
- ".yaml": LoadFromYamlBytes,
- ".yml": LoadFromYamlBytes,
- }
- )
- // children and mapField should not be both filled.
- // named fields and map cannot be bound to the same field name.
- type fieldInfo struct {
- children map[string]*fieldInfo
- mapField *fieldInfo
- }
- // FillDefault fills the default values for the given v,
- // and the premise is that the value of v must be guaranteed to be empty.
- func FillDefault(v any) error {
- return fillDefaultUnmarshaler.Unmarshal(map[string]any{}, v)
- }
- // Load loads config into v from file, .json, .yaml and .yml are acceptable.
- func Load(file string, v any, opts ...Option) error {
- content, err := os.ReadFile(file)
- if err != nil {
- return err
- }
- loader, ok := loaders[strings.ToLower(path.Ext(file))]
- if !ok {
- return fmt.Errorf("unrecognized file type: %s", file)
- }
- var opt options
- for _, o := range opts {
- o(&opt)
- }
- if opt.env {
- return loader([]byte(os.ExpandEnv(string(content))), v)
- }
- return loader(content, v)
- }
- // LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
- // Deprecated: use Load instead.
- func LoadConfig(file string, v any, opts ...Option) error {
- return Load(file, v, opts...)
- }
- // LoadFromJsonBytes loads config into v from content json bytes.
- func LoadFromJsonBytes(content []byte, v any) error {
- info, err := buildFieldsInfo(reflect.TypeOf(v), "")
- if err != nil {
- return err
- }
- var m map[string]any
- if err = jsonx.Unmarshal(content, &m); err != nil {
- return err
- }
- lowerCaseKeyMap := toLowerCaseKeyMap(m, info)
- return mapping.UnmarshalJsonMap(lowerCaseKeyMap, v, mapping.WithCanonicalKeyFunc(toLowerCase))
- }
- // LoadConfigFromJsonBytes loads config into v from content json bytes.
- // Deprecated: use LoadFromJsonBytes instead.
- func LoadConfigFromJsonBytes(content []byte, v any) error {
- return LoadFromJsonBytes(content, v)
- }
- // LoadFromTomlBytes loads config into v from content toml bytes.
- func LoadFromTomlBytes(content []byte, v any) error {
- b, err := encoding.TomlToJson(content)
- if err != nil {
- return err
- }
- return LoadFromJsonBytes(b, v)
- }
- // LoadFromYamlBytes loads config into v from content yaml bytes.
- func LoadFromYamlBytes(content []byte, v any) error {
- b, err := encoding.YamlToJson(content)
- if err != nil {
- return err
- }
- return LoadFromJsonBytes(b, v)
- }
- // LoadConfigFromYamlBytes loads config into v from content yaml bytes.
- // Deprecated: use LoadFromYamlBytes instead.
- func LoadConfigFromYamlBytes(content []byte, v any) error {
- return LoadFromYamlBytes(content, v)
- }
- // MustLoad loads config into v from path, exits on error.
- func MustLoad(path string, v any, opts ...Option) {
- if err := Load(path, v, opts...); err != nil {
- log.Fatalf("error: config file %s, %s", path, err.Error())
- }
- }
- func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName string) error {
- if prev, ok := info.children[key]; ok {
- if child.mapField != nil {
- return newConflictKeyError(fullName)
- }
- if err := mergeFields(prev, key, child.children, fullName); err != nil {
- return err
- }
- } else {
- info.children[key] = child
- }
- return nil
- }
- func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
- switch ft.Kind() {
- case reflect.Struct:
- fields, err := buildFieldsInfo(ft, fullName)
- if err != nil {
- return err
- }
- for k, v := range fields.children {
- if err = addOrMergeFields(info, k, v, fullName); err != nil {
- return err
- }
- }
- case reflect.Map:
- elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
- if err != nil {
- return err
- }
- if _, ok := info.children[lowerCaseName]; ok {
- return newConflictKeyError(fullName)
- }
- info.children[lowerCaseName] = &fieldInfo{
- children: make(map[string]*fieldInfo),
- mapField: elemField,
- }
- default:
- if _, ok := info.children[lowerCaseName]; ok {
- return newConflictKeyError(fullName)
- }
- info.children[lowerCaseName] = &fieldInfo{
- children: make(map[string]*fieldInfo),
- }
- }
- return nil
- }
- func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
- tp = mapping.Deref(tp)
- switch tp.Kind() {
- case reflect.Struct:
- return buildStructFieldsInfo(tp, fullName)
- case reflect.Array, reflect.Slice:
- return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName)
- case reflect.Chan, reflect.Func:
- return nil, fmt.Errorf("unsupported type: %s", tp.Kind())
- default:
- return &fieldInfo{
- children: make(map[string]*fieldInfo),
- }, nil
- }
- }
- func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
- var finfo *fieldInfo
- var err error
- switch ft.Kind() {
- case reflect.Struct:
- finfo, err = buildFieldsInfo(ft, fullName)
- if err != nil {
- return err
- }
- case reflect.Array, reflect.Slice:
- finfo, err = buildFieldsInfo(ft.Elem(), fullName)
- if err != nil {
- return err
- }
- case reflect.Map:
- elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
- if err != nil {
- return err
- }
- finfo = &fieldInfo{
- children: make(map[string]*fieldInfo),
- mapField: elemInfo,
- }
- default:
- finfo, err = buildFieldsInfo(ft, fullName)
- if err != nil {
- return err
- }
- }
- return addOrMergeFields(info, lowerCaseName, finfo, fullName)
- }
- func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
- info := &fieldInfo{
- children: make(map[string]*fieldInfo),
- }
- for i := 0; i < tp.NumField(); i++ {
- field := tp.Field(i)
- if !field.IsExported() {
- continue
- }
- name := getTagName(field)
- lowerCaseName := toLowerCase(name)
- ft := mapping.Deref(field.Type)
- // flatten anonymous fields
- if field.Anonymous {
- if err := buildAnonymousFieldInfo(info, lowerCaseName, ft,
- getFullName(fullName, lowerCaseName)); err != nil {
- return nil, err
- }
- } else if err := buildNamedFieldInfo(info, lowerCaseName, ft,
- getFullName(fullName, lowerCaseName)); err != nil {
- return nil, err
- }
- }
- return info, nil
- }
- // getTagName get the tag name of the given field, if no tag name, use file.Name.
- // field.Name is returned on tags like `json:""` and `json:",optional"`.
- func getTagName(field reflect.StructField) string {
- if tag, ok := field.Tag.Lookup(jsonTagKey); ok {
- if pos := strings.IndexByte(tag, jsonTagSep); pos >= 0 {
- tag = tag[:pos]
- }
- tag = strings.TrimSpace(tag)
- if len(tag) > 0 {
- return tag
- }
- }
- return field.Name
- }
- func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo, fullName string) error {
- if len(prev.children) == 0 || len(children) == 0 {
- return newConflictKeyError(fullName)
- }
- // merge fields
- for k, v := range children {
- if _, ok := prev.children[k]; ok {
- return newConflictKeyError(fullName)
- }
- prev.children[k] = v
- }
- return nil
- }
- func toLowerCase(s string) string {
- return strings.ToLower(s)
- }
- func toLowerCaseInterface(v any, info *fieldInfo) any {
- switch vv := v.(type) {
- case map[string]any:
- return toLowerCaseKeyMap(vv, info)
- case []any:
- var arr []any
- for _, vvv := range vv {
- arr = append(arr, toLowerCaseInterface(vvv, info))
- }
- return arr
- default:
- return v
- }
- }
- func toLowerCaseKeyMap(m map[string]any, info *fieldInfo) map[string]any {
- res := make(map[string]any)
- for k, v := range m {
- ti, ok := info.children[k]
- if ok {
- res[k] = toLowerCaseInterface(v, ti)
- continue
- }
- lk := toLowerCase(k)
- if ti, ok = info.children[lk]; ok {
- res[lk] = toLowerCaseInterface(v, ti)
- } else if info.mapField != nil {
- res[k] = toLowerCaseInterface(v, info.mapField)
- } else {
- res[k] = v
- }
- }
- return res
- }
- type conflictKeyError struct {
- key string
- }
- func newConflictKeyError(key string) conflictKeyError {
- return conflictKeyError{key: key}
- }
- func (e conflictKeyError) Error() string {
- return fmt.Sprintf("conflict key %s, pay attention to anonymous fields", e.key)
- }
- func getFullName(parent, child string) string {
- if len(parent) == 0 {
- return child
- }
- return strings.Join([]string{parent, child}, ".")
- }
|