Repository: caarlos0/env (go)

Issue:
Rename `EnvVarIsNotSetError` to `VarIsNotSetError`
The beginning of the error name is the same as the package name

Pull request:
fix: improve errors
closes #328

Patch:
diff --git a/README.md b/README.md
index 6527dc1..3625727 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,7 @@ Complete list:
 - `uint8`
 - `uint`
 - `time.Duration`
+- `time.Location`
 - `encoding.TextUnmarshaler`
 - `url.URL`
 
diff --git a/env.go b/env.go
index 27776b8..6f4ee4b 100644
--- a/env.go
+++ b/env.go
@@ -87,21 +87,34 @@ var (
 
 func defaultTypeParsers() map[reflect.Type]ParserFunc {
 	return map[reflect.Type]ParserFunc{
-		reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
-			u, err := url.Parse(v)
-			if err != nil {
-				return nil, newParseValueError("unable to parse URL", err)
-			}
-			return *u, nil
-		},
-		reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
-			s, err := time.ParseDuration(v)
-			if err != nil {
-				return nil, newParseValueError("unable to parse duration", err)
-			}
-			return s, err
-		},
+		reflect.TypeOf(url.URL{}):       parseURL,
+		reflect.TypeOf(time.Nanosecond): parseDuration,
+		reflect.TypeOf(time.Location{}): parseLocation,
+	}
+}
+
+func parseURL(v string) (interface{}, error) {
+	u, err := url.Parse(v)
+	if err != nil {
+		return nil, newParseValueError("unable to parse URL", err)
+	}
+	return *u, nil
+}
+
+func parseDuration(v string) (interface{}, error) {
+	d, err := time.ParseDuration(v)
+	if err != nil {
+		return nil, newParseValueError("unable to parse duration", err)
+	}
+	return d, err
+}
+
+func parseLocation(v string) (interface{}, error) {
+	loc, err := time.LoadLocation(v)
+	if err != nil {
+		return nil, newParseValueError("unable to parse location", err)
 	}
+	return *loc, nil
 }
 
 // ParserFunc defines the signature of a function that can be used within
@@ -550,11 +563,11 @@ func get(fieldParams FieldParams, opts Options) (val string, err error) {
 	}
 
 	if fieldParams.Required && !exists && len(fieldParams.OwnKey) > 0 {
-		return "", newEnvVarIsNotSet(fieldParams.Key)
+		return "", newVarIsNotSetError(fieldParams.Key)
 	}
 
 	if fieldParams.NotEmpty && val == "" {
-		return "", newEmptyEnvVarError(fieldParams.Key)
+		return "", newEmptyVarError(fieldParams.Key)
 	}
 
 	if fieldParams.LoadFile && val != "" {
diff --git a/error.go b/error.go
index 156ca3e..a5c6ae4 100644
--- a/error.go
+++ b/error.go
@@ -12,8 +12,8 @@ import (
 // NotStructPtrError
 // NoParserError
 // NoSupportedTagOptionError
-// EnvVarIsNotSetError
-// EmptyEnvVarError
+// VarIsNotSetError
+// EmptyVarError
 // LoadFileContentError
 // ParseValueError
 type AggregateError struct {
@@ -65,16 +65,14 @@ func (e ParseError) Error() string {
 	return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err)
 }
 
-// The error occurs when pass something that is not a pointer to a Struct to Parse
+// The error occurs when pass something that is not a pointer to a struct to Parse
 type NotStructPtrError struct{}
 
 func (e NotStructPtrError) Error() string {
 	return "expected a pointer to a Struct"
 }
 
-// This error occurs when there is no parser provided for given type
-// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
-// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
+// This error occurs when there is no parser provided for given type.
 type NoParserError struct {
 	Name string
 	Type reflect.Type
@@ -88,9 +86,9 @@ func (e NoParserError) Error() string {
 	return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type)
 }
 
-// This error occurs when the given tag is not supported
-// In-built supported tags: "", "file", "required", "unset", "notEmpty", "expand", "envDefault", "envSeparator"
-// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name
+// This error occurs when the given tag is not supported.
+// Built-in supported tags: "", "file", "required", "unset", "notEmpty",
+// "expand", "envDefault", and "envSeparator".
 type NoSupportedTagOptionError struct {
 	Tag string
 }
@@ -103,36 +101,43 @@ func (e NoSupportedTagOptionError) Error() string {
 	return fmt.Sprintf("tag option %q not supported", e.Tag)
 }
 
-// This error occurs when the required variable is not set
-// Read about required fields: https://github.com/caarlos0/env#required-fields
-type EnvVarIsNotSetError struct {
+// This error occurs when the required variable is not set.
+//
+// deprecated: use VarIsNotSetError
+type EnvVarIsNotSetError = VarIsNotSetError
+
+// This error occurs when the required variable is not set.
+type VarIsNotSetError struct {
 	Key string
 }
 
-func newEnvVarIsNotSet(key string) error {
-	return EnvVarIsNotSetError{key}
+func newVarIsNotSetError(key string) error {
+	return VarIsNotSetError{key}
 }
 
-func (e EnvVarIsNotSetError) Error() string {
+func (e VarIsNotSetError) Error() string {
 	return fmt.Sprintf(`required environment variable %q is not set`, e.Key)
 }
 
 // This error occurs when the variable which must be not empty is existing but has an empty value
-// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields
-type EmptyEnvVarError struct {
+//
+// deprecated: use EmptyVarError
+type EmptyEnvVarError = EmptyVarError
+
+// This error occurs when the variable which must be not empty is existing but has an empty value
+type EmptyVarError struct {
 	Key string
 }
 
-func newEmptyEnvVarError(key string) error {
-	return EmptyEnvVarError{key}
+func newEmptyVarError(key string) error {
+	return EmptyVarError{key}
 }
 
-func (e EmptyEnvVarError) Error() string {
+func (e EmptyVarError) Error() string {
 	return fmt.Sprintf("environment variable %q should not be empty", e.Key)
 }
 
-// This error occurs when it's impossible to load the value from the file
-// Read about From file feature: https://github.com/caarlos0/env#from-file
+// This error occurs when it's impossible to load the value from the file.
 type LoadFileContentError struct {
 	Filename string
 	Key      string
@@ -147,9 +152,7 @@ func (e LoadFileContentError) Error() string {
 	return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err)
 }
 
-// This error occurs when it's impossible to convert value using given parser
-// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
-// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
+// This error occurs when it's impossible to convert value using given parser.
 type ParseValueError struct {
 	Msg string
 	Err error


Test patch:
diff --git a/env_test.go b/env_test.go
index 1a567b4..55d2fb6 100644
--- a/env_test.go
+++ b/env_test.go
@@ -108,6 +108,11 @@ type Config struct {
 	DurationPtr  *time.Duration   `env:"DURATION"`
 	DurationPtrs []*time.Duration `env:"DURATIONS"`
 
+	Location     time.Location    `env:"LOCATION"`
+	Locations    []time.Location  `env:"LOCATIONS"`
+	LocationPtr  *time.Location   `env:"LOCATION"`
+	LocationPtrs []*time.Location `env:"LOCATIONS"`
+
 	Unmarshaler     unmarshaler    `env:"UNMARSHALER"`
 	UnmarshalerPtr  *unmarshaler   `env:"UNMARSHALER"`
 	Unmarshalers    []unmarshaler  `env:"UNMARSHALERS"`
@@ -118,7 +123,7 @@ type Config struct {
 	URLs    []url.URL  `env:"URLS"`
 	URLPtrs []*url.URL `env:"URLS"`
 
-	StringWithdefault string `env:"DATABASE_URL" envDefault:"postgres://localhost:5432/db"`
+	StringWithDefault string `env:"DATABASE_URL" envDefault:"postgres://localhost:5432/db"`
 
 	CustomSeparator []string `env:"SEPSTRINGS" envSeparator:":"`
 
@@ -254,6 +259,12 @@ func TestParsesEnv(t *testing.T) {
 	t.Setenv("DURATION", tos(duration1))
 	t.Setenv("DURATIONS", toss(duration1, duration2))
 
+	location1 := time.UTC
+	location2, errLoadLocation := time.LoadLocation("Europe/Berlin")
+	isNoErr(t, errLoadLocation)
+	t.Setenv("LOCATION", tos(location1))
+	t.Setenv("LOCATIONS", toss(location1, location2))
+
 	unmarshaler1 := unmarshaler{time.Minute}
 	unmarshaler2 := unmarshaler{time.Millisecond * 1232}
 	t.Setenv("UNMARSHALER", tos(unmarshaler1.Duration))
@@ -377,6 +388,13 @@ func TestParsesEnv(t *testing.T) {
 	isEqual(t, &duration1, cfg.DurationPtrs[0])
 	isEqual(t, &duration2, cfg.DurationPtrs[1])
 
+	isEqual(t, *location1, cfg.Location)
+	isEqual(t, location1, cfg.LocationPtr)
+	isEqual(t, *location1, cfg.Locations[0])
+	isEqual(t, *location2, cfg.Locations[1])
+	isEqual(t, location1, cfg.LocationPtrs[0])
+	isEqual(t, location2, cfg.LocationPtrs[1])
+
 	isEqual(t, unmarshaler1, cfg.Unmarshaler)
 	isEqual(t, &unmarshaler1, cfg.UnmarshalerPtr)
 	isEqual(t, unmarshaler1, cfg.Unmarshalers[0])
@@ -391,7 +409,7 @@ func TestParsesEnv(t *testing.T) {
 	isEqual(t, url1, cfg.URLPtrs[0].String())
 	isEqual(t, url2, cfg.URLPtrs[1].String())
 
-	isEqual(t, "postgres://localhost:5432/db", cfg.StringWithdefault)
+	isEqual(t, "postgres://localhost:5432/db", cfg.StringWithDefault)
 	isEqual(t, nonDefinedStr, cfg.NonDefined.String)
 	isEqual(t, nonDefinedStr, cfg.NestedNonDefined.NonDefined.String)
 
@@ -632,8 +650,8 @@ func TestParsesEnvInnerFailsMultipleErrors(t *testing.T) {
 	err := Parse(&config{})
 	isErrorWithMessage(t, err, `env: required environment variable "NAME" is not set; parse error on field "Number" of type "int": strconv.ParseInt: parsing "not-a-number": invalid syntax; required environment variable "AGE" is not set`)
 	isTrue(t, errors.Is(err, ParseError{}))
-	isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
-	isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+	isTrue(t, errors.Is(err, VarIsNotSetError{}))
+	isTrue(t, errors.Is(err, VarIsNotSetError{}))
 }
 
 func TestParsesEnvInnerNil(t *testing.T) {
@@ -801,6 +819,20 @@ func TestInvalidDurations(t *testing.T) {
 	isTrue(t, errors.Is(err, ParseError{}))
 }
 
+func TestInvalidLocation(t *testing.T) {
+	t.Setenv("LOCATION", "should-be-a-valid-location")
+	err := Parse(&Config{})
+	isErrorWithMessage(t, err, `env: parse error on field "Location" of type "time.Location": unable to parse location: unknown time zone should-be-a-valid-location; parse error on field "LocationPtr" of type "*time.Location": unable to parse location: unknown time zone should-be-a-valid-location`)
+	isTrue(t, errors.Is(err, ParseError{}))
+}
+
+func TestInvalidLocations(t *testing.T) {
+	t.Setenv("LOCATIONS", "should-be-a-valid-location,UTC,Europe/Berlin")
+	err := Parse(&Config{})
+	isErrorWithMessage(t, err, `env: parse error on field "Locations" of type "[]time.Location": unable to parse location: unknown time zone should-be-a-valid-location; parse error on field "LocationPtrs" of type "[]*time.Location": unable to parse location: unknown time zone should-be-a-valid-location`)
+	isTrue(t, errors.Is(err, ParseError{}))
+}
+
 func TestParseStructWithoutEnvTag(t *testing.T) {
 	cfg := Config{}
 	isNoErr(t, Parse(&cfg))
@@ -900,7 +932,7 @@ func TestErrorRequiredNotSet(t *testing.T) {
 	}
 	err := Parse(&config{})
 	isErrorWithMessage(t, err, `env: required environment variable "IS_REQUIRED" is not set`)
-	isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+	isTrue(t, errors.Is(err, VarIsNotSetError{}))
 }
 
 func TestNoErrorNotEmptySet(t *testing.T) {
@@ -926,7 +958,7 @@ func TestErrorNotEmptySet(t *testing.T) {
 	}
 	err := Parse(&config{})
 	isErrorWithMessage(t, err, `env: environment variable "IS_REQUIRED" should not be empty`)
-	isTrue(t, errors.Is(err, EmptyEnvVarError{}))
+	isTrue(t, errors.Is(err, EmptyVarError{}))
 }
 
 func TestErrorRequiredAndNotEmptySet(t *testing.T) {
@@ -936,7 +968,7 @@ func TestErrorRequiredAndNotEmptySet(t *testing.T) {
 	}
 	err := Parse(&config{})
 	isErrorWithMessage(t, err, `env: environment variable "IS_REQUIRED" should not be empty`)
-	isTrue(t, errors.Is(err, EmptyEnvVarError{}))
+	isTrue(t, errors.Is(err, EmptyVarError{}))
 }
 
 func TestErrorRequiredNotSetWithDefault(t *testing.T) {
@@ -1016,7 +1048,7 @@ func TestParseUnsetRequireOptions(t *testing.T) {
 
 	err := Parse(&cfg)
 	isErrorWithMessage(t, err, `env: required environment variable "PASSWORD" is not set`)
-	isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+	isTrue(t, errors.Is(err, VarIsNotSetError{}))
 	t.Setenv("PASSWORD", "superSecret")
 	isNoErr(t, Parse(&cfg))
 
@@ -1435,7 +1467,7 @@ func TestFileNoParamRequired(t *testing.T) {
 
 	err := Parse(&config{})
 	isErrorWithMessage(t, err, `env: required environment variable "SECRET_KEY" is not set`)
-	isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+	isTrue(t, errors.Is(err, VarIsNotSetError{}))
 }
 
 func TestFileBadFile(t *testing.T) {
@@ -1525,11 +1557,11 @@ func TestRequiredIfNoDefOption(t *testing.T) {
 	t.Run("missing", func(t *testing.T) {
 		err := ParseWithOptions(&cfg, Options{RequiredIfNoDef: true})
 		isErrorWithMessage(t, err, `env: required environment variable "NAME" is not set; required environment variable "FRUIT" is not set`)
-		isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+		isTrue(t, errors.Is(err, VarIsNotSetError{}))
 		t.Setenv("NAME", "John")
 		err = ParseWithOptions(&cfg, Options{RequiredIfNoDef: true})
 		isErrorWithMessage(t, err, `env: required environment variable "FRUIT" is not set`)
-		isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+		isTrue(t, errors.Is(err, VarIsNotSetError{}))
 	})
 
 	t.Run("all set", func(t *testing.T) {
@@ -1561,7 +1593,7 @@ func TestRequiredIfNoDefNested(t *testing.T) {
 
 		err := ParseWithOptions(&cfg, Options{RequiredIfNoDef: true})
 		isErrorWithMessage(t, err, `env: required environment variable "SERVER_PORT" is not set`)
-		isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+		isTrue(t, errors.Is(err, VarIsNotSetError{}))
 	})
 
 	t.Run("all set", func(t *testing.T) {
@@ -2123,7 +2155,7 @@ func TestIssue298ErrorNestedFieldRequiredNotSet(t *testing.T) {
 	cfg := ComplexConfig{}
 	err := Parse(&cfg)
 	isErrorWithMessage(t, err, `env: required environment variable "FOO_0_STR" is not set`)
-	isTrue(t, errors.Is(err, EnvVarIsNotSetError{}))
+	isTrue(t, errors.Is(err, VarIsNotSetError{}))
 }
 
 func TestIssue320(t *testing.T) {
diff --git a/example_test.go b/example_test.go
index ae8cc06..fffdbec 100644
--- a/example_test.go
+++ b/example_test.go
@@ -424,7 +424,7 @@ func ExampleParse_errorHandling() {
 
 	var cfg Config
 	if err := Parse(&cfg); err != nil {
-		if errors.Is(err, EmptyEnvVarError{}) {
+		if errors.Is(err, EmptyVarError{}) {
 			fmt.Println("oopsie")
 		}
 		aggErr := AggregateError{}
@@ -440,7 +440,7 @@ func ExampleParse_errorHandling() {
 				// EmptyEnvVarError
 				// LoadFileContentError
 				// ParseValueError
-				case EmptyEnvVarError:
+				case EmptyVarError:
 					fmt.Println("daisy")
 				default:
 					fmt.Printf("Unknown error type %v", v)