From c0dc44e28e84ad989ed6d52b8febc89b3a7adc7e Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 17 Jan 2024 15:51:28 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20SettableError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- errorf.go | 48 +++++++++++++++++++++++++++++++++++++++++++----- errorf_test.go | 33 +++++++++++++++++++++++++++++++++ interface.go | 12 ++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/errorf.go b/errorf.go index 40e2e1d..03bde47 100644 --- a/errorf.go +++ b/errorf.go @@ -1,15 +1,28 @@ package errors -import "fmt" +import ( + "fmt" + "net/http" +) -// Returns a ResponsableError formatted from the message, with the specified status. +// Returns a SettableError 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 { +// +// The Msg method may also be formatted. +// +// If you want a different user message, use Msg, such as: +// +// err := errors.Errorf(http.StatusNotFound, "%w", sqlError).Msg("user not found %d", userId) +func Errorf(status int, format string, parts ...any) SettableError { + if len(parts) == 0 { + return &erf{status, format, ""} + } + err := fmt.Errorf(format, parts...) msg := err.Error() - er := &erf{status, msg} + er := &erf{status, msg, ""} if we, ok := err.(wrappedError); ok { return &wrapErr{er, we.Unwrap()} } @@ -20,24 +33,49 @@ func Errorf(status int, format string, parts ...any) ResponsableError { } var _ ResponsableError = new(erf) +var _ SettableError = new(erf) type erf struct { stat int + err string msg string } func (e *erf) GetStatus() int { + if e.stat < http.StatusContinue || e.stat >= 600 { + e.stat = http.StatusInternalServerError + } return e.stat } func (e *erf) Error() string { - return e.msg + return e.err } func (e *erf) GetMsg() string { + if e.msg == "" { + return e.err + } return e.msg } +func (e *erf) Status(status int) SettableError { + // GetStatus already handles invalid values, so we'll just ignore this. + if status < http.StatusContinue || status >= 600 { + return e + } + e.stat = status + return e +} + +func (e *erf) Msg(msg string, parts ...any) SettableError { + e.msg = msg + if len(parts) > 0 { + e.msg = fmt.Sprintf(msg, parts...) + } + return e +} + var _ UnwrappableError = new(wrapErr) type wrapErr struct { diff --git a/errorf_test.go b/errorf_test.go index f48b0a5..1a83ca0 100644 --- a/errorf_test.go +++ b/errorf_test.go @@ -69,3 +69,36 @@ func (s *ErrorfTestSuite) TestWrapTwo() { s.Assert().Same(wrappedOne, unwrapped[0]) s.Assert().Same(wrappedTwo, unwrapped[1]) } + +func (s *ErrorfTestSuite) TestSet() { + var err SettableError = Errorf(http.StatusTeapot, "Unable to BREW") + s.Assert().NotNil(err) + + err.Status(http.StatusTooEarly).Msg("It's only %dAM", 2) + s.Assert().Equal(http.StatusTooEarly, err.GetStatus()) + s.Assert().Equal("It's only 2AM", err.GetMsg()) + s.Assert().Equal("Unable to BREW", err.Error()) + + err.Msg("I am so great") + s.Assert().Equal("I am so great", err.GetMsg()) +} + +func (s *ErrorfTestSuite) TestGetStatusOutsideRange() { + var err ResponsableError = Errorf(5, "Hello") + s.Assert().NotNil(err) + s.Assert().Equal(http.StatusInternalServerError, err.GetStatus()) + + err = Errorf(5, "Hello") + s.Assert().NotNil(err) + s.Assert().Equal(http.StatusInternalServerError, err.GetStatus()) +} + +func (s *ErrorfTestSuite) TestSetStatusOutsideRange() { + var err SettableError = Errorf(http.StatusPaymentRequired, "Hello") + s.Assert().NotNil(err) + + err.Status(10) + s.Assert().Equal(http.StatusPaymentRequired, err.GetStatus()) + err.Status(600) + s.Assert().Equal(http.StatusPaymentRequired, err.GetStatus()) +} diff --git a/interface.go b/interface.go index 4a687d6..da03a48 100644 --- a/interface.go +++ b/interface.go @@ -6,12 +6,24 @@ package errors // GetStatus should return an appropriate HTTP status. // GetMsg should return a message suitable to display to the end user. If the message // returned by Error is safe for the end user, it may simply call that. +// GetStatus should not return a value outside of the 100 - 599 range. type ResponsableError interface { Error() string GetStatus() int GetMsg() string } +// SettableError is a ResponsableError which can be modified after initial creation. +// The Status method can set the status, while the Msg method can set user message. +// If any values are passed after the message, it should be passed to fmt.Sprintf for formatting. +// Methods are chainable. +// SetStatus should not use a value outside of 100-599. It may either ignore such values, or panic. +type SettableError interface { + ResponsableError + Status(int) SettableError + Msg(string, ...any) SettableError +} + // UnwrappableError allows a ResponsableError to wrap another error. // It may be appropriate for Error() to delegate to the wrapped error. type UnwrappableError interface {