Skip to content

Commit

Permalink
Merge pull request #8 from jimmykodes/add_default_values
Browse files Browse the repository at this point in the history
Add default values
  • Loading branch information
hinshun authored Aug 3, 2020
2 parents 5660fe1 + 8b9a491 commit 9271595
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 4 deletions.
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

0 comments on commit 9271595

Please sign in to comment.