From 6e7400df4dc7bcfb71a3e115b466058bda6e15f5 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 17 Jan 2024 10:58:39 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20errors.Errorf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- errorf.go | 61 +++++++++++++++++++++++++++++++++++++++++++ errorf_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 8 ++++++ go.sum | 10 +++++++ interface.go | 10 +++++++ 5 files changed, 160 insertions(+) create mode 100644 errorf.go create mode 100644 errorf_test.go create mode 100644 go.sum diff --git a/errorf.go b/errorf.go new file mode 100644 index 0000000..0f34db5 --- /dev/null +++ b/errorf.go @@ -0,0 +1,61 @@ +package errors + +import "fmt" + +// Returns a ResponsableError formatted from the message, with the specified status. +// Errorf passes the heavy lifting off to fmt.Errorf, and returns a similar error. +// If one error is passed in the format, this will return an UnwrappableError. +// If more than one error is passed in the format, this will return an UnwrappableErrors. +func Errorf(status int, format string, parts ...any) ResponsableError { + err := fmt.Errorf(format, parts...) + msg := err.Error() + er := &erf{status, msg} + if we, ok := err.(wrappedError); ok { + return &wrapErr{er, we.Unwrap()} + } + if wes, ok := err.(wrappedErrors); ok { + return &wrapErrs{er, wes.Unwrap()} + } + return er +} + +var _ ResponsableError = new(erf) + +type erf struct { + stat int + msg string +} + +func (e *erf) Status() int { + return e.stat +} + +func (e *erf) Error() string { + return e.msg +} + +func (e *erf) Msg() string { + return e.msg +} + +var _ UnwrappableError = new(wrapErr) + +type wrapErr struct { + *erf + err error +} + +func (e *wrapErr) Unwrap() error { + return e.err +} + +var _ UnwrappableErrors = new(wrapErrs) + +type wrapErrs struct { + *erf + errs []error +} + +func (e *wrapErrs) Unwrap() []error { + return e.errs +} diff --git a/errorf_test.go b/errorf_test.go new file mode 100644 index 0000000..314e0ee --- /dev/null +++ b/errorf_test.go @@ -0,0 +1,71 @@ +package errors + +import ( + "errors" + "net/http" + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestErrorf(t *testing.T) { + suite.Run(t, new(ErrorfTestSuite)) +} + +type ErrorfTestSuite struct { + suite.Suite +} + +func (s *ErrorfTestSuite) TestNoWrap() { + var err ResponsableError = Errorf(http.StatusTeapot, "%d is more than %d", 42, 13) + s.Assert().NotNil(err) + + _, ok := err.(UnwrappableError) + s.Assert().False(ok) + + _, ok = err.(UnwrappableErrors) + s.Assert().False(ok) + + exp := "42 is more than 13" + s.Assert().Equal(exp, err.Error()) + s.Assert().Equal(exp, err.Msg()) + s.Assert().Equal(http.StatusTeapot, err.Status()) +} + +func (s *ErrorfTestSuite) TestWrapOne() { + var wrapped error = errors.New("Here is my handle.") + var err ResponsableError = Errorf(http.StatusTeapot, "I'm a little teapot. %w", wrapped) + s.Assert().NotNil(err) + + we, ok := err.(UnwrappableError) + s.Assert().True(ok) + + _, ok = err.(UnwrappableErrors) + s.Assert().False(ok) + + exp := "I'm a little teapot. Here is my handle." + s.Assert().Equal(exp, we.Error()) + s.Assert().Equal(exp, we.Msg()) + s.Assert().Same(wrapped, we.Unwrap()) +} + +func (s *ErrorfTestSuite) TestWrapTwo() { + var wrappedOne error = errors.New("short and stout") + var wrappedTwo error = errors.New("Here is my handle.") + var err ResponsableError = Errorf(http.StatusTeapot, "I'm a little teapot: %w. %w", wrappedOne, wrappedTwo) + s.Assert().NotNil(err) + + _, ok := err.(UnwrappableError) + s.Assert().False(ok) + + we, ok := err.(UnwrappableErrors) + s.Assert().True(ok) + + exp := "I'm a little teapot: short and stout. Here is my handle." + s.Assert().Equal(exp, we.Error()) + s.Assert().Equal(exp, we.Msg()) + unwrapped := we.Unwrap() + s.Assert().Len(unwrapped, 2) + s.Assert().Same(wrappedOne, unwrapped[0]) + s.Assert().Same(wrappedTwo, unwrapped[1]) +} diff --git a/go.mod b/go.mod index 52f9d44..1e117a3 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module codeberg.org/danjones000/responsable-errors go 1.21.5 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4b6e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/interface.go b/interface.go index 1169966..516cc2c 100644 --- a/interface.go +++ b/interface.go @@ -27,3 +27,13 @@ type UnwrappableErrors interface { ResponsableError Unwrap() []error } + +type wrappedError interface { + Error() string + Unwrap() error +} + +type wrappedErrors interface { + Error() string + Unwrap() []error +}