From d6e5a55f3cec4c2213bc6c0991c979bdeedefe0c Mon Sep 17 00:00:00 2001 From: Gerwin van de Steeg <20346649+gwvandesteeg@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:50:25 +1300 Subject: [PATCH] feat: add errors.Is support to all errors Add functionality to support errors.Is on all generated errors to keep in line with best practice on checking whether an error is of the specified type as per changes to error handling in go1.13. --- internal/encoding/error_test.go | 17 ++++++++++ util.go | 6 ++++ util_test.go | 11 +++++++ viper.go | 17 ++++++++++ viper_test.go | 58 +++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+) create mode 100644 internal/encoding/error_test.go diff --git a/internal/encoding/error_test.go b/internal/encoding/error_test.go new file mode 100644 index 0000000..61a8bd7 --- /dev/null +++ b/internal/encoding/error_test.go @@ -0,0 +1,17 @@ +package encoding + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_encodingError(t *testing.T) { + err1 := fmt.Errorf("test error") + err2 := encodingError("encoding error") + assert.NotErrorIs(t, err1, err2) + assert.NotErrorIs(t, err2, err1) + assert.ErrorIs(t, err2, encodingError("encoding error")) + assert.NotErrorIs(t, err2, encodingError("other encodingerror")) +} diff --git a/util.go b/util.go index 117c6ac..b6dfa3c 100644 --- a/util.go +++ b/util.go @@ -37,6 +37,12 @@ func (pe ConfigParseError) Unwrap() error { return pe.err } +// Is adds a check to see if the specified target is an error of this type, see [errors.Is] +func (pe ConfigParseError) Is(err error) bool { + _, ok := err.(*ConfigParseError) + return ok +} + // toCaseInsensitiveValue checks if the value is a map; // if so, create a copy and lower-case the keys recursively. func toCaseInsensitiveValue(value any) any { diff --git a/util_test.go b/util_test.go index 8d0bda8..c32ba4b 100644 --- a/util_test.go +++ b/util_test.go @@ -11,6 +11,7 @@ package viper import ( + "fmt" "os" "path/filepath" "testing" @@ -84,3 +85,13 @@ func TestAbsPathify(t *testing.T) { assert.Equal(t, test.output, got) } } + +func TestConfigParseError(t *testing.T) { + // test a generic error + err1 := fmt.Errorf("test error") + assert.NotErrorIs(t, err1, &ConfigParseError{}) + // test the wrapped generic error + err2 := ConfigParseError{err: err1} + assert.ErrorIs(t, err2, &ConfigParseError{}) + assert.ErrorIs(t, err2.Unwrap(), err1) +} diff --git a/viper.go b/viper.go index 20eb4da..5a8ca63 100644 --- a/viper.go +++ b/viper.go @@ -61,6 +61,17 @@ func (e ConfigMarshalError) Error() string { return fmt.Sprintf("While marshaling config: %s", e.err.Error()) } +// Unwrap returns the wrapped error. +func (e ConfigMarshalError) Unwrap() error { + return e.err +} + +// Is adds a check to see if the specified target is an error of this type, see [errors.Is] +func (e ConfigMarshalError) Is(err error) bool { + _, ok := err.(*ConfigMarshalError) + return ok +} + var v *Viper type RemoteResponse struct { @@ -118,6 +129,12 @@ func (fnfe ConfigFileNotFoundError) Error() string { return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations) } +// Is adds a check to see if the specified target is an error of this type, see [errors.Is] +func (e ConfigFileNotFoundError) Is(err error) bool { + _, ok := err.(*ConfigFileNotFoundError) + return ok +} + // ConfigFileAlreadyExistsError denotes failure to write new configuration file. type ConfigFileAlreadyExistsError string diff --git a/viper_test.go b/viper_test.go index 0b1f407..6dae9e5 100644 --- a/viper_test.go +++ b/viper_test.go @@ -8,6 +8,7 @@ package viper import ( "bytes" "encoding/json" + "fmt" "io" "os" "os/exec" @@ -2696,3 +2697,60 @@ func skipWindows(t *testing.T) { t.Skip("Skip test on Windows") } } + +// Test the ConfigMarshalError +func TestConfigMarshalError(t *testing.T) { + // test a generic error + err1 := fmt.Errorf("test error") + assert.NotErrorIs(t, err1, &ConfigMarshalError{}) + // test the wrapped generic error + err2 := ConfigMarshalError{err: err1} + assert.ErrorIs(t, err2, &ConfigMarshalError{}) + assert.ErrorIs(t, err2.Unwrap(), err1) +} + +func TestUnsupportedConfigError(t *testing.T) { + err1 := fmt.Errorf("test error") + err2 := UnsupportedConfigError("some string") + assert.NotErrorIs(t, err2, err1) + assert.NotErrorIs(t, err1, err2) + assert.ErrorIs(t, err2, UnsupportedConfigError("some string")) + assert.NotErrorIs(t, err2, UnsupportedConfigError("other string")) +} + +func TestUnsupportedRemoteProviderError(t *testing.T) { + err1 := fmt.Errorf("test error") + err2 := UnsupportedRemoteProviderError("some string") + assert.NotErrorIs(t, err1, err2) + assert.NotErrorIs(t, err2, err1) + assert.ErrorIs(t, err2, UnsupportedRemoteProviderError("some string")) + assert.NotErrorIs(t, err2, UnsupportedRemoteProviderError("other string")) +} + +func TestRemoteConfigError(t *testing.T) { + err1 := fmt.Errorf("test error") + err2 := RemoteConfigError("some string") + assert.NotErrorIs(t, err1, err2) + assert.NotErrorIs(t, err2, err1) + assert.ErrorIs(t, err2, RemoteConfigError("some string")) + assert.NotErrorIs(t, err2, RemoteConfigError("other string")) +} + +func TestConfigFileNotFoundError(t *testing.T) { + err1 := fmt.Errorf("test error") + err2 := ConfigFileNotFoundError{name: "name", locations: "locations"} + assert.NotErrorIs(t, err1, err2) + assert.NotErrorIs(t, err2, err1) + assert.ErrorIs(t, err2, &ConfigFileNotFoundError{}) + assert.ErrorIs(t, err2, &ConfigFileNotFoundError{name: "name", locations: "locations"}) + assert.ErrorIs(t, err2, &ConfigFileNotFoundError{name: "other name", locations: "other locations"}) +} + +func TestConfigFileAlreadyExistsError(t *testing.T) { + err1 := fmt.Errorf("test error") + err2 := ConfigFileAlreadyExistsError("some string") + assert.NotErrorIs(t, err1, err2) + assert.NotErrorIs(t, err2, err1) + assert.ErrorIs(t, err2, ConfigFileAlreadyExistsError("some string")) + assert.NotErrorIs(t, err2, ConfigFileAlreadyExistsError("other string")) +}