Skip to content

Commit

Permalink
Merge pull request #4 from elri/invalid-vals
Browse files Browse the repository at this point in the history
Error handling mode
  • Loading branch information
elri committed Nov 22, 2022
2 parents 61e5aa3 + 7e1ee74 commit 36fb47c
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 30 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Coverage files
coverage.txt
*.xml
64 changes: 52 additions & 12 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ var (

writedefconf bool
printconf bool

configErrorHandling flag.ErrorHandling
)

var osExit = os.Exit //to enable testing
Expand All @@ -34,6 +36,25 @@ func init() {
flagSet.Usage = Usage
}

// Init sets the error handling property.
// The error handling for the config package is the same as that
// for the std flag package
func Init(errorHandling flag.ErrorHandling) {
configErrorHandling = errorHandling
flagSet.Init("config", errorHandling)
}

func handleError(err error) {
switch configErrorHandling {
case flag.ContinueOnError:
return
case flag.ExitOnError:
osExit(2)
case flag.PanicOnError:
panic(err)
}
}

func SetEnvPrefix(prefix string) {
envPrefix = prefix
}
Expand All @@ -48,7 +69,11 @@ func SetEnvsToParse(envVarNames []string) (err error) {
if ok {
e = strings.ToLower(e)
envs[e] = envVar
} //else if STOP ON ERROR (TODO)
} else {
newErr := fmt.Errorf("could not find %s", e)
err = addErr(err, newErr)
handleError(err)
}
}
return
}
Expand Down Expand Up @@ -93,7 +118,7 @@ func setup(cfg interface{}, filename string, dirs ...string) (err error) {
name := strings.ToLower(field.Name)
v := envs[name]
msg := "type of environmental variable not one that is handled by config"
env_err := setFieldString(v, fieldVal, msg)
env_err := setFieldString(v, name, fieldVal, msg)
if env_err != nil {
err = addErr(err, env_err)
}
Expand Down Expand Up @@ -192,7 +217,7 @@ func setField(toInsert interface{}, fieldVal reflect.Value, defaultMsg string) (
return
}

func setFieldString(toInsert interface{}, fieldVal reflect.Value, defaultMsg string) (err error) {
func setFieldString(toInsert interface{}, fieldName string, fieldVal reflect.Value, defaultMsg string) (err error) {
if toInsert != nil {
toInsertVal := reflect.ValueOf(toInsert)
var converted interface{}
Expand Down Expand Up @@ -224,6 +249,11 @@ func setFieldString(toInsert interface{}, fieldVal reflect.Value, defaultMsg str
if err == nil {
newVal := reflect.ValueOf(converted)
fieldVal.Set(newVal)
} else {
errStr := fmt.Sprintf("env var '%s' trying to set field '%s' with type %s to '%s' (ignored)", envPrefix+fieldName, fieldName, k, toInsertValStr)
err = errors.New(errStr)
handleError(err)
fmt.Println("WARNING: " + errStr)
}
}
return
Expand Down Expand Up @@ -252,8 +282,13 @@ func StringIgnoreZeroValues(c interface{}) string {

func createString(c interface{}, printZeroValues bool) string {

doPrint := func(field reflect.Value) bool {
return !field.IsZero() || field.Kind() == reflect.Bool || printZeroValues
doPrint := func(fieldVal reflect.Value) bool {
return !fieldVal.IsZero() || fieldVal.Kind() == reflect.Bool || printZeroValues
}

isTime := func(fieldVal reflect.Value) bool {
now := time.Now()
return reflect.Indirect(fieldVal).Type() == reflect.ValueOf(now).Type()
}

ret := ""
Expand All @@ -264,13 +299,18 @@ func createString(c interface{}, printZeroValues bool) string {
fieldVal := rv.Field(i)
switch fieldVal.Kind() {
case reflect.Struct:
ret += fmt.Sprintf("%s: \n", strings.ToLower(field.Name))
jTyp := fieldVal.Type()
for j := 0; j < fieldVal.NumField(); j++ {
jField := fieldVal.Field(j)
name := strings.ToLower(jTyp.Field(j).Name)
if doPrint(jField) {
ret += fmt.Sprint(" ", name, ": ", jField, "\n")
// Check if time.Time type
if isTime(fieldVal) {
ret += fmt.Sprint(strings.ToLower(field.Name), ": ", reflect.Indirect(fieldVal), "\n")
} else {
ret += fmt.Sprintf("%s: \n", strings.ToLower(field.Name))
jTyp := fieldVal.Type()
for j := 0; j < fieldVal.NumField(); j++ {
jField := fieldVal.Field(j)
name := strings.ToLower(jTyp.Field(j).Name)
if doPrint(jField) {
ret += fmt.Sprint(" ", name, ": ", jField, "\n")
}
}
}
default:
Expand Down
130 changes: 129 additions & 1 deletion config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -34,9 +35,79 @@ func Test_String(t *testing.T) {
assert.Equal(t, expc, mioStr)
}

func Test_handleError(t *testing.T) {
/* errStr := "handleError testing"
err := errors.New(errStr)
// CONTINUEONERROR
Init(flag.ContinueOnError)
handleError(err)
//nothing happens
// EXITONERROR
Init(flag.ExitOnError)
// intercept exit func
oldOsExit := osExit
defer func() { osExit = oldOsExit }()
var got int
myExit := func(code int) {
got = code
}
osExit = myExit
// parse conf
handleError(err)
if exp := 2; got != exp {
t.Errorf("Expected exit code: %d, got: %d", exp, got)
}
*/
}

func Test_SetEnvsToParse(t *testing.T) {
resetConfig()
SetEnvPrefix("CONFTEST_")

envs := make(map[string]string, 0)
envs["env1"] = "text"
envs["env2"] = "3.141595"
envs["env3"] = "true"
envs["env4"] = "79"

envStrs := []string{"env1", "env2", "env3", "env4"}

err := SetEnvsToParse(envStrs)
assert.NotNil(t, err)
for _, e := range envStrs {
exp := fmt.Sprintf("could not find %s", e)
assert.Contains(t, err.Error(), exp)
}

for e, v := range envs {
envVarName := "CONFTEST_" + e
os.Setenv(envVarName, v)
}
err = SetEnvsToParse(envStrs)
assert.Nil(t, err)

for _, env := range envStrs {
err = os.Unsetenv("CONFTEST_" + env)
assert.Nil(t, err)
}
}

func Test_ConfigDefault(t *testing.T) {
var err error

// don't care if default file doesn't exist
err = SetDefaultFile("doesntexist.yml")
assert.NotNil(t, err)

err = SetUpConfiguration(new(TestConfig))
assert.Nil(t, err)

// set default config file
err = SetDefaultFile(DEFAULT_TEST_CONFIG)
assert.Nil(t, err)
Expand Down Expand Up @@ -115,6 +186,61 @@ func Test_ConfigEnv(t *testing.T) {

}

func Test_ConfigEnvFaultyVals(t *testing.T) {
var err error

// Redirect stdin & stdout
var r, w *os.File
r, w, err = os.Pipe()
assert.Nil(t, err)
origStdout := os.Stdout
os.Stdout = w
origStdin := os.Stdin
os.Stdin = r
defer func() {
// Restore
os.Stdout = origStdout
os.Stdin = origStdin
}()

resetConfig()

// env setup
SetEnvPrefix("CONFTEST_")

envs := make(map[string]string, 0)
envs["Pim"] = "3.141595"
envs["Pi"] = "env_pim"
envs["Dreams"] = "79"
envs["Age"] = "true"

envStrs := make([]string, 0)
for e, v := range envs {
envStrs = append(envStrs, e)
envVarName := "CONFTEST_" + e
os.Setenv(envVarName, v)
}

err = SetEnvsToParse(envStrs)
assert.Nil(t, err)

// set default config file
err = SetDefaultFile(DEFAULT_TEST_CONFIG)
assert.Nil(t, err)

// get config
conf := new(TestConfig)
err = SetUpConfigurationWithConfigFile(conf, "test/test.yml")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "ignored")

output := make([]byte, 1024)
_, err = r.Read(output)
assert.Nil(t, err)
assert.Contains(t, string(output), "WARNING")

}

func Test_ConfigFlags(t *testing.T) {
var err error

Expand Down Expand Up @@ -347,9 +473,11 @@ func Test_setFieldString(t *testing.T) {
rv := reflect.ValueOf(ms).Elem()
typ := rv.Type()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldVal := rv.Field(i)
name := strings.ToLower(field.Name)

err := setFieldString(wrong[i], fieldVal, "arbitrary error msg")
err := setFieldString(wrong[i], name, fieldVal, "arbitrary error msg")
assert.NotNil(t, err)
}
}
11 changes: 7 additions & 4 deletions flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ func GetFlagValue(f *flag.Flag) *FlagValue {

func ensureFlagValue(f *flag.Flag) (changed bool) {
if f.Value == nil {
// TODO - ContinueOnError default behaviour
err := errors.New("flag Value is nil")
handleError(err)
return
}
val := reflect.Indirect(reflect.ValueOf(f.Value))
Expand Down Expand Up @@ -223,7 +224,9 @@ func beforeParse() func(*flag.Flag) {
func afterParse() func(*flag.Flag) {
return func(f *flag.Flag) {
if !flagSet.Parsed() {
panic(errors.New("flagSet not parsed")) //TODO
err := errors.New("flagSet not parsed")
handleError(err)
// panic(errors.New("flagSet not parsed")) //TODO
}
if ParsedFlag(f) {
if f.Name == printConfFlagName && f.Value.String() == "true" {
Expand Down Expand Up @@ -275,9 +278,9 @@ func addFlagValueToMap(m map[string]interface{}, f *flag.Flag, value string) {
m[name], err = strconv.ParseUint(value, 10, 64)
}

if err != nil {
if err != nil { //probably won't reach here, flag.Parse() will protest before this
fmt.Println("panicking in addFlagValueToMap", f.Name, value)
panic(err) //TODO . but probably won't reach here, flag.Parse() will protest before this
handleError(err)
}

}
39 changes: 27 additions & 12 deletions flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func Test_ParseBoolFLag(t *testing.T) {
}

func Test_ParseFlags(t *testing.T) {
var err error
flagSet := testInit()

_ = flagSet.Bool(b, false, "usage")
Expand All @@ -193,15 +194,17 @@ func Test_ParseFlags(t *testing.T) {
_ = flagSet.Uint(ui, 20, "usage")
_ = flagSet.Duration(d, 5*time.Second, "usage")

flags := map[string]*flag.Flag{
b: LookupFlag(b),
f64: LookupFlag(f64),
i: LookupFlag(i),
i64: LookupFlag(i64),
str: LookupFlag(str),
ui: LookupFlag(ui),
d: LookupFlag(d)}
// only given flag is default flag '-write def conf'
SetFlagSetArgs([]string{"-write-def-conf"})
err = ParseFlags()
assert.Nil(t, err)

//only given flag is default flag '-print-conf'
SetFlagSetArgs([]string{"-print-conf"})
err = ParseFlags()
assert.Nil(t, err)

//Parsed
args := []string{
"-f64", "3.1415",
"-i", "342",
Expand All @@ -210,10 +213,18 @@ func Test_ParseFlags(t *testing.T) {
}
SetFlagSetArgs(args)

//parse
err := ParseFlags()
err = ParseFlags()
assert.Nil(t, err)

flags := map[string]*flag.Flag{
b: LookupFlag(b),
f64: LookupFlag(f64),
i: LookupFlag(i),
i64: LookupFlag(i64),
str: LookupFlag(str),
ui: LookupFlag(ui),
d: LookupFlag(d)}

for _, arg := range args {
argSplit := strings.Split(arg, "-")
if len(argSplit) > 1 {
Expand Down Expand Up @@ -359,6 +370,11 @@ func Test_afterParse(t *testing.T) {
f.Value = &FlagValue{Value: f.Value}
})

// no flags added if parse hasn't happened
// continueonerror is default so nothing happens
flagSet.VisitAll(afterParse())
assert.Equal(t, flags, make(map[string]interface{}))

err := flagSet.Parse(args)
assert.Nil(t, err)

Expand Down Expand Up @@ -454,8 +470,7 @@ func Test_DefaultFlagPrint(t *testing.T) {
args_print := []string{"-write-def-conf", "-print-conf"}
SetFlagSetArgs(args_print)

var err error
err = ParseFlags()
err := ParseFlags()
assert.Nil(t, err)

// intercept exit func
Expand Down
Loading

0 comments on commit 36fb47c

Please sign in to comment.