diff --git a/env/env.go b/env/env.go index 0e82395..d442b95 100644 --- a/env/env.go +++ b/env/env.go @@ -99,6 +99,30 @@ func ToShellWithPrefix(prefix string, kvl []KeyValue) string { return sb.String() } +func SerializeValue(value interface{}) string { + switch v := value.(type) { + case bool: + res := "false" + if v { + res = "true" + } + return res + case string: + return strconv.Quote(v) + default: + return strconv.Quote(fmt.Sprint(value)) + } +} + +func kindIsIn(k reflect.Kind, kinds ...reflect.Kind) bool { + for _, kind := range kinds { + if k == kind { + return true + } + } + return false +} + // StructToEnvVars converts a struct to a map of environment variables. // The struct can have a `env` tag on each field. // The tag should be in the format `env:"ENV_VAR_NAME"`. @@ -119,16 +143,21 @@ func StructToEnvVars(s interface{}) []KeyValue { } t := v.Type() for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - tag := field.Tag.Get("env") + fieldType := t.Field(i) + tag := fieldType.Tag.Get("env") if tag == "-" { continue } if tag == "" { - tag = CamelCaseToUpperSnakeCase(field.Name) + tag = CamelCaseToUpperSnakeCase(fieldType.Name) + } + fieldValue := v.Field(i) + stringValue := "" + if !kindIsIn(fieldValue.Kind(), reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice) { + value := fieldValue.Interface() + stringValue = SerializeValue(value) } - value := v.Field(i).Interface() - envVars = append(envVars, KeyValue{Key: tag, Value: strconv.Quote(fmt.Sprint(value))}) + envVars = append(envVars, KeyValue{Key: tag, Value: stringValue}) } return envVars } diff --git a/env/env_test.go b/env/env_test.go index 62c73d4..12590c8 100644 --- a/env/env_test.go +++ b/env/env_test.go @@ -98,8 +98,10 @@ type FooConfig struct { Foo string Bar string Blah int `env:"A_SPECIAL_BLAH"` + ABool bool NotThere int `env:"-"` HTTPServer string + IntPointer *int } func TestStructToEnvVars(t *testing.T) { @@ -107,23 +109,27 @@ func TestStructToEnvVars(t *testing.T) { Foo: "a\nfoo with \" quotes and \\ and '", Bar: "42str", Blah: 42, + ABool: true, NotThere: 13, HTTPServer: "http://localhost:8080", + IntPointer: nil, } empty := env.StructToEnvVars(42) // error/empty if len(empty) != 0 { t.Errorf("expected empty, got %v", empty) } envVars := env.StructToEnvVars(&foo) - if len(envVars) != 4 { - t.Errorf("expected 4 env vars, got %+v", envVars) + if len(envVars) != 6 { + t.Errorf("expected 4 env vars, got %d: %+v", len(envVars), envVars) } str := env.ToShellWithPrefix("TST_", envVars) expected := `TST_FOO="a\nfoo with \" quotes and \\ and '" TST_BAR="42str" TST_A_SPECIAL_BLAH="42" +TST_A_BOOL=true TST_HTTP_SERVER="http://localhost:8080" -export TST_FOO TST_BAR TST_A_SPECIAL_BLAH TST_HTTP_SERVER +TST_INT_POINTER= +export TST_FOO TST_BAR TST_A_SPECIAL_BLAH TST_A_BOOL TST_HTTP_SERVER TST_INT_POINTER ` if str != expected { t.Errorf("\n---expected:---\n%s\n---got:---\n%s", expected, str)