diff --git a/pkg/infrastructure/config/ini.go b/pkg/infrastructure/config/ini.go index 92ed8df8..cc67e4cd 100644 --- a/pkg/infrastructure/config/ini.go +++ b/pkg/infrastructure/config/ini.go @@ -66,6 +66,10 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e keyComment: make(map[string]string), RWMutex: sync.RWMutex{}, } + + cfg.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) { + return cfg.getdata(key), nil + }) cfg.Lock() defer cfg.Unlock() diff --git a/pkg/infrastructure/config/json/json.go b/pkg/infrastructure/config/json/json.go index dae55118..c65eff4d 100644 --- a/pkg/infrastructure/config/json/json.go +++ b/pkg/infrastructure/config/json/json.go @@ -25,7 +25,10 @@ import ( "strings" "sync" + "github.com/mitchellh/mapstructure" + "github.com/astaxie/beego/pkg/infrastructure/config" + "github.com/astaxie/beego/pkg/infrastructure/logs" ) // JSONConfig is a json config parser and implements Config interface. @@ -70,11 +73,48 @@ func (js *JSONConfig) ParseData(data []byte) (config.Configer, error) { // JSONConfigContainer is a config which represents the json configuration. // Only when get value, support key as section:name type. type JSONConfigContainer struct { - config.BaseConfiger data map[string]interface{} sync.RWMutex } +func (c *JSONConfigContainer) Unmarshaler(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error { + sub, err := c.sub(ctx, prefix) + if err != nil { + return err + } + return mapstructure.Decode(sub, obj) +} + +func (c *JSONConfigContainer) Sub(ctx context.Context, key string) (config.Configer, error) { + sub, err := c.sub(ctx, key) + if err != nil { + return nil, err + } + return &JSONConfigContainer{ + data: sub, + }, nil +} + +func (c *JSONConfigContainer) sub(ctx context.Context, key string) (map[string]interface{}, error) { + if key == "" { + return c.data, nil + } + value, ok := c.data[key] + if !ok { + return nil, errors.New(fmt.Sprintf("key is not found: %s", key)) + } + + res, ok := value.(map[string]interface{}) + if !ok { + return nil, errors.New(fmt.Sprintf("the type of value is invalid, key: %s", key)) + } + return res, nil +} + +func (c *JSONConfigContainer) OnChange(ctx context.Context, key string, fn func(value string)) { + logs.Warn("unsupported operation") +} + // Bool returns the boolean value for a given key. func (c *JSONConfigContainer) Bool(ctx context.Context, key string) (bool, error) { val := c.getData(key) diff --git a/pkg/infrastructure/config/json/json_test.go b/pkg/infrastructure/config/json/json_test.go index 486d2b11..5275ee57 100644 --- a/pkg/infrastructure/config/json/json_test.go +++ b/pkg/infrastructure/config/json/json_test.go @@ -15,10 +15,13 @@ package json import ( + "context" "fmt" "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/astaxie/beego/pkg/infrastructure/config" ) @@ -223,4 +226,27 @@ func TestJson(t *testing.T) { if !jsonconf.DefaultBool(nil, "unknown", true) { t.Error("unknown keys with default value wrong") } + + sub, err := jsonconf.Sub(context.Background(), "database") + assert.Nil(t, err) + assert.NotNil(t, sub) + + sub, err = sub.Sub(context.Background(), "conns") + assert.Nil(t, err) + + maxCon, _ := sub.Int(context.Background(), "maxconnection") + assert.Equal(t, 12, maxCon) + + dbCfg := &DatabaseConfig{} + err = sub.Unmarshaler(context.Background(), "", dbCfg) + assert.Nil(t, err) + assert.Equal(t, 12, dbCfg.MaxConnection) + assert.True(t, dbCfg.Autoconnect) + assert.Equal(t, "info", dbCfg.Connectioninfo) +} + +type DatabaseConfig struct { + MaxConnection int `json:"maxconnection"` + Autoconnect bool `json:"autoconnect"` + Connectioninfo string `json:"connectioninfo"` } diff --git a/pkg/infrastructure/config/xml/xml.go b/pkg/infrastructure/config/xml/xml.go index e3e93b01..e5096b9b 100644 --- a/pkg/infrastructure/config/xml/xml.go +++ b/pkg/infrastructure/config/xml/xml.go @@ -26,7 +26,7 @@ // // cnf, err := config.NewConfig("xml", "config.xml") // -//More docs http://beego.me/docs/module/config.md +// More docs http://beego.me/docs/module/config.md package xml import ( @@ -40,7 +40,11 @@ import ( "strings" "sync" + "github.com/mitchellh/mapstructure" + "github.com/astaxie/beego/pkg/infrastructure/config" + "github.com/astaxie/beego/pkg/infrastructure/logs" + "github.com/beego/x2j" ) @@ -75,11 +79,53 @@ func (xc *Config) ParseData(data []byte) (config.Configer, error) { // ConfigContainer is a Config which represents the xml configuration. type ConfigContainer struct { - config.BaseConfiger data map[string]interface{} sync.Mutex } +// Unmarshaler is a little be inconvenient since the xml library doesn't know type. +// So when you use +// 1 +// The "1" is a string, not int +func (c *ConfigContainer) Unmarshaler(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error { + sub, err := c.sub(ctx, prefix) + if err != nil { + return err + } + return mapstructure.Decode(sub, obj) +} + +func (c *ConfigContainer) Sub(ctx context.Context, key string) (config.Configer, error) { + sub, err := c.sub(ctx, key) + if err != nil { + return nil, err + } + + return &ConfigContainer{ + data: sub, + }, nil + +} + +func (c *ConfigContainer) sub(ctx context.Context, key string) (map[string]interface{}, error) { + if key == "" { + return c.data, nil + } + value, ok := c.data[key] + if !ok { + return nil, errors.New(fmt.Sprintf("the key is not found: %s", key)) + } + res, ok := value.(map[string]interface{}) + if !ok { + return nil, errors.New(fmt.Sprintf("the value of this key is not a structure: %s", key)) + } + return res, nil +} + +func (c *ConfigContainer) OnChange(ctx context.Context, key string, fn func(value string)) { + logs.Warn("Unsupported operation") +} + // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(ctx context.Context, key string) (bool, error) { if v := c.data[key]; v != nil { @@ -155,7 +201,7 @@ func (c *ConfigContainer) String(ctx context.Context, key string) (string, error // DefaultString returns the string value for a given key. // if err != nil return defaultVal func (c *ConfigContainer) DefaultString(ctx context.Context, key string, defaultVal string) string { - v, err := c.String(nil, key) + v, err := c.String(ctx, key) if v == "" || err != nil { return defaultVal } diff --git a/pkg/infrastructure/config/xml/xml_test.go b/pkg/infrastructure/config/xml/xml_test.go index 470280e0..0a3eb313 100644 --- a/pkg/infrastructure/config/xml/xml_test.go +++ b/pkg/infrastructure/config/xml/xml_test.go @@ -15,10 +15,13 @@ package xml import ( + "context" "fmt" "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/astaxie/beego/pkg/infrastructure/config" ) @@ -120,8 +123,36 @@ func TestXML(t *testing.T) { t.Fatal(err) } - res, _ := xmlconf.String(nil, "name") + res, _ := xmlconf.String(context.Background(), "name") if res != "astaxie" { t.Fatal("get name error") } + + sub, err := xmlconf.Sub(context.Background(), "mysection") + assert.Nil(t, err) + assert.NotNil(t, sub) + name, err := sub.String(context.Background(), "name") + assert.Nil(t, err) + assert.Equal(t, "MySection", name) + + id, err := sub.Int(context.Background(), "id") + assert.Nil(t, err) + assert.Equal(t, 1, id) + + sec := &Section{} + + err = sub.Unmarshaler(context.Background(), "", sec) + assert.Nil(t, err) + assert.Equal(t, "MySection", sec.Name) + + sec = &Section{} + + err = xmlconf.Unmarshaler(context.Background(), "mysection", sec) + assert.Nil(t, err) + assert.Equal(t, "MySection", sec.Name) + +} + +type Section struct { + Name string `xml:"name"` } diff --git a/pkg/infrastructure/config/yaml/yaml.go b/pkg/infrastructure/config/yaml/yaml.go index 1f4f1d23..61ea45b9 100644 --- a/pkg/infrastructure/config/yaml/yaml.go +++ b/pkg/infrastructure/config/yaml/yaml.go @@ -42,8 +42,10 @@ import ( "sync" "github.com/beego/goyaml2" + "gopkg.in/yaml.v2" "github.com/astaxie/beego/pkg/infrastructure/config" + "github.com/astaxie/beego/pkg/infrastructure/logs" ) // Config is a yaml config parser and implements Config interface. @@ -120,11 +122,61 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) { // ConfigContainer is a config which represents the yaml configuration. type ConfigContainer struct { - config.BaseConfiger data map[string]interface{} sync.RWMutex } +// Unmarshaler is similar to Sub +func (c *ConfigContainer) Unmarshaler(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error { + sub, err := c.sub(ctx, prefix) + if err != nil { + return err + } + + bytes, err := yaml.Marshal(sub) + if err != nil { + return err + } + return yaml.Unmarshal(bytes, obj) +} + +func (c *ConfigContainer) Sub(ctx context.Context, key string) (config.Configer, error) { + sub, err := c.sub(ctx, key) + if err != nil { + return nil, err + } + return &ConfigContainer{ + data: sub, + }, nil +} + +func (c *ConfigContainer) sub(ctx context.Context, key string) (map[string]interface{}, error) { + tmpData := c.data + keys := strings.Split(key, ".") + for idx, k := range keys { + if v, ok := tmpData[k]; ok { + switch v.(type) { + case map[string]interface{}: + { + tmpData = v.(map[string]interface{}) + if idx == len(keys)-1 { + return tmpData, nil + } + } + default: + return nil, errors.New(fmt.Sprintf("the key is invalid: %s", key)) + } + } + } + + return tmpData, nil +} + +func (c *ConfigContainer) OnChange(ctx context.Context, key string, fn func(value string)) { + // do nothing + logs.Warn("Unsupported operation: OnChange") +} + // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(ctx context.Context, key string) (bool, error) { v, err := c.getData(key) @@ -291,7 +343,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { c.RLock() defer c.RUnlock() - keys := strings.Split(key, ".") + keys := strings.Split(c.key(key), ".") tmpData := c.data for idx, k := range keys { if v, ok := tmpData[k]; ok { @@ -314,6 +366,10 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { return nil, fmt.Errorf("not exist key %q", key) } +func (c *ConfigContainer) key(key string) string { + return key +} + func init() { config.Register("yaml", &Config{}) } diff --git a/pkg/infrastructure/config/yaml/yaml_test.go b/pkg/infrastructure/config/yaml/yaml_test.go index 197b68e4..1fd4e894 100644 --- a/pkg/infrastructure/config/yaml/yaml_test.go +++ b/pkg/infrastructure/config/yaml/yaml_test.go @@ -15,10 +15,13 @@ package yaml import ( + "context" "fmt" "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/astaxie/beego/pkg/infrastructure/config" ) @@ -37,6 +40,9 @@ func TestYaml(t *testing.T) { "path1": ${GOPATH} "path2": ${GOPATH||/home/go} "empty": "" +"user": + "name": "tom" + "age": 13 ` keyValue = map[string]interface{}{ @@ -114,4 +120,33 @@ func TestYaml(t *testing.T) { t.Fatal("get name error") } + sub, err := yamlconf.Sub(context.Background(), "user") + assert.Nil(t, err) + assert.NotNil(t, sub) + name, err := sub.String(context.Background(), "name") + assert.Nil(t, err) + assert.Equal(t, "tom", name) + + age, err := sub.Int(context.Background(), "age") + assert.Nil(t, err) + assert.Equal(t, 13, age) + + user := &User{} + + err = sub.Unmarshaler(context.Background(), "", user) + assert.Nil(t, err) + assert.Equal(t, "tom", user.Name) + assert.Equal(t, 13, user.Age) + + user = &User{} + + err = yamlconf.Unmarshaler(context.Background(), "user", user) + assert.Nil(t, err) + assert.Equal(t, "tom", user.Name) + assert.Equal(t, 13, user.Age) +} + +type User struct { + Name string `yaml:"name"` + Age int `yaml:"age"` }