diff --git a/.gitignore b/.gitignore index ac2c8e5..c633d30 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.a *.so *.swp +main # Folders _obj diff --git a/cli/cmd.go b/cli/cmd.go index ee44220..6d79310 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -137,32 +137,27 @@ func (this *Command) Init(i interface{}) error { func (this *Command) parseValue(v reflect.Value) error { typeOfStruct := v.Type() + var err error - for i := 0; i < v.NumField(); i++ { + for i := 0; i < v.NumField() && err == nil; i++ { valueOfField := v.Field(i) kindOfField := valueOfField.Kind() structOfField := typeOfStruct.Field(i) if kindOfField == reflect.Ptr { if !valueOfField.IsNil() && valueOfField.CanSet() { - cmd := this.createCliFlagSet(structOfField.Tag) - if err := cmd.Init(valueOfField.Interface()); err != nil { - return err - } + cmd := this.createSubCommand(structOfField.Tag) + err = cmd.Init(valueOfField.Interface()) } } else if kindOfField == reflect.Struct { - cmd := this.createCliFlagSet(structOfField.Tag) - if err := cmd.parseValue(valueOfField); err != nil { - return err - } + cmd := this.createSubCommand(structOfField.Tag) + err = cmd.parseValue(valueOfField) } else { - if err := this.addFlag(valueOfField, structOfField); err != nil { - return err - } + err = this.addFlag(valueOfField, structOfField) } } - return nil + return err } func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error { @@ -207,7 +202,7 @@ func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error { return nil } -func (this *Command) createCliFlagSet(tag reflect.StructTag) *Command { +func (this *Command) createSubCommand(tag reflect.StructTag) *Command { cmdTag, ok := tag.Lookup("cmd") if !ok || cmdTag == "" { return this diff --git a/cli/cmd_test.go b/cli/cmd_test.go index 513bf9c..f7b30ed 100644 --- a/cli/cmd_test.go +++ b/cli/cmd_test.go @@ -5,59 +5,13 @@ import ( "strconv" "testing" + "github.com/eschao/config/test" "github.com/stretchr/testify/assert" ) -type dbConfig struct { - Host string `cmd:"dbHost database server hostname"` - Port int `cmd:"dbPort database server port"` - User string `cmd:"dbUser database username"` - Password string `cmd:"dbPassword database user password"` - Log logConfig `cmd:"log database log configuration"` -} - -type loginConfig struct { - User string `cmd:"user login username"` - Password string `cmd:"password login password"` -} - -type logConfig struct { - Path string `cmd:"path log path"` - Level string `cmd:"level log level {debug|warning|error}"` -} - -type serviceConfig struct { - Host string `cmd:"hostname service hostname"` - Port int `cmd:"port service port"` - DBConfig dbConfig `cmd:"database database configuration"` - Login *loginConfig `cmd:"login login user and password"` - Log logConfig `cmd:"log service log configuration"` -} - -type typesConfig struct { - BoolValue bool `cmd:"bool boolean value"` - StrValue string `cmd:"str string value"` - Int8Value int8 `cmd:"int8 int8 value"` - Int16Value int16 `cmd:"int16 int16 value"` - IntValue int `cmd:"int int value"` - Int32Value int32 `cmd:"int32 int32 value"` - Int64Value int64 `cmd:"int64 int64 value"` - Uint8Value uint8 `cmd:"uint8 uint8 value"` - Uint16Value uint16 `cmd:"uint16 uint16 value"` - UintValue uint `cmd:"uint uint value"` - Uint32Value uint32 `cmd:"uint32 uint32 value"` - Uint64Value uint64 `cmd:"uint64 uint64 value"` - Float32Value float32 `cmd:"float32 float32 value"` - Float64Value float64 `cmd:"float64 float64 value"` -} - -type defValueConfig struct { - BoolValue bool `cmd:"bool boolean value" default:"true"` -} - func TestServiceCommand(t *testing.T) { assert := assert.New(t) - serviceConfig := serviceConfig{} + serviceConfig := test.ServiceConfig{} cmd := New("Service") err := cmd.Init(&serviceConfig) if err != nil { @@ -66,80 +20,65 @@ func TestServiceCommand(t *testing.T) { // assert service cmd assert.NotNil(cmd.FlagSet) - assert.NotNil(cmd.FlagSet.Lookup("hostname"), - "service cmd should have {hostname} parameter") - assert.NotNil(cmd.FlagSet.Lookup("port"), - "service cmd should have {port} parameter") - assert.Equal(2, len(cmd.SubCommands), - "service cmd should have 2 sub cmds") - assert.Nil(cmd.SubCommands["login"], - "service cmd shouldn't have {login} sub cmd") + assert.NotNil(cmd.FlagSet.Lookup("hostname")) + assert.NotNil(cmd.FlagSet.Lookup("port")) + assert.Equal(2, len(cmd.SubCommands)) + assert.Nil(cmd.SubCommands["login"]) // assert database sub cmd dbCmd := cmd.SubCommands["database"] assert.NotNil(dbCmd, "service cmd should have {database} sub cmd") - assert.NotNil(dbCmd.FlagSet.Lookup("dbHost"), - "database cmd should have {dbHost} parameter") - assert.NotNil(dbCmd.FlagSet.Lookup("dbPort"), - "database cmd should have {dbPort} parameter") - assert.NotNil(dbCmd.FlagSet.Lookup("dbUser"), - "database cmd should have {dbUser} parameter") - assert.NotNil(dbCmd.FlagSet.Lookup("dbPassword"), - "database cmd should have {dbPassword} parameter") + assert.NotNil(dbCmd.FlagSet.Lookup("dbHost")) + assert.NotNil(dbCmd.FlagSet.Lookup("dbPort")) + assert.NotNil(dbCmd.FlagSet.Lookup("dbUser")) + assert.NotNil(dbCmd.FlagSet.Lookup("dbPassword")) // assert database log sub cmd dbLogCmd := dbCmd.SubCommands["log"] assert.NotNil(dbCmd, "database cmd should have {log} sub cmd") - assert.NotNil(dbLogCmd.FlagSet.Lookup("path"), - "database log cmd should have {path} parameter") - assert.NotNil(dbLogCmd.FlagSet.Lookup("level"), - "database log cmd should have {level} parameter") - assert.Equal(0, len(dbLogCmd.SubCommands), - "database log cmd shouldn't have sub cmd") + assert.NotNil(dbLogCmd.FlagSet.Lookup("path")) + assert.NotNil(dbLogCmd.FlagSet.Lookup("level")) + assert.Equal(0, len(dbLogCmd.SubCommands)) // assert log cmd logCmd := cmd.SubCommands["log"] assert.NotNil(logCmd, "service cmd should have {log} sub cmd") - assert.NotNil(logCmd.FlagSet.Lookup("path"), - "log cmd should have {path} parameter") - assert.NotNil(logCmd.FlagSet.Lookup("level"), - "log cmd should have {level} parameter") + assert.NotNil(logCmd.FlagSet.Lookup("path")) + assert.NotNil(logCmd.FlagSet.Lookup("level")) } func TestLoginSubCommand(t *testing.T) { assert := assert.New(t) - serviceConfig := serviceConfig{Login: &loginConfig{}} + serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}} cmd := New("Service") - assert.NoError(cmd.Init(&serviceConfig), "Can't init service command") + assert.NoError(cmd.Init(&serviceConfig)) // assert login sub command loginCmd := cmd.SubCommands["login"] assert.NotNil(loginCmd, "service cmd should have {login} sub cmd") - assert.NotNil(loginCmd.FlagSet.Lookup("user"), - "login cmd should have {user} parameter") - assert.NotNil(loginCmd.FlagSet.Lookup("password"), - "login cmd should have {password} parameter") + assert.NotNil(loginCmd.FlagSet.Lookup("user")) + assert.NotNil(loginCmd.FlagSet.Lookup("password")) } func TestLoginCommandWithValues(t *testing.T) { assert := assert.New(t) - loginConfig := loginConfig{} + loginConfig := test.LoginConfig{} cmd := New("Login") assert.NoError(cmd.Init(&loginConfig), "Can't init login command") username := "test-user" password := "test-passwd" args := []string{"-user", username, "--password", password} - assert.NoError(cmd.Parse(args), "Can't parse login command") - assert.Equal(username, loginConfig.User, "Failed to parse login command") - assert.Equal(password, loginConfig.Password, "Failed to parse login command") + assert.NoError(cmd.Parse(args)) + assert.Equal(username, loginConfig.User) + assert.Equal(password, loginConfig.Password) } func TestServiceCommandWithValues(t *testing.T) { assert := assert.New(t) - serviceConfig := serviceConfig{Login: &loginConfig{}} + serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}} cmd := New("Service") - assert.NoError(cmd.Init(&serviceConfig), "Can't init service command") + assert.NoError(cmd.Init(&serviceConfig)) serviceHost := "service-hostname" servicePort := 8080 @@ -159,155 +98,112 @@ func TestServiceCommandWithValues(t *testing.T) { serviceArgs := []string{"--hostname", serviceHost, "--port", strconv.Itoa(servicePort), "log", "-path", serviceLogPath, "-level", serviceLogLevel} - assert.NoError(cmd.Parse(serviceArgs), "Can't parse service command") - assert.Equal(serviceHost, serviceConfig.Host, - "Service hostname is not equal") - assert.Equal(servicePort, serviceConfig.Port, - "Service port is not equal") - assert.Equal(serviceLogPath, serviceConfig.Log.Path, - "Service log path is not equal") - assert.Equal(serviceLogLevel, serviceConfig.Log.Level, - "Service log level is not equal") + assert.NoError(cmd.Parse(serviceArgs)) + assert.Equal(serviceHost, serviceConfig.Host) + assert.Equal(servicePort, serviceConfig.Port) + assert.Equal(serviceLogPath, serviceConfig.Log.Path) + assert.Equal(serviceLogLevel, serviceConfig.Log.Level) dbCmdArgs := []string{"database", "-dbHost", dbHost, "-dbPort", strconv.Itoa(dbPort), "-dbUser", dbUser, "-dbPassword", dbPassword} - assert.NoError(cmd.Parse(dbCmdArgs), "Can't parse service database command") - assert.Equal(dbHost, serviceConfig.DBConfig.Host, - "Database hostname is not equal") - assert.Equal(dbPort, serviceConfig.DBConfig.Port, - "Database port is not equal") - assert.Equal(dbUser, serviceConfig.DBConfig.User, - "Database username is not equal") - assert.Equal(dbPassword, serviceConfig.DBConfig.Password, - "Database password is not equal") + assert.NoError(cmd.Parse(dbCmdArgs)) + assert.Equal(dbHost, serviceConfig.DBConfig.Host) + assert.Equal(dbPort, serviceConfig.DBConfig.Port) + assert.Equal(dbUser, serviceConfig.DBConfig.User) + assert.Equal(dbPassword, serviceConfig.DBConfig.Password) loginCmdArgs := []string{"login", "--user", loginUser, "-password", loginPassword} - assert.NoError(cmd.Parse(loginCmdArgs), "Can't parse service login command") - assert.Equal(loginUser, serviceConfig.Login.User, - "Login username is not equal") - assert.Equal(loginPassword, serviceConfig.Login.Password, - "Login password is not equal") + assert.NoError(cmd.Parse(loginCmdArgs)) + assert.Equal(loginUser, serviceConfig.Login.User) + assert.Equal(loginPassword, serviceConfig.Login.Password) dbLogCmdArgs := []string{"database", "log", "-path", dbLogPath, "-level", dbLogLevel} - assert.NoError(cmd.Parse(dbLogCmdArgs), "Can't parse database log command") - assert.Equal(dbLogPath, serviceConfig.DBConfig.Log.Path, - "Database log path is not equal") - assert.Equal(dbLogLevel, serviceConfig.DBConfig.Log.Level, - "Database log level is not equal") + assert.NoError(cmd.Parse(dbLogCmdArgs)) + assert.Equal(dbLogPath, serviceConfig.DBConfig.Log.Path) + assert.Equal(dbLogLevel, serviceConfig.DBConfig.Log.Level) } func TestVariousTypeCommand(t *testing.T) { assert := assert.New(t) - typesConfig := typesConfig{} - cmd := NewWith("Types", flag.ContinueOnError, nil) + typesConfig := test.TypesConfig{} + cmd := NewWith("Types", flag.ContinueOnError, func(cmd *Command) func() { + return func() { + } + }) assert.NoError(cmd.Init(&typesConfig)) // bool value - assert.NoError(cmd.Parse([]string{"-bool=true"}), - "Can't parse bool value command") - assert.Equal(true, typesConfig.BoolValue, "Bool value is not true") - assert.NoError(cmd.Parse([]string{"-bool"}), - "Can't parse bool value command") - assert.Equal(true, typesConfig.BoolValue, "Bool value is not false") - assert.Error(cmd.Parse([]string{"-bool=xxx"}), - "Parsing string as bool should have an error") + assert.NoError(cmd.Parse([]string{"-bool=true"})) + assert.Equal(true, typesConfig.BoolValue) + assert.NoError(cmd.Parse([]string{"-bool"})) + assert.Equal(true, typesConfig.BoolValue) + assert.Error(cmd.Parse([]string{"-bool=xxx"})) // string value - assert.NoError(cmd.Parse([]string{"-str=xxx"}), - "Can't parse string value command") - assert.Equal("xxx", typesConfig.StrValue, "String value it not equal") - assert.NoError(cmd.Parse([]string{"-str", "yyy"}), - "Can't parse string value command") - assert.Equal("yyy", typesConfig.StrValue, "String value is not equal") + assert.NoError(cmd.Parse([]string{"-str=xxx"})) + assert.Equal("xxx", typesConfig.StrValue) + assert.NoError(cmd.Parse([]string{"-str", "yyy"})) + assert.Equal("yyy", typesConfig.StrValue) // int8 value - assert.NoError(cmd.Parse([]string{"-int8=100"}), - "Can't parse int8 value command") - assert.Equal(int8(100), typesConfig.Int8Value, "Int8 value is not equal") - assert.Error(cmd.Parse([]string{"-int8=xxx"}), - "Parsing string as int8 should have an error") + assert.NoError(cmd.Parse([]string{"-int8=100"})) + assert.Equal(int8(100), typesConfig.Int8Value) + assert.Error(cmd.Parse([]string{"-int8=xxx"})) // int16 value - assert.NoError(cmd.Parse([]string{"-int16=200"}), - "Can't parse int16 value command") - assert.Equal(int16(200), typesConfig.Int16Value, "Int16 value is not equal") - assert.Error(cmd.Parse([]string{"-int16=xxx"}), - "Parsing string as int16 should have an error") + assert.NoError(cmd.Parse([]string{"-int16=200"})) + assert.Equal(int16(200), typesConfig.Int16Value) + assert.Error(cmd.Parse([]string{"-int16=xxx"})) // int value - assert.NoError(cmd.Parse([]string{"-int=300"}), - "Can't parse int value command") - assert.Equal(int(300), typesConfig.IntValue, "Int value is not equal") - assert.Error(cmd.Parse([]string{"-int=xxx"}), - "Parsing string as int should have an error") + assert.NoError(cmd.Parse([]string{"-int=300"})) + assert.Equal(int(300), typesConfig.IntValue) + assert.Error(cmd.Parse([]string{"-int=xxx"})) // int32 value - assert.NoError(cmd.Parse([]string{"-int32=400"}), - "Can't parse int32 value command") - assert.Equal(int32(400), typesConfig.Int32Value, "Int32 value is not equal") - assert.Error(cmd.Parse([]string{"-int32=xxx"}), - "Parsing string as int32 should have an error") + assert.NoError(cmd.Parse([]string{"-int32=400"})) + assert.Equal(int32(400), typesConfig.Int32Value) + assert.Error(cmd.Parse([]string{"-int32=xxx"})) // int64 value - assert.NoError(cmd.Parse([]string{"-int64=500"}), - "Can't parse int64 value command") - assert.Equal(int64(500), typesConfig.Int64Value, "Int64 value is not equal") - assert.Error(cmd.Parse([]string{"-int64=xxx"}), - "Parsing string as int64 should have an error") + assert.NoError(cmd.Parse([]string{"-int64=500"})) + assert.Equal(int64(500), typesConfig.Int64Value) + assert.Error(cmd.Parse([]string{"-int64=xxx"})) // uint8 value - assert.NoError(cmd.Parse([]string{"-uint8=10"}), - "Can't parse uint8 value command") - assert.Equal(uint8(10), typesConfig.Uint8Value, "Uint8 value is not equal") - assert.Error(cmd.Parse([]string{"-uint8=-10"}), - "Parsing string as uint8 should have an error") + assert.NoError(cmd.Parse([]string{"-uint8=10"})) + assert.Equal(uint8(10), typesConfig.Uint8Value) + assert.Error(cmd.Parse([]string{"-uint8=-10"})) // uint16 value - assert.NoError(cmd.Parse([]string{"-uint16=1000"}), - "Can't parse uint16 value command") - assert.Equal(uint16(1000), typesConfig.Uint16Value, - "Uint16 value is not equal") - assert.Error(cmd.Parse([]string{"-uint16=xxx"}), - "Parsing string as uint16 should have an error") + assert.NoError(cmd.Parse([]string{"-uint16=1000"})) + assert.Equal(uint16(1000), typesConfig.Uint16Value) + assert.Error(cmd.Parse([]string{"-uint16=xxx"})) // uint value - assert.NoError(cmd.Parse([]string{"-uint=2000"}), - "Can't parse uint value command") - assert.Equal(uint(2000), typesConfig.UintValue, "Uint value is not equal") - assert.Error(cmd.Parse([]string{"-uint=xxx"}), - "Parsing string as uint should have an error") + assert.NoError(cmd.Parse([]string{"-uint=2000"})) + assert.Equal(uint(2000), typesConfig.UintValue) + assert.Error(cmd.Parse([]string{"-uint=xxx"})) // uint32 value - assert.NoError(cmd.Parse([]string{"-uint32=3000"}), - "Can't parse uint32 value command") - assert.Equal(uint32(3000), typesConfig.Uint32Value, - "Uint32 value is not equal") - assert.Error(cmd.Parse([]string{"-uint32=xxx"}), - "Parsing string as uint32 should have an error") + assert.NoError(cmd.Parse([]string{"-uint32=3000"})) + assert.Equal(uint32(3000), typesConfig.Uint32Value) + assert.Error(cmd.Parse([]string{"-uint32=xxx"})) // uint64 value - assert.NoError(cmd.Parse([]string{"-uint64=4000"}), - "Can't parse uint64 value command") - assert.Equal(uint64(4000), typesConfig.Uint64Value, - "Uint64 value is not equal") - assert.Error(cmd.Parse([]string{"-uint64=xxx"}), - "Parsing string as uint64 should have an error") + assert.NoError(cmd.Parse([]string{"-uint64=4000"})) + assert.Equal(uint64(4000), typesConfig.Uint64Value) + assert.Error(cmd.Parse([]string{"-uint64=xxx"})) // float32 value - assert.NoError(cmd.Parse([]string{"-float32=1.234"}), - "Can't parse float32 value command") - assert.Equal(float32(1.234), typesConfig.Float32Value, - "Float32 value is not equal") - assert.Error(cmd.Parse([]string{"-float32=xxx"}), - "Parsing string as float32 should have an error") + assert.NoError(cmd.Parse([]string{"-float32=1.234"})) + assert.Equal(float32(1.234), typesConfig.Float32Value) + assert.Error(cmd.Parse([]string{"-float32=xxx"})) // float64 value - assert.NoError(cmd.Parse([]string{"-float64=2.345"}), - "Can't parse float64 value command") - assert.Equal(float64(2.345), typesConfig.Float64Value, - "Float64 value is not equal") - assert.Error(cmd.Parse([]string{"-float64=xxx"}), - "Parsing string as float64 should have an error") + assert.NoError(cmd.Parse([]string{"-float64=2.345"})) + assert.Equal(float64(2.345), typesConfig.Float64Value) + assert.Error(cmd.Parse([]string{"-float64=xxx"})) } diff --git a/config.go b/config.go index 0b6455d..d58af1a 100644 --- a/config.go +++ b/config.go @@ -2,46 +2,220 @@ package config import ( "encoding/json" - "errors" + "flag" + "fmt" "io/ioutil" + "os" + "path/filepath" "reflect" + + "github.com/eschao/config/cli" + "github.com/eschao/config/env" + "github.com/eschao/config/utils" + "gopkg.in/yaml.v2" ) -type Field struct { - JsonName string - YamlName string - PropName string - EnvName string - CliName string - Value reflect.Value - DefaultValue string - Separator string -} +const ( + DefaultJSONConfig = "config.json" + DefaultYamlConfig = "config.yaml" + DefaultPropConfig = "config.properties" +) -type Config struct { - Fields []Field -} +const ( + JSONConfigType = "json" + YamlConfigType = "yaml" + PropConfigType = "properties" +) -func (this *Config) Init() *Config { - if this.Fields == nil { - this.Fields = []Field{} +func ParseDefault(i interface{}) error { + ptrRef := reflect.ValueOf(i) + + if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr { + return fmt.Errorf("Expect a structure pointer type instead of %s", + ptrRef.Kind().String()) } - return this + valueOfStruct := ptrRef.Elem() + if valueOfStruct.Kind() != reflect.Struct { + return fmt.Errorf("Expect a structure pointer type instead of %s", + valueOfStruct.Kind().String()) + } + + return parseValue(valueOfStruct) } -func (this *Config) ParseJSON(jsonFile string, data interface{}) error { - raw, err := ioutil.ReadFile(jsonFile) +func parseValue(v reflect.Value) error { + typeOfStruct := v.Type() + var err error + for i := 0; i < v.NumField() && err == nil; i++ { + valueOfField := v.Field(i) + kindOfField := valueOfField.Kind() + structOfField := typeOfStruct.Field(i) + + if kindOfField == reflect.Ptr { + if !valueOfField.IsNil() && valueOfField.CanSet() { + err = ParseDefault(valueOfField.Interface()) + } else { + continue + } + } else if kindOfField == reflect.Struct { + err = parseValue(valueOfField) + } + + defValue, ok := structOfField.Tag.Lookup("default") + if !ok { + continue + } + + kind := valueOfField.Kind() + switch kind { + case reflect.Bool: + err = utils.SetValueWithBool(valueOfField, defValue) + case reflect.String: + valueOfField.SetString(defValue) + case reflect.Int8: + err = utils.SetValueWithIntX(valueOfField, defValue, 8) + case reflect.Int16: + err = utils.SetValueWithIntX(valueOfField, defValue, 16) + case reflect.Int, reflect.Int32: + err = utils.SetValueWithIntX(valueOfField, defValue, 32) + case reflect.Int64: + err = utils.SetValueWithIntX(valueOfField, defValue, 64) + case reflect.Uint8: + err = utils.SetValueWithUintX(valueOfField, defValue, 8) + case reflect.Uint16: + err = utils.SetValueWithUintX(valueOfField, defValue, 16) + case reflect.Uint, reflect.Uint32: + err = utils.SetValueWithUintX(valueOfField, defValue, 32) + case reflect.Uint64: + err = utils.SetValueWithUintX(valueOfField, defValue, 64) + case reflect.Float32: + err = utils.SetValueWithFloatX(valueOfField, defValue, 32) + case reflect.Float64: + err = utils.SetValueWithFloatX(valueOfField, defValue, 64) + case reflect.Slice: + sp, ok := structOfField.Tag.Lookup("separator") + if !ok { + sp = ":" + } + err = utils.SetValueWithSlice(valueOfField, defValue, sp) + + default: + return fmt.Errorf("Can't support type: %s", kind.String()) + } + } + + return err +} + +func ParseEnv(i interface{}) error { + return env.ParseWith(i, "") +} + +func ParseCli(i interface{}) error { + cli := cli.New(os.Args[0]) + if err := cli.Init(i); err != nil { + return err + } + if err := cli.Parse(os.Args[1:]); err != nil { + return err + } + return nil +} + +func ParseConfig(i interface{}, configFlag string) error { + configFile := flag.String(configFlag, "", "Specifiy configuration file") + return ParseConfigFile(i, *configFile) +} + +func ParseConfigFile(i interface{}, configFile string) error { + var err error + if configFile == "" { + configFile, err = getDefaultConfigFile() + if err != nil { + return err + } + } + + configType, err := getConfigFileType(configFile) if err != nil { - return errors.New("Can't open json file. Err: " + err.Error()) + return err } - err = json.Unmarshal(raw, data) - if err != nil { - return errors.New("Failed unmarshal json. Err: " + err.Error()) + switch configType { + case JSONConfigType: + return parseJSON(i, configFile) + case YamlConfigType: + return parseYaml(i, configFile) + case PropConfigType: + return parseProp(i, configFile) + default: + return fmt.Errorf("Can't support config file: %s", configFile) } - //fmt.Printf("Data: %v", *data.(*interface{})) - return nil } + +func parseJSON(i interface{}, jsonFile string) error { + raw, err := ioutil.ReadFile(jsonFile) + if err != nil { + return fmt.Errorf("Can't open json config file. %s", err.Error()) + } + + return json.Unmarshal(raw, i) +} + +func parseYaml(i interface{}, yamlFile string) error { + raw, err := ioutil.ReadFile(yamlFile) + if err != nil { + return fmt.Errorf("Can't open yaml config file. %s", err.Error()) + } + + return yaml.Unmarshal(raw, i) +} + +func parseProp(i interface{}, propFile string) error { + return fmt.Errorf("Properties config has not implemented!") +} + +func getDefaultConfigFile() (string, error) { + exe, err := os.Executable() + if err != nil { + return "", fmt.Errorf("Can't find default config file. %s", err.Error()) + } + + path := filepath.Dir(exe) + string(filepath.Separator) + + // check json config + jsonConfig := path + DefaultJSONConfig + if _, err := os.Stat(jsonConfig); err == nil { + return jsonConfig, nil + } + + // check yaml config + yamlConfig := path + DefaultYamlConfig + if _, err := os.Stat(yamlConfig); err == nil { + return yamlConfig, nil + } + + // check prop config + propConfig := path + DefaultPropConfig + if _, err := os.Stat(propConfig); err == nil { + return propConfig, nil + } + + return "", fmt.Errorf("No default config file found in path: %s", path) +} + +func getConfigFileType(configFile string) (string, error) { + ext := filepath.Ext(configFile) + if ext == ".json" { + return JSONConfigType, nil + } else if ext == ".yaml" || ext == ".yml" { + return YamlConfigType, nil + } else if ext == ".properties" || ext == ".prop" { + return PropConfigType, nil + } + + return "", fmt.Errorf("Can't support file type: %s", configFile) +} diff --git a/config_test.go b/config_test.go index 63f3de5..9e3df9d 100644 --- a/config_test.go +++ b/config_test.go @@ -1,27 +1,101 @@ package config import ( + "os" + "path/filepath" + "runtime" + "strconv" "testing" + + "github.com/eschao/config/test" + "github.com/stretchr/testify/assert" ) -type TestConfig struct { - Name string `json:"name" default:"test-name"` - Path string `json:"path" default:"./"` +const ( + LOGIN_USER = "test-login-user" + LOGIN_PASSWORD = "test-login-passwd" + SERVICE_HOST = "test-service-host" + SERVICE_PORT = 8080 + SERVICE_LOG_PATH = "/var/log/service" + SERVICE_LOG_LEVEL = "debug" + DB_HOST = "test-db-host" + DB_PORT = 9090 + DB_USER = "test-db-user" + DB_PASSWORD = "test-db-password" + DB_LOG_PATH = "/var/log/db" + DB_LOG_LEVEL = "error" +) + +func TestDefaultValueConfig(t *testing.T) { + conf := test.DefValueConfig{} + assert := assert.New(t) + assert.NoError(ParseDefault(&conf)) + + assert.Equal(true, conf.BoolValue) + assert.Equal(123, conf.IntValue) + assert.Equal(float64(123.4567), conf.Float64Value) + assert.Equal("default-string", conf.StrValue) + assert.Equal(3, len(conf.SliceValue)) + assert.Equal("xx", conf.SliceValue[0]) + assert.Equal("yy", conf.SliceValue[1]) + assert.Equal("zz", conf.SliceValue[2]) + assert.Equal("", conf.NoDefValue) } -func TestJSONConfig(t *testing.T) { - config := Config{} - myConfig := TestConfig{} - err := config.Init().ParseJSON("test.json", &myConfig) - if err != nil { - t.Errorf("JSON config test failed. ", err.Error()) - } +func TestEnvConfig(t *testing.T) { + dbLogPrefix := "LOG_" - if myConfig.Name != "jsonconfig" { - t.Errorf("Name json value: %s != jsonconfig", myConfig.Name) - } + os.Setenv("HOST", DB_HOST) + os.Setenv("PORT", strconv.Itoa(DB_PORT)) + os.Setenv("USER", DB_USER) + os.Setenv("PASSWORD", DB_PASSWORD) + os.Setenv(dbLogPrefix+"PATH", DB_LOG_PATH) + os.Setenv(dbLogPrefix+"LEVEL", DB_LOG_LEVEL) - if myConfig.Path != "/var" { - t.Errorf("Path json value: %s != /var", myConfig.Path) - } + defer os.Unsetenv("HOST") + defer os.Unsetenv("PORT") + defer os.Unsetenv("USER") + defer os.Unsetenv("PASSWORD") + defer os.Unsetenv(dbLogPrefix + "PATH") + defer os.Unsetenv(dbLogPrefix + "LEVEL") + + conf := test.DBConfig{} + assert := assert.New(t) + assert.NoError(ParseEnv(&conf)) + assert.Equal(DB_HOST, conf.Host) + assert.Equal(DB_PORT, conf.Port) + assert.Equal(DB_USER, conf.User) + assert.Equal(DB_PASSWORD, conf.Password) + assert.Equal(DB_LOG_PATH, conf.Log.Path) + assert.Equal(DB_LOG_LEVEL, conf.Log.Level) +} + +func TestJSONConfigFile(t *testing.T) { + _, curTestFile, _, _ := runtime.Caller(0) + path := filepath.Dir(curTestFile) + + conf := test.DBConfig{} + assert := assert.New(t) + assert.NoError(ParseConfigFile(&conf, path+"/test/config.json")) + assert.Equal(DB_HOST, conf.Host) + assert.Equal(DB_PORT, conf.Port) + assert.Equal(DB_USER, conf.User) + assert.Equal(DB_PASSWORD, conf.Password) + assert.Equal(DB_LOG_PATH, conf.Log.Path) + assert.Equal(DB_LOG_LEVEL, conf.Log.Level) +} + +func TestYamlConfigFile(t *testing.T) { + _, curTestFile, _, _ := runtime.Caller(0) + path := filepath.Dir(curTestFile) + + conf := test.DBConfig{} + assert := assert.New(t) + assert.NoError(ParseConfigFile(&conf, path+"/test/config.yaml")) + assert.Equal(DB_HOST, conf.Host) + assert.Equal(DB_PORT, conf.Port) + assert.Equal(DB_USER, conf.User) + assert.Equal(DB_PASSWORD, conf.Password) + assert.Equal(DB_LOG_PATH, conf.Log.Path) + assert.Equal(DB_LOG_LEVEL, conf.Log.Level) } diff --git a/env/env.go b/env/env.go index 80a6537..cc5bf0f 100644 --- a/env/env.go +++ b/env/env.go @@ -4,11 +4,15 @@ import ( "fmt" "os" "reflect" - "strconv" - "strings" + + "github.com/eschao/config/utils" ) -func Unmarshal(i interface{}) error { +func Parse(i interface{}) error { + return ParseWith(i, "") +} + +func ParseWith(i interface{}, prefix string) error { ptrRef := reflect.ValueOf(i) if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr { @@ -22,37 +26,37 @@ func Unmarshal(i interface{}) error { valueOfStruct.Kind().String()) } - return unmarshal(valueOfStruct) + return parseValue(valueOfStruct, prefix) } -func unmarshal(v reflect.Value) error { +func parseValue(v reflect.Value, prefix string) error { typeOfStruct := v.Type() - for i := 0; i < v.NumField(); i++ { + var err error + for i := 0; i < v.NumField() && err == nil; i++ { valueOfField := v.Field(i) kindOfField := valueOfField.Kind() structOfField := typeOfStruct.Field(i) - //fmt.Printf("Name: %s, Type: %s\n", structOfField.Name, kindOfField.String()) // recursively unmarshal if value is ptr type if kindOfField == reflect.Ptr { if !valueOfField.IsNil() && valueOfField.CanSet() { - Unmarshal(valueOfField.Interface()) + err = ParseWith(valueOfField.Interface(), + prefix+structOfField.Tag.Get("env")) } else { continue } } else if kindOfField == reflect.Struct { - unmarshal(valueOfField) + err = parseValue(valueOfField, prefix+structOfField.Tag.Get("env")) } - if err := setFieldValue(valueOfField, structOfField); err != nil { - return err - } + err = setFieldValue(valueOfField, structOfField, prefix) } - return nil + return err } func getEnvValue(envName string, f reflect.StructField) (string, bool) { + //fmt.Printf("Lookup ENV: %s\n", envName) envValue, ok := os.LookupEnv(envName) if !ok { envValue, ok = f.Tag.Lookup("default") @@ -61,13 +65,13 @@ func getEnvValue(envName string, f reflect.StructField) (string, bool) { return envValue, ok } -func setFieldValue(v reflect.Value, f reflect.StructField) error { +func setFieldValue(v reflect.Value, f reflect.StructField, prefix string) error { envName := f.Tag.Get("env") if envName == "" { return nil } - envValue, ok := getEnvValue(envName, f) + envValue, ok := getEnvValue(prefix+envName, f) if !ok { return nil } @@ -76,168 +80,47 @@ func setFieldValue(v reflect.Value, f reflect.StructField) error { return fmt.Errorf("%s: can't be set", f.Name) } + var err error kind := v.Kind() - name := f.Name switch kind { case reflect.Bool: - return setFieldValueWithBool(name, v, envValue) - + err = utils.SetValueWithBool(v, envValue) case reflect.String: v.SetString(envValue) - return nil - case reflect.Int8: - return setFieldValueWithIntX(name, v, envValue, 8) - + err = utils.SetValueWithIntX(v, envValue, 8) case reflect.Int16: - return setFieldValueWithIntX(name, v, envValue, 16) - + err = utils.SetValueWithIntX(v, envValue, 16) case reflect.Int, reflect.Int32: - return setFieldValueWithIntX(name, v, envValue, 32) - + err = utils.SetValueWithIntX(v, envValue, 32) case reflect.Int64: - return setFieldValueWithIntX(name, v, envValue, 64) - + err = utils.SetValueWithIntX(v, envValue, 64) case reflect.Uint8: - return setFieldValueWithUintX(name, v, envValue, 8) - + err = utils.SetValueWithUintX(v, envValue, 8) case reflect.Uint16: - return setFieldValueWithUintX(name, v, envValue, 16) - + err = utils.SetValueWithUintX(v, envValue, 16) case reflect.Uint, reflect.Uint32: - return setFieldValueWithUintX(name, v, envValue, 32) - + err = utils.SetValueWithUintX(v, envValue, 32) case reflect.Uint64: - return setFieldValueWithUintX(name, v, envValue, 64) - + err = utils.SetValueWithUintX(v, envValue, 64) case reflect.Float32: - return setFieldValueWithFloatX(name, v, envValue, 32) - + err = utils.SetValueWithFloatX(v, envValue, 32) case reflect.Float64: - return setFieldValueWithFloatX(name, v, envValue, 64) + err = utils.SetValueWithFloatX(v, envValue, 64) case reflect.Slice: sp, ok := f.Tag.Lookup("separator") if !ok { sp = ":" } - return setFieldValueWithSlice(name, v, envValue, sp) + err = utils.SetValueWithSlice(v, envValue, sp) default: return fmt.Errorf("Can't support type: %s", kind.String()) } -} -func setFieldValueWithBool(name string, v reflect.Value, - envValue string) error { - value, err := strconv.ParseBool(envValue) if err != nil { - return fmt.Errorf("%s: can't convert %s to bool value. %s", name, envValue, - err.Error()) + return fmt.Errorf("%s: %s", f.Name, err.Error()) } - - v.SetBool(value) - return nil -} - -func setFieldValueWithFloatX(name string, v reflect.Value, envValue string, - bitSize int) error { - value, err := strconv.ParseFloat(envValue, bitSize) - if err != nil { - return fmt.Errorf("%s: can't convert %s to float%d value. %s", name, - envValue, bitSize, err.Error()) - } - - v.SetFloat(value) - return nil -} - -func setFieldValueWithIntX(name string, v reflect.Value, envValue string, - bitSize int) error { - value, err := strconv.ParseInt(envValue, 10, bitSize) - if err != nil { - return fmt.Errorf("%s: can't convert %s to int%d value. %s", name, - envValue, bitSize, err.Error()) - } - - v.SetInt(value) - return nil -} - -func setFieldValueWithUintX(name string, v reflect.Value, envValue string, - bitSize int) error { - value, err := strconv.ParseUint(envValue, 10, bitSize) - if err != nil { - return fmt.Errorf("%s: can't convert %s to uint%d value. %s", name, - envValue, bitSize, err.Error()) - } - - v.SetUint(value) - return nil -} - -func setFieldValueWithSlice(name string, v reflect.Value, envValue string, - separator string) error { - data := strings.Split(envValue, separator) - size := len(data) - if size > 0 { - slice := reflect.MakeSlice(v.Type(), size, size) - for i := 0; i < size; i++ { - ele := slice.Index(i) - kind := ele.Kind() - switch kind { - case reflect.Bool: - if err := setFieldValueWithBool(name, ele, data[i]); err != nil { - return err - } - case reflect.String: - ele.SetString(data[i]) - case reflect.Uint8: - if err := setFieldValueWithUintX(name, ele, data[i], 8); err != nil { - return err - } - case reflect.Uint16: - if err := setFieldValueWithUintX(name, ele, data[i], 16); err != nil { - return err - } - case reflect.Uint, reflect.Uint32: - if err := setFieldValueWithUintX(name, ele, data[i], 32); err != nil { - return err - } - case reflect.Uint64: - if err := setFieldValueWithUintX(name, ele, data[i], 64); err != nil { - return err - } - case reflect.Int8: - if err := setFieldValueWithIntX(name, ele, data[i], 8); err != nil { - return err - } - case reflect.Int16: - if err := setFieldValueWithIntX(name, ele, data[i], 16); err != nil { - return err - } - case reflect.Int, reflect.Int32: - if err := setFieldValueWithIntX(name, ele, data[i], 32); err != nil { - return err - } - case reflect.Int64: - if err := setFieldValueWithIntX(name, ele, data[i], 64); err != nil { - return err - } - case reflect.Float32: - if err := setFieldValueWithFloatX(name, ele, data[i], 32); err != nil { - return err - } - case reflect.Float64: - if err := setFieldValueWithFloatX(name, ele, data[i], 64); err != nil { - return err - } - default: - return fmt.Errorf("%s: can't support type: %s", name, kind.String()) - } - } - v.Set(slice) - } - return nil } diff --git a/env/env_test.go b/env/env_test.go index fd6fd9e..634a068 100644 --- a/env/env_test.go +++ b/env/env_test.go @@ -1,194 +1,259 @@ package env import ( - "fmt" "os" "strconv" - "strings" "testing" -) -type EnvConfig1 struct { - Hostname string `env:"CONFIG_TEST_HOSTNAME" default:"localhost"` - Port int `env:"CONFIG_TEST_PORT"` - User string `env:"CONFIG_TEST_USER"` - Password string `env:"CONFIG_TEST_PASSWORD"` - Path1 []string `env:"CONFIG_TEST_PATH1"` - Path2 []string `env:"CONFIG_TEST_PATH2" separator:";"` - Home string -} + "github.com/eschao/config/test" + "github.com/stretchr/testify/assert" +) const ( - TEST_HOSTNAME = "test-hostname" - TEST_PORT = 8080 - TEST_USER = "test-user" - TEST_PASSWORD = "test-password" - TEST_PATH1 = "/usr:/var:/bin" - TEST_PATH2 = "/root;/home;/tmp" + LOGIN_USER = "test-login-user" + LOGIN_PASSWORD = "test-login-passwd" + SERVICE_HOST = "test-service-host" + SERVICE_PORT = 8080 + SERVICE_LOG_PATH = "/var/log/service" + SERVICE_LOG_LEVEL = "debug" + DB_HOST = "test-db-host" + DB_PORT = 9090 + DB_USER = "test-db-user" + DB_PASSWORD = "test-db-password" + DB_LOG_PATH = "/var/log/db" + DB_LOG_LEVEL = "error" ) -func setEnvConfig1() { - os.Setenv("CONFIG_TEST_HOSTNAME", TEST_HOSTNAME) - os.Setenv("CONFIG_TEST_PORT", strconv.Itoa(TEST_PORT)) - os.Setenv("CONFIG_TEST_USER", TEST_USER) - os.Setenv("CONFIG_TEST_PASSWORD", TEST_PASSWORD) - os.Setenv("CONFIG_TEST_PATH1", TEST_PATH1) - os.Setenv("CONFIG_TEST_PATH2", TEST_PATH2) +func TestLoginConfigEnv(t *testing.T) { + os.Setenv("USER", LOGIN_USER) + os.Setenv("PASSWORD", LOGIN_PASSWORD) + defer os.Unsetenv("USER") + defer os.Unsetenv("PASSWORD") + + assert := assert.New(t) + loginConfig := test.LoginConfig{} + assert.NoError(Parse(&loginConfig)) + + assert.Equal(LOGIN_USER, loginConfig.User) + assert.Equal(LOGIN_PASSWORD, loginConfig.Password) } -func unsetEnvConfig1() { - os.Unsetenv("CONFIG_TEST_HOSTNAME") - os.Unsetenv("CONFIG_TEST_PORT") - os.Unsetenv("CONFIG_TEST_USER") - os.Unsetenv("CONFIG_TEST_PASSWORD") +func TestLoginConfigEnvWithPrefix(t *testing.T) { + os.Setenv("DB_USER", LOGIN_USER) + os.Setenv("DB_PASSWORD", LOGIN_PASSWORD) + defer os.Unsetenv("DB_USER") + defer os.Unsetenv("DB_PASSWORD") + + assert := assert.New(t) + loginConfig := test.LoginConfig{} + assert.NoError(ParseWith(&loginConfig, "DB_")) + assert.Equal(LOGIN_USER, loginConfig.User) + assert.Equal(LOGIN_PASSWORD, loginConfig.Password) } -func assertEqual(expected, actual []string) (bool, error) { - if len(expected) != len(actual) { - return false, fmt.Errorf("Expected length of array is %d, but actual is %d", - len(expected), len(actual)) - } +func TestServiceConfigEnv(t *testing.T) { + servicePrefix := "CONFIG_TEST_SERVICE_" + serviceLogPrefix := servicePrefix + "LOG_" + dbPrefix := servicePrefix + "DB_" + dbLogPrefix := dbPrefix + "LOG_" - for i := 0; i < len(expected); i++ { - if expected[i] != actual[i] { - return false, fmt.Errorf("Expected array[%d]=%s, but acutal array[%d]=%s", - i, expected[i], i, actual[i]) - } - } + os.Setenv(servicePrefix+"HOST", SERVICE_HOST) + os.Setenv(servicePrefix+"PORT", strconv.Itoa(SERVICE_PORT)) + os.Setenv(serviceLogPrefix+"PATH", SERVICE_LOG_PATH) + os.Setenv(serviceLogPrefix+"LEVEL", SERVICE_LOG_LEVEL) + os.Setenv(dbPrefix+"HOST", DB_HOST) + os.Setenv(dbPrefix+"PORT", strconv.Itoa(DB_PORT)) + os.Setenv(dbPrefix+"USER", DB_USER) + os.Setenv(dbPrefix+"PASSWORD", DB_PASSWORD) + os.Setenv(dbLogPrefix+"PATH", DB_LOG_PATH) + os.Setenv(dbLogPrefix+"LEVEL", DB_LOG_LEVEL) - return true, nil + defer os.Unsetenv(servicePrefix + "HOST") + defer os.Unsetenv(servicePrefix + "PORT") + defer os.Unsetenv(serviceLogPrefix + "PATH") + defer os.Unsetenv(serviceLogPrefix + "LEVEL") + defer os.Unsetenv(dbPrefix + "HOST") + defer os.Unsetenv(dbPrefix + "PORT") + defer os.Unsetenv(dbPrefix + "USER") + defer os.Unsetenv(dbPrefix + "PASSWORD") + defer os.Unsetenv(dbLogPrefix + "PATH") + defer os.Unsetenv(dbLogPrefix + "LEVEL") + + assert := assert.New(t) + serviceConfig := test.ServiceConfig{} + assert.NoError(Parse(&serviceConfig)) + assert.Equal(SERVICE_HOST, serviceConfig.Host) + assert.Equal(SERVICE_PORT, serviceConfig.Port) + assert.Equal(SERVICE_LOG_PATH, serviceConfig.Log.Path) + assert.Equal(SERVICE_LOG_LEVEL, serviceConfig.Log.Level) + assert.Equal(DB_HOST, serviceConfig.DBConfig.Host) + assert.Equal(DB_PORT, serviceConfig.DBConfig.Port) + assert.Equal(DB_USER, serviceConfig.DBConfig.User) + assert.Equal(DB_PASSWORD, serviceConfig.DBConfig.Password) + assert.Equal(DB_LOG_PATH, serviceConfig.DBConfig.Log.Path) + assert.Equal(DB_LOG_LEVEL, serviceConfig.DBConfig.Log.Level) } -func TestEnvConfig1(t *testing.T) { - setEnvConfig1() - defer unsetEnvConfig1() +func TestServiceLoginConfigEnv(t *testing.T) { + serviceLoginPrefix := "CONFIG_TEST_SERVICE_LOGIN_" + os.Setenv(serviceLoginPrefix+"USER", LOGIN_USER) + os.Setenv(serviceLoginPrefix+"PASSWORD", LOGIN_PASSWORD) + defer os.Unsetenv(serviceLoginPrefix + "USER") + defer os.Unsetenv(serviceLoginPrefix + "PASSWORD") - conf := EnvConfig1{} - err := Unmarshal(&conf) - if err != nil { - t.Errorf("Can't unmarshal config1 from environemnt variables. %s", - err.Error()) - return - } - - if conf.Hostname != TEST_HOSTNAME { - t.Errorf("Expect Hostname: %s, but got: %s", TEST_HOSTNAME, conf.Hostname) - } - - if conf.Port != TEST_PORT { - t.Errorf("Expect Port: %d, but got: %d", TEST_PORT, conf.Port) - } - - if conf.User != TEST_USER { - t.Errorf("Expect User: %s, but got: %s", TEST_USER, conf.User) - } - - if conf.Password != TEST_PASSWORD { - t.Errorf("Expect Password: %s, but got: %s", TEST_PASSWORD, conf.Password) - } - - if conf.Home != "" { - t.Errorf("Expect Home is empty, but got: %s", conf.Home) - } - - expectedPath1 := strings.Split(TEST_PATH1, ":") - if ok, err := assertEqual(expectedPath1, conf.Path1); !ok { - t.Error(err.Error()) - } - - expectedPath2 := strings.Split(TEST_PATH2, ";") - if ok, err := assertEqual(expectedPath2, conf.Path2); !ok { - t.Error(err.Error()) - } + assert := assert.New(t) + serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}} + assert.NoError(Parse(&serviceConfig)) + assert.Equal(LOGIN_USER, serviceConfig.Login.User) + assert.Equal(LOGIN_PASSWORD, serviceConfig.Login.Password) } -func TestEnvConfig1WithDefaultValue(t *testing.T) { - os.Setenv("CONFIG_TEST_PORT", strconv.Itoa(TEST_PORT)) - os.Setenv("CONFIG_TEST_USER", TEST_USER) - os.Setenv("CONFIG_TEST_PASSWORD", TEST_PASSWORD) - defer unsetEnvConfig1() +func TestTypesConfigEnv(t *testing.T) { + typesPrefix := "CONFIG_TEST_" + os.Setenv(typesPrefix+"BOOL", "true") + os.Setenv(typesPrefix+"STR", "test-string") + os.Setenv(typesPrefix+"INT8", "100") + os.Setenv(typesPrefix+"INT16", "1000") + os.Setenv(typesPrefix+"INT", "10000") + os.Setenv(typesPrefix+"INT32", "100000") + os.Setenv(typesPrefix+"INT64", "1000000") + os.Setenv(typesPrefix+"UINT8", "200") + os.Setenv(typesPrefix+"UINT16", "2000") + os.Setenv(typesPrefix+"UINT", "20000") + os.Setenv(typesPrefix+"UINT32", "200000") + os.Setenv(typesPrefix+"UINT64", "2000000") + os.Setenv(typesPrefix+"FLOAT32", "1.234") + os.Setenv(typesPrefix+"FLOAT64", "2222.33333") - conf := EnvConfig1{} - err := Unmarshal(&conf) - if err != nil { - t.Errorf("Can't unmarshal config1 from environemnt variables. %s", - err.Error()) - return - } + defer os.Unsetenv(typesPrefix + "BOOL") + defer os.Unsetenv(typesPrefix + "STR") + defer os.Unsetenv(typesPrefix + "INT8") + defer os.Unsetenv(typesPrefix + "INT16") + defer os.Unsetenv(typesPrefix + "INT") + defer os.Unsetenv(typesPrefix + "INT32") + defer os.Unsetenv(typesPrefix + "INT64") + defer os.Unsetenv(typesPrefix + "UINT8") + defer os.Unsetenv(typesPrefix + "UINT16") + defer os.Unsetenv(typesPrefix + "UINT") + defer os.Unsetenv(typesPrefix + "UINT32") + defer os.Unsetenv(typesPrefix + "UINT64") + defer os.Unsetenv(typesPrefix + "FLOAT32") + defer os.Unsetenv(typesPrefix + "FLOAT64") - if conf.Hostname != "localhost" { - t.Errorf("Expect Hostname: localhost, bug got: %s", conf.Hostname) - } - - if conf.Port != TEST_PORT { - t.Errorf("Expect Port: %d, but got: %d", TEST_PORT, conf.Port) - } - - if conf.User != TEST_USER { - t.Errorf("Expect User: %s, but got: %s", TEST_USER, conf.User) - } - - if conf.Password != TEST_PASSWORD { - t.Errorf("Expect Password: %s, but got: %s", TEST_PASSWORD, conf.Password) - } - - if conf.Home != "" { - t.Errorf("Expect Home is empty, but got: %s", conf.Home) - } + assert := assert.New(t) + typesConfig := test.TypesConfig{} + assert.NoError(Parse(&typesConfig)) + assert.Equal(true, typesConfig.BoolValue) + assert.Equal("test-string", typesConfig.StrValue) + assert.Equal(int8(100), typesConfig.Int8Value) + assert.Equal(int16(1000), typesConfig.Int16Value) + assert.Equal(10000, typesConfig.IntValue) + assert.Equal(int32(100000), typesConfig.Int32Value) + assert.Equal(int64(1000000), typesConfig.Int64Value) + assert.Equal(uint8(200), typesConfig.Uint8Value) + assert.Equal(uint16(2000), typesConfig.Uint16Value) + assert.Equal(uint(20000), typesConfig.UintValue) + assert.Equal(uint32(200000), typesConfig.Uint32Value) + assert.Equal(uint64(2000000), typesConfig.Uint64Value) + assert.Equal(float32(1.234), typesConfig.Float32Value) + assert.Equal(float64(2222.33333), typesConfig.Float64Value) } -type EnvConfig2 struct { - Config1 EnvConfig1 - Server string `env:"CONFIG_ENV_TEST_SERVER"` +func TestTypesConfigWithErrorEnv(t *testing.T) { + assert := assert.New(t) + typesConfig := test.TypesConfig{} + typesPrefix := "CONFIG_TEST_" + os.Setenv(typesPrefix+"BOOL", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "BOOL") + + os.Setenv(typesPrefix+"INT8", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "INT8") + + os.Setenv(typesPrefix+"INT16", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "INT16") + + os.Setenv(typesPrefix+"INT", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "INT") + + os.Setenv(typesPrefix+"INT32", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "INT32") + + os.Setenv(typesPrefix+"INT64", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "INT64") + + os.Setenv(typesPrefix+"UINT8", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "UINT8") + + os.Setenv(typesPrefix+"UINT16", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "UINT16") + + os.Setenv(typesPrefix+"UINT", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "UINT") + + os.Setenv(typesPrefix+"UINT32", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "UINT32") + + os.Setenv(typesPrefix+"UINT64", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "UINT64") + + os.Setenv(typesPrefix+"FLOAT32", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "FLOAT32") + + os.Setenv(typesPrefix+"FLOAT64", "xxx") + assert.Error(Parse(&typesConfig)) + os.Unsetenv(typesPrefix + "FLOAT64") + + defer os.Unsetenv(typesPrefix + "BOOL") + defer os.Unsetenv(typesPrefix + "INT8") + defer os.Unsetenv(typesPrefix + "INT16") + defer os.Unsetenv(typesPrefix + "INT") + defer os.Unsetenv(typesPrefix + "INT32") + defer os.Unsetenv(typesPrefix + "INT64") + defer os.Unsetenv(typesPrefix + "UINT8") + defer os.Unsetenv(typesPrefix + "UINT16") + defer os.Unsetenv(typesPrefix + "UINT") + defer os.Unsetenv(typesPrefix + "UINT32") + defer os.Unsetenv(typesPrefix + "UINT64") + defer os.Unsetenv(typesPrefix + "FLOAT32") + defer os.Unsetenv(typesPrefix + "FLOAT64") } -const ( - TEST_SERVER = "test-server" -) +func TestSlicesConfigEnv(t *testing.T) { + prefix := "CONFIG_TEST_SLICES_" + os.Setenv(prefix+"PATHS", "/var:/usr:/home") + os.Setenv(prefix+"DEBUG", "/root;/log;/opt") + os.Setenv(prefix+"VALUES", "1,2,4,5") -func setEnvConfig2() { - setEnvConfig1() - os.Setenv("CONFIG_ENV_TEST_SERVER", TEST_SERVER) -} - -func unsetEnvConfig2() { - unsetEnvConfig1() - os.Unsetenv("CONFIG_ENV_TEST_SERVER") -} - -func TestEnvConfig2(t *testing.T) { - setEnvConfig2() - defer unsetEnvConfig2() - - conf := EnvConfig2{} - err := Unmarshal(&conf) - if err != nil { - t.Errorf("Can't unmarshal config2 from environemnt variables. %s", - err.Error()) - return - } - - if conf.Config1.Hostname != TEST_HOSTNAME { - t.Errorf("Expect Hostname: %s, but got: %s", TEST_HOSTNAME, conf.Config1.Hostname) - } - - if conf.Config1.Port != TEST_PORT { - t.Errorf("Expect Port: %d, but got: %d", TEST_PORT, conf.Config1.Port) - } - - if conf.Config1.User != TEST_USER { - t.Errorf("Expect User: %s, but got: %s", TEST_USER, conf.Config1.User) - } - - if conf.Config1.Password != TEST_PASSWORD { - t.Errorf("Expect Password: %s, but got: %s", TEST_PASSWORD, conf.Config1.Password) - } - - if conf.Config1.Home != "" { - t.Errorf("Expect Home is empty, but got: %s", conf.Config1.Home) - } - - if conf.Server != TEST_SERVER { - t.Errorf("Expect Server: %s, but got: %s", TEST_SERVER, conf.Server) - } + defer os.Unsetenv(prefix + "PATHS") + defer os.Unsetenv(prefix + "DEBUG") + defer os.Unsetenv(prefix + "VALUES") + + assert := assert.New(t) + conf := test.SlicesConfig{} + assert.NoError(Parse(&conf)) + assert.Equal(3, len(conf.Paths)) + assert.Equal("/var", conf.Paths[0]) + assert.Equal("/usr", conf.Paths[1]) + assert.Equal("/home", conf.Paths[2]) + assert.Equal(3, len(conf.Debugs)) + assert.Equal("/root", conf.Debugs[0]) + assert.Equal("/log", conf.Debugs[1]) + assert.Equal("/opt", conf.Debugs[2]) + assert.Equal(4, len(conf.Values)) + assert.Equal(1, conf.Values[0]) + assert.Equal(2, conf.Values[1]) + assert.Equal(4, conf.Values[2]) + assert.Equal(5, conf.Values[3]) } diff --git a/main b/main deleted file mode 100755 index 1f4cd64..0000000 Binary files a/main and /dev/null differ diff --git a/main.go b/main.go deleted file mode 100644 index 100a24a..0000000 --- a/main.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - //"github.com/eschao/Config/cli" -) - -type DBCli struct { - Host string `cli:"hostname database server hostname"` - Port string `cli:"port database server port"` - User string `cli:"user database username"` - Password string `cli:"password database user password"` -} - -func main() { - - countCommand := flag.NewFlagSet("count", flag.ExitOnError) - listCommand := flag.NewFlagSet("list", flag.ExitOnError) - - // Count subcommand flag pointers - // Adding a new choice for --metric of 'substring' and a new --substring flag - countTextPtr := countCommand.String("text", "", "Text to parse. (Required)") - countMetricPtr := countCommand.String("metric", "chars", "Metric {chars|words|lines|substring}. (Required)") - countSubstringPtr := countCommand.String("substring", "", "The substring to be counted. Required for --metric=substring") - countUniquePtr := countCommand.Bool("unique", false, "Measure unique values of a metric.") - - // List subcommand flag pointers - listTextPtr := listCommand.String("text", "", "Text to parse. (Required)") - listMetricPtr := listCommand.String("metric", "chars", "Metric . (Required)") - listUniquePtr := listCommand.Bool("unique", false, "Measure unique values of a metric.") - - // Verify that a subcommand has been provided - // os.Arg[0] is the main command - // os.Arg[1] will be the subcommand - if len(os.Args) < 2 { - //fmt.Println("list or count subcommand is required") - flag.PrintDefaults() - os.Exit(1) - } - - // Switch on the subcommand - // Parse the flags for appropriate FlagSet - // FlagSet.Parse() requires a set of arguments to parse as input - // os.Args[2:] will be all arguments starting after the subcommand at os.Args[1] - switch os.Args[1] { - case "list": - listCommand.Parse(os.Args[2:]) - case "count": - countCommand.Parse(os.Args[2:]) - default: - flag.PrintDefaults() - os.Exit(1) - } - - // Check which subcommand was Parsed using the FlagSet.Parsed() function. Handle each case accordingly. - // FlagSet.Parse() will evaluate to false if no flags were parsed (i.e. the user did not provide any flags) - if listCommand.Parsed() { - // Required Flags - if *listTextPtr == "" { - listCommand.PrintDefaults() - os.Exit(1) - } - //Choice flag - metricChoices := map[string]bool{"chars": true, "words": true, "lines": true} - if _, validChoice := metricChoices[*listMetricPtr]; !validChoice { - listCommand.PrintDefaults() - os.Exit(1) - } - // Print - fmt.Printf("textPtr: %s, metricPtr: %s, uniquePtr: %t\n", *listTextPtr, *listMetricPtr, *listUniquePtr) - } - - if countCommand.Parsed() { - // Required Flags - if *countTextPtr == "" { - countCommand.PrintDefaults() - os.Exit(1) - } - // If the metric flag is substring, the substring flag is required - if *countMetricPtr == "substring" && *countSubstringPtr == "" { - countCommand.PrintDefaults() - os.Exit(1) - } - //If the metric flag is not substring, the substring flag must not be used - if *countMetricPtr != "substring" && *countSubstringPtr != "" { - fmt.Println("--substring may only be used with --metric=substring.") - countCommand.PrintDefaults() - os.Exit(1) - } - //Choice flag - metricChoices := map[string]bool{"chars": true, "words": true, "lines": true, "substring": true} - if _, validChoice := metricChoices[*listMetricPtr]; !validChoice { - countCommand.PrintDefaults() - os.Exit(1) - } - //Print - fmt.Printf("textPtr: %s, metricPtr: %s, substringPtr: %v, uniquePtr: %t\n", *countTextPtr, *countMetricPtr, *countSubstringPtr, *countUniquePtr) - } - /* - config := DBCli{} - root := cli.CliFlag{} - err := cli.Parse(&config, &root) - - if err != nil { - fmt.Printf("Can't parse cli. %s", err.Error()) - } - flag.Parse() - - fmt.Printf("Host=%s\n", config.Host) - */ - -} diff --git a/test.json b/test.json deleted file mode 100644 index fc286de..0000000 --- a/test.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "jsonconfig", - "path": "/var" -} diff --git a/test/config.json b/test/config.json new file mode 100644 index 0000000..12bb410 --- /dev/null +++ b/test/config.json @@ -0,0 +1,10 @@ +{ + "dbHost": "test-db-host", + "dbPort": 9090, + "dbUser": "test-db-user", + "dbPassword": "test-db-password", + "log": { + "path": "/var/log/db", + "level": "error" + } +} diff --git a/test/config.yaml b/test/config.yaml new file mode 100644 index 0000000..ca2d56f --- /dev/null +++ b/test/config.yaml @@ -0,0 +1,8 @@ +dbHost: test-db-host +dbPort: 9090 +dbUser: test-db-user +dbPassword: test-db-password +log: + path: /var/log/db + level: error + diff --git a/test/data.go b/test/data.go new file mode 100644 index 0000000..295e3cc --- /dev/null +++ b/test/data.go @@ -0,0 +1,59 @@ +package test + +type DBConfig struct { + Host string `json:"dbHost" yaml:"dbHost" env:"HOST" cmd:"dbHost database server hostname"` + Port int `json:"dbPort" yaml:"dbPort" env:"PORT" cmd:"dbPort database server port"` + User string `json:"dbUser" yaml:"dbUser" env:"USER" cmd:"dbUser database username"` + Password string `json:"dbPassword" yaml:"dbPassword" env:"PASSWORD" cmd:"dbPassword database user password"` + Log LogConfig `json:"log" yaml:"log" env:"LOG_" cmd:"log database log configuration"` +} + +type LoginConfig struct { + User string `json:"user" yaml:"user" env:"USER" cmd:"user login username"` + Password string `json:"password" yaml:"password" env:"PASSWORD" cmd:"password login password"` +} + +type LogConfig struct { + Path string `json:"path" yaml:"path" env:"PATH" cmd:"path log path"` + Level string `json:"level" yaml:"level" env:"LEVEL" cmd:"level log level {debug|warning|error}"` +} + +type ServiceConfig struct { + Host string `env:"CONFIG_TEST_SERVICE_HOST" cmd:"hostname service hostname"` + Port int `env:"CONFIG_TEST_SERVICE_PORT" cmd:"port service port"` + DBConfig DBConfig `env:"CONFIG_TEST_SERVICE_DB_" cmd:"database database configuration"` + Login *LoginConfig `env:"CONFIG_TEST_SERVICE_LOGIN_" cmd:"login login user and password"` + Log LogConfig `env:"CONFIG_TEST_SERVICE_LOG_" cmd:"log service log configuration"` +} + +type TypesConfig struct { + BoolValue bool `env:"CONFIG_TEST_BOOL" cmd:"bool boolean value"` + StrValue string `env:"CONFIG_TEST_STR" cmd:"str string value"` + Int8Value int8 `env:"CONFIG_TEST_INT8" cmd:"int8 int8 value"` + Int16Value int16 `env:"CONFIG_TEST_INT16" cmd:"int16 int16 value"` + IntValue int `env:"CONFIG_TEST_INT" cmd:"int int value"` + Int32Value int32 `env:"CONFIG_TEST_INT32" cmd:"int32 int32 value"` + Int64Value int64 `env:"CONFIG_TEST_INT64" cmd:"int64 int64 value"` + Uint8Value uint8 `env:"CONFIG_TEST_UINT8" cmd:"uint8 uint8 value"` + Uint16Value uint16 `env:"CONFIG_TEST_UINT16" cmd:"uint16 uint16 value"` + UintValue uint `env:"CONFIG_TEST_UINT" cmd:"uint uint value"` + Uint32Value uint32 `env:"CONFIG_TEST_UINT32" cmd:"uint32 uint32 value"` + Uint64Value uint64 `env:"CONFIG_TEST_UINT64" cmd:"uint64 uint64 value"` + Float32Value float32 `env:"CONFIG_TEST_FLOAT32" cmd:"float32 float32 value"` + Float64Value float64 `env:"CONFIG_TEST_FLOAT64" cmd:"float64 float64 value"` +} + +type DefValueConfig struct { + BoolValue bool `env:"CONFIG_TEST_BOOL" cmd:"bool boolean value" default:"true"` + IntValue int `env:"CONFIG_TEST_INT" cmd:"int int value" default:"123"` + Float64Value float64 `env:"CONFIG_TEST_FLOAT64" cmd:"float64 float64 value" default:"123.4567"` + StrValue string `env:"CONFIG_TEST_STR" cmd:"str string value" default:"default-string"` + SliceValue []string `env:"CONFIG_TEST_SLICE" cmd:"slice slice values" default:"xx:yy:zz"` + NoDefValue string `env:"CONFIG_TEST_NO_DEFVALUE" cmd:"nodefvalue no default value"` +} + +type SlicesConfig struct { + Paths []string `env:"CONFIG_TEST_SLICES_PATHS"` + Debugs []string `env:"CONFIG_TEST_SLICES_DEBUG" separator:";"` + Values []int `env:"CONFIG_TEST_SLICES_VALUES" separator:","` +} diff --git a/utils/utils.go b/utils/utils.go index 57bac51..e83cb7c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,8 +1,10 @@ package utils import ( + "fmt" "reflect" "strconv" + "strings" ) func SetValueWithBool(v reflect.Value, boolValue string) error { @@ -45,69 +47,51 @@ func SetValueWithUintX(v reflect.Value, uintValue string, bitSize int) error { return nil } -/* -func setFieldValueWithSlice(name string, v reflect.Value, envValue string, - separator string) error { - data := strings.Split(envValue, separator) +func SetValueWithSlice(v reflect.Value, slice string, separator string) error { + data := strings.Split(slice, separator) size := len(data) if size > 0 { slice := reflect.MakeSlice(v.Type(), size, size) for i := 0; i < size; i++ { ele := slice.Index(i) kind := ele.Kind() + var err error switch kind { case reflect.Bool: - if err := setFieldValueWithBool(name, ele, data[i]); err != nil { - return err - } + err = SetValueWithBool(ele, data[i]) case reflect.String: ele.SetString(data[i]) case reflect.Uint8: - if err := setFieldValueWithUintX(name, ele, data[i], 8); err != nil { - return err - } + err = SetValueWithUintX(ele, data[i], 8) case reflect.Uint16: - if err := setFieldValueWithUintX(name, ele, data[i], 16); err != nil { - return err - } + err = SetValueWithUintX(ele, data[i], 16) case reflect.Uint, reflect.Uint32: - if err := setFieldValueWithUintX(name, ele, data[i], 32); err != nil { - return err - } + err = SetValueWithUintX(ele, data[i], 32) case reflect.Uint64: - if err := setFieldValueWithUintX(name, ele, data[i], 64); err != nil { - return err - } + err = SetValueWithUintX(ele, data[i], 64) case reflect.Int8: - if err := setFieldValueWithIntX(name, ele, data[i], 8); err != nil { - return err - } + err = SetValueWithIntX(ele, data[i], 8) case reflect.Int16: - if err := setFieldValueWithIntX(name, ele, data[i], 16); err != nil { - return err - } + err = SetValueWithIntX(ele, data[i], 16) case reflect.Int, reflect.Int32: - if err := setFieldValueWithIntX(name, ele, data[i], 32); err != nil { - return err - } + err = SetValueWithIntX(ele, data[i], 32) case reflect.Int64: - if err := setFieldValueWithIntX(name, ele, data[i], 64); err != nil { - return err - } + err = SetValueWithIntX(ele, data[i], 64) case reflect.Float32: - if err := setFieldValueWithFloatX(name, ele, data[i], 32); err != nil { - return err - } + err = SetValueWithFloatX(ele, data[i], 32) case reflect.Float64: - if err := setFieldValueWithFloatX(name, ele, data[i], 64); err != nil { - return err - } + err = SetValueWithFloatX(ele, data[i], 64) default: - return fmt.Errorf("%s: can't support type: %s", name, kind.String()) + return fmt.Errorf("Can't support type: %s", kind.String()) + } + + if err != nil { + return err } } + v.Set(slice) } return nil -}*/ +}