diff --git a/README.md b/README.md index 20526d0..5553bb0 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ type Environment struct { Duration time.Duration `env:"TYPE_DURATION"` DefaultValue string `env:"MISSING_VAR,default=default_value"` RequiredValue string `env:"IM_REQUIRED,required=true"` + CustomError string `env:"CUSTOM_ERROR,required=true,error=custom_message"` } func main() { diff --git a/env.go b/env.go index 29d6360..62ae55d 100644 --- a/env.go +++ b/env.go @@ -36,11 +36,22 @@ 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") ) +// ErrMissingRequiredValue returned when a field with required=true contains no value or default +type ErrMissingRequiredValue struct { + VariableName string +} + +func (e ErrMissingRequiredValue) Error() string { + return fmt.Sprintf("value for '%s' is required", e.VariableName) +} + +func IsErrMissingRequiredValue(err error) bool { + _, ok := err.(ErrMissingRequiredValue) + return ok +} + // Unmarshal parses an EnvSet and stores the result in the value pointed to by // v. Fields that are matched in v will be deleted from EnvSet, resulting in // an EnvSet with the remaining environment variables. If v is nil or not a @@ -93,20 +104,26 @@ func Unmarshal(es EnvSet, v interface{}) error { var ( envValue string - ok bool + ok bool + lastKey string ) for _, envKey := range envTag.Keys { envValue, ok = es[envKey] if ok { break } + lastKey = envKey } if !ok { if envTag.Default != "" { envValue = envTag.Default } else if envTag.Required { - return ErrMissingRequiredValue + if envTag.ErrorName != "" { + return ErrMissingRequiredValue{envTag.ErrorName} + } else { + return ErrMissingRequiredValue{lastKey} + } } else { continue } @@ -248,9 +265,10 @@ func Marshal(v interface{}) (EnvSet, error) { } type tag struct { - Keys []string - Default string - Required bool + Keys []string + Default string + Required bool + ErrorName string } func parseTag(tagString string) tag { @@ -264,6 +282,8 @@ func parseTag(tagString string) tag { t.Default = keyData[1] case "required": t.Required = strings.ToLower(keyData[1]) == "true" + case "error": + t.ErrorName = keyData[1] default: // just ignoring unsupported keys continue diff --git a/env_test.go b/env_test.go index b1f6c90..6349996 100644 --- a/env_test.go +++ b/env_test.go @@ -81,6 +81,12 @@ type RequiredValueStruct struct { InvalidExtra string `env:"INVALID,invalid=invalid"` } +type ErrorNameStruct struct { + Required string `env:"REQUIRED_VAL,required=true"` + RequiredWithName string `env:"REQUIRED_MISSING,required=true,error=NAME"` + NotRequired string `env:"NOT_REQUIRED,required=false,error=NAME"` +} + func TestUnmarshal(t *testing.T) { environ := map[string]string{ "HOME": "/home/test", @@ -251,7 +257,7 @@ func TestUnmarshalUnexported(t *testing.T) { } func TestUnmarshalDefaultValues(t *testing.T) { - environ := map[string]string { + environ := map[string]string{ "PRESENT": "youFoundMe", } var defaultValueStruct DefaultValueStruct @@ -279,7 +285,7 @@ func TestUnmarshalRequiredValues(t *testing.T) { environ := map[string]string{} var requiredValuesStruct RequiredValueStruct err := Unmarshal(environ, &requiredValuesStruct) - if err != ErrMissingRequiredValue { + if !IsErrMissingRequiredValue(err) { t.Errorf("Expected error 'ErrMissingRequiredValue' but go '%s'", err) } environ["REQUIRED_VAL"] = "required" @@ -295,6 +301,22 @@ func TestUnmarshalRequiredValues(t *testing.T) { } } +func TestUnmarshalRequiredErrorName(t *testing.T) { + var err error + environ := map[string]string{} + var errorNameStruct ErrorNameStruct + err = Unmarshal(environ, &errorNameStruct) + if err.Error() != "value for 'REQUIRED_VAL' is required" { + t.Errorf("Expected response \"value for 'REQUIRED_VAL' is required\" but go '%s'", err) + } + + environ["REQUIRED_VAL"] = "required" + err = Unmarshal(environ, &errorNameStruct) + if err.Error() != "value for 'NAME' is required" { + t.Errorf("Expected response \"value for 'NAME' is required\" but go '%s'", err) + } +} + func TestMarshal(t *testing.T) { validStruct := ValidStruct{ Home: "/home/test",