Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default values #8

Merged
merged 3 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ type Environment struct {

Extras env.EnvSet

Duration time.Duration `env:"TYPE_DURATION"`
Duration time.Duration `env:"TYPE_DURATION"`
DefaultValue string `env:"MISSING_VAR,default=default_value"`
RequiredValue string `env:"IM_REQUIRED,required=true"`
}

func main() {
Expand Down
43 changes: 40 additions & 3 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ var (

// ErrUnexportedField returned when a field with tag "env" is not exported.
ErrUnexportedField = errors.New("field must be exported")

// ErrMissingRequiredValue returned when a field with required=true contains no value or default
ErrMissingRequiredValue = errors.New("value for this field is required")
)

// Unmarshal parses an EnvSet and stores the result in the value pointed to by
Expand Down Expand Up @@ -86,21 +89,27 @@ func Unmarshal(es EnvSet, v interface{}) error {
return ErrUnexportedField
}

envKeys := strings.Split(tag, ",")
envTag := parseTag(tag)

var (
envValue string
ok bool
)
for _, envKey := range envKeys {
for _, envKey := range envTag.Keys {
envValue, ok = es[envKey]
if ok {
break
}
}

if !ok {
continue
if envTag.Default != "" {
envValue = envTag.Default
} else if envTag.Required {
return ErrMissingRequiredValue
} else {
continue
}
}

err := set(typeField.Type, valueField, envValue)
Expand Down Expand Up @@ -237,3 +246,31 @@ func Marshal(v interface{}) (EnvSet, error) {

return es, nil
}

type tag struct {
Keys []string
Default string
Required bool
}

func parseTag(tagString string) tag {
var t tag
envKeys := strings.Split(tagString, ",")
for _, key := range envKeys {
if strings.Contains(key, "=") {
keyData := strings.Split(key, "=")
switch strings.ToLower(keyData[0]) {
case "default":
t.Default = keyData[1]
case "required":
t.Required = strings.ToLower(keyData[1]) == "true"
default:
// just ignoring unsupported keys
continue
}
} else {
t.Keys = append(t.Keys, key)
}
}
return t
}
60 changes: 60 additions & 0 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ type UnexportedStruct struct {
home string `env:"HOME"`
}

type DefaultValueStruct struct {
DefaultString string `env:"MISSING_STRING,default=found"`
DefaultBool bool `env:"MISSING_BOOL,default=true"`
DefaultInt int `env:"MISSING_INT,default=7"`
DefaultDuration time.Duration `env:"MISSING_DURATION,default=5s"`
DefaultWithOptionsMissing string `env:"MISSING_1,MISSING_2,default=present"`
DefaultWithOptionsPresent string `env:"MISSING_1,PRESENT,default=present"`
}

type RequiredValueStruct struct {
Required string `env:"REQUIRED_VAL,required=true"`
RequiredWithDefault string `env:"REQUIRED_MISSING,default=myValue,required=true"`
NotRequired string `env:"NOT_REQUIRED,required=false"`
InvalidExtra string `env:"INVALID,invalid=invalid"`
}

func TestUnmarshal(t *testing.T) {
environ := map[string]string{
"HOME": "/home/test",
Expand Down Expand Up @@ -233,6 +249,50 @@ func TestUnmarshalUnexported(t *testing.T) {
}
}

func TestUnmarshalDefaultValues(t *testing.T) {
environ := map[string]string {
"PRESENT": "youFoundMe",
}
var defaultValueStruct DefaultValueStruct
err := Unmarshal(environ, &defaultValueStruct)
if err != nil {
t.Errorf("Expected no error but got %s", err)
}
testCases := [][]interface{}{
{defaultValueStruct.DefaultInt, 7},
{defaultValueStruct.DefaultBool, true},
{defaultValueStruct.DefaultString, "found"},
{defaultValueStruct.DefaultDuration, 5 * time.Second},
{defaultValueStruct.DefaultWithOptionsMissing, "present"},
{defaultValueStruct.DefaultWithOptionsPresent, "youFoundMe"},
}
for _, testCase := range testCases {
if testCase[0] != testCase[1] {
t.Errorf("Expected field value to be '%v' but got '%v'", testCase[1], testCase[0])
}
}
}

func TestUnmarshalRequiredValues(t *testing.T) {
environ := map[string]string{}
var requiredValuesStruct RequiredValueStruct
err := Unmarshal(environ, &requiredValuesStruct)
if err != ErrMissingRequiredValue {
t.Errorf("Expected error 'ErrMissingRequiredValue' but go '%s'", err)
}
environ["REQUIRED_VAL"] = "required"
err = Unmarshal(environ, &requiredValuesStruct)
if err != nil {
t.Errorf("Expected no error but got '%s'", err)
}
if requiredValuesStruct.Required != "required" {
t.Errorf("Expected field value to be '%s' but got '%s'", "required", requiredValuesStruct.Required)
}
if requiredValuesStruct.RequiredWithDefault != "myValue" {
t.Errorf("Expected field value to be '%s' but got '%s'", "myValue", requiredValuesStruct.RequiredWithDefault)
}
}

func TestMarshal(t *testing.T) {
validStruct := ValidStruct{
Home: "/home/test",
Expand Down