diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f05820..871c69d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,5 @@ # Changelog -## [0.2.1] - 2024-02-07 - -### Added - -- NewUnauthorized for default 401 -- NewForbidden for default 403 - -## [0.2.0] - 2024-01-21 - -### Added - -- ResponsableError.JSON -- ResponsableError extends json.Marshaler -- SettableError.SetField - -## [0.1.1] - 2024-01-17 - -### Changed - -- Just the license - ## [0.1.0] - 2024-01-17 Initial Release! Hope you like it! diff --git a/README.md b/README.md index 4df69c0..dc401b7 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,4 @@ In the second example, `err.GetStatus()` returns 404, `err.GetMsg()` returns "Us ## Future plans -Any suggestions/requests? +I'm thinking about making the errors able to return a default body from `json.Marshal`, possibly with an optional override.` diff --git a/errorf.go b/errorf.go index 5f371e5..03bde47 100644 --- a/errorf.go +++ b/errorf.go @@ -1,7 +1,6 @@ package errors import ( - "encoding/json" "fmt" "net/http" ) @@ -15,16 +14,15 @@ import ( // // If you want a different user message, use Msg, such as: // -// err := errors.Errorf(http.StatusNotFound, "%w", sqlError).SetMsg("user not found %d", userId) +// err := errors.Errorf(http.StatusNotFound, "%w", sqlError).Msg("user not found %d", userId) func Errorf(status int, format string, parts ...any) SettableError { - meta := make(map[string]any) if len(parts) == 0 { - return &erf{status, format, "", meta} + return &erf{status, format, ""} } err := fmt.Errorf(format, parts...) msg := err.Error() - er := &erf{status, msg, "", meta} + er := &erf{status, msg, ""} if we, ok := err.(wrappedError); ok { return &wrapErr{er, we.Unwrap()} } @@ -36,16 +34,14 @@ func Errorf(status int, format string, parts ...any) SettableError { var _ ResponsableError = new(erf) var _ SettableError = new(erf) -var _ json.Marshaler = new(erf) type erf struct { stat int err string msg string - meta map[string]any } -func (e *erf) Status() int { +func (e *erf) GetStatus() int { if e.stat < http.StatusContinue || e.stat >= 600 { e.stat = http.StatusInternalServerError } @@ -56,15 +52,15 @@ func (e *erf) Error() string { return e.err } -func (e *erf) Msg() string { +func (e *erf) GetMsg() string { if e.msg == "" { return e.err } return e.msg } -func (e *erf) SetStatus(status int) SettableError { - // Status already handles invalid values, so we'll just ignore this. +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 } @@ -72,7 +68,7 @@ func (e *erf) SetStatus(status int) SettableError { return e } -func (e *erf) SetMsg(msg string, parts ...any) SettableError { +func (e *erf) Msg(msg string, parts ...any) SettableError { e.msg = msg if len(parts) > 0 { e.msg = fmt.Sprintf(msg, parts...) @@ -80,21 +76,6 @@ func (e *erf) SetMsg(msg string, parts ...any) SettableError { return e } -func (e *erf) SetField(field string, value any) SettableError { - e.meta[field] = value - return e -} - -func (e *erf) JSON() any { - m := e.meta - m["error"] = e.Msg() - return m -} - -func (e *erf) MarshalJSON() ([]byte, error) { - return json.Marshal(e.JSON()) -} - var _ UnwrappableError = new(wrapErr) type wrapErr struct { diff --git a/errorf_test.go b/errorf_test.go index 3847ca2..1a83ca0 100644 --- a/errorf_test.go +++ b/errorf_test.go @@ -2,7 +2,6 @@ package errors import ( "errors" - "encoding/json" "net/http" "testing" @@ -29,8 +28,8 @@ func (s *ErrorfTestSuite) TestNoWrap() { 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()) + s.Assert().Equal(exp, err.GetMsg()) + s.Assert().Equal(http.StatusTeapot, err.GetStatus()) } func (s *ErrorfTestSuite) TestWrapOne() { @@ -46,7 +45,7 @@ func (s *ErrorfTestSuite) TestWrapOne() { exp := "I'm a little teapot. Here is my handle." s.Assert().Equal(exp, we.Error()) - s.Assert().Equal(exp, we.Msg()) + s.Assert().Equal(exp, we.GetMsg()) s.Assert().Same(wrapped, we.Unwrap()) } @@ -64,7 +63,7 @@ func (s *ErrorfTestSuite) TestWrapTwo() { 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()) + s.Assert().Equal(exp, we.GetMsg()) unwrapped := we.Unwrap() s.Assert().Len(unwrapped, 2) s.Assert().Same(wrappedOne, unwrapped[0]) @@ -75,52 +74,31 @@ func (s *ErrorfTestSuite) TestSet() { var err SettableError = Errorf(http.StatusTeapot, "Unable to BREW") s.Assert().NotNil(err) - err.SetStatus(http.StatusTooEarly).SetMsg("It's only %dAM", 2) - s.Assert().Equal(http.StatusTooEarly, err.Status()) - s.Assert().Equal("It's only 2AM", err.Msg()) + 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.SetMsg("I am so great") - s.Assert().Equal("I am so great", err.Msg()) + 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.Status()) + s.Assert().Equal(http.StatusInternalServerError, err.GetStatus()) err = Errorf(5, "Hello") s.Assert().NotNil(err) - s.Assert().Equal(http.StatusInternalServerError, err.Status()) + s.Assert().Equal(http.StatusInternalServerError, err.GetStatus()) } func (s *ErrorfTestSuite) TestSetStatusOutsideRange() { var err SettableError = Errorf(http.StatusPaymentRequired, "Hello") s.Assert().NotNil(err) - err.SetStatus(10) - s.Assert().Equal(http.StatusPaymentRequired, err.Status()) - err.SetStatus(600) - s.Assert().Equal(http.StatusPaymentRequired, err.Status()) -} - -func (s *ErrorfTestSuite) TestJSON() { - var err SettableError = Errorf(http.StatusPaymentRequired, "Hello") - s.Assert().NotNil(err) - - m := err.JSON() - ma, _ := m.(map[string]any) - s.Assert().Equal("Hello", ma["error"]) - s.Assert().Nil(ma["number"]) - - j, _ := json.Marshal(err) - s.Assert().Equal(`{"error":"Hello"}`, string(j)) - - err.SetField("number",42) - m = err.JSON() - ma, _ = m.(map[string]any) - s.Assert().Equal(42, ma["number"]) - - j, _ = json.Marshal(err) - s.Assert().Equal(`{"error":"Hello","number":42}`, string(j)) + 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 5701b3b..da03a48 100644 --- a/interface.go +++ b/interface.go @@ -1,42 +1,27 @@ package errors -import "encoding/json" - // ResponsableError is an error that has information useful in an HTTP response. // The string returned by Error should be suitable for logging useful information // to assist debugging the error. -// -// Status should return an appropriate HTTP status. It must not return < 100 || >= 600. -// -// Msg should return a message suitable to display to the end user. If the message +// 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. -// -// JSON should return something that can be marshalled to JSON for the response body. -// It can be something as simple as map[string]string{"error":err.Msg()}. -// JSON's return value should be used by MarshalJSON() +// GetStatus should not return a value outside of the 100 - 599 range. type ResponsableError interface { - json.Marshaler Error() string - Status() int - Msg() string - JSON() any + GetStatus() int + GetMsg() string } // SettableError is a ResponsableError which can be modified after initial creation. -// -// The SetStatus method can set the status, while the SetMsg method can set user message. +// 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. -// -// SetField should add some metadata to the error, which will be included in the data returned by JSON() type SettableError interface { ResponsableError - SetStatus(int) SettableError - SetMsg(string, ...any) SettableError - SetField(string, any) SettableError + Status(int) SettableError + Msg(string, ...any) SettableError } // UnwrappableError allows a ResponsableError to wrap another error. diff --git a/new.go b/new.go index a4c7eef..73e1cf6 100644 --- a/new.go +++ b/new.go @@ -20,19 +20,9 @@ func NewBadRequest(format string, parts ...any) SettableError { return Errorf(http.StatusBadRequest, format, parts...) } -// A 401 error with the error message "Unauthorized" -func NewUnauthorized() SettableError { - return Errorf(http.StatusUnauthorized, "Unauthorized") -} - -// A 403 error with the error message "Forbidden" -func NewForbidden() SettableError { - return Errorf(http.StatusForbidden, "Forbidden") -} - // Represents a 500 error. For this error, the user error is preset to "Unknown Error". func NewInternalError(format string, parts ...any) SettableError { status := http.StatusInternalServerError msg := "Unknown Error" - return Errorf(status, format, parts...).SetMsg(msg) + return Errorf(status, format, parts...).Msg(msg) } diff --git a/new_test.go b/new_test.go index 71ef3c8..fc74651 100644 --- a/new_test.go +++ b/new_test.go @@ -18,54 +18,40 @@ type NewTestSuite struct { func (s *NewTestSuite) TestNotFound() { msg := "I can't see you" var err ResponsableError = NewNotFound(msg) - s.Assert().Error(err) - s.Assert().Equal(http.StatusNotFound, err.Status()) - s.Assert().Equal(msg, err.Msg()) + s.Assert().NotNil(err) + s.Assert().Equal(http.StatusNotFound, err.GetStatus()) + s.Assert().Equal(msg, err.GetMsg()) s.Assert().Equal(msg, err.Error()) } -func (s *NewTestSuite) TestUnauthorized() { - var err ResponsableError = NewUnauthorized() - s.Assert().Error(err) - s.Assert().Equal(http.StatusUnauthorized, err.Status()) - s.Assert().Equal("Unauthorized", err.Msg()) -} - -func (s *NewTestSuite) TestForbidden() { - var err ResponsableError = NewForbidden() - s.Assert().Error(err) - s.Assert().Equal(http.StatusForbidden, err.Status()) - s.Assert().Equal("Forbidden", err.Msg()) -} - func (s *NewTestSuite) TestNotFoundDefaultMsg() { msg := "Not Found" var err ResponsableError = NewNotFound("") - s.Assert().Error(err) - s.Assert().Equal(msg, err.Msg()) + s.Assert().NotNil(err) + s.Assert().Equal(msg, err.GetMsg()) s.Assert().Equal(msg, err.Error()) } func (s *NewTestSuite) TestBadRequest() { msg := "I can't see you" var err ResponsableError = NewBadRequest(msg) - s.Assert().Error(err) - s.Assert().Equal(http.StatusBadRequest, err.Status()) - s.Assert().Equal(msg, err.Msg()) + s.Assert().NotNil(err) + s.Assert().Equal(http.StatusBadRequest, err.GetStatus()) + s.Assert().Equal(msg, err.GetMsg()) s.Assert().Equal(msg, err.Error()) } func (s *NewTestSuite) TestBadRequestDefaultMsg() { msg := "Bad Request" var err ResponsableError = NewBadRequest("") - s.Assert().Error(err) - s.Assert().Equal(msg, err.Msg()) + s.Assert().NotNil(err) + s.Assert().Equal(msg, err.GetMsg()) s.Assert().Equal(msg, err.Error()) } func (s *NewTestSuite) TestInternal() { var err ResponsableError = NewInternalError("%d > %d", 42, 13) - s.Assert().Error(err) + s.Assert().NotNil(err) s.Assert().Equal("42 > 13", err.Error()) - s.Assert().Equal("Unknown Error", err.Msg()) + s.Assert().Equal("Unknown Error", err.GetMsg()) }