Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 18 additions & 33 deletions errors/errors.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,26 @@
package errors

type BadRequest struct {
type BaseError struct {
Message string
}

func (e *BadRequest) Error() string {
func (e *BaseError) Error() string {
return e.Message
}

type NotFound struct {
Message string
}

func (e *NotFound) Error() string {
return e.Message
}

type ServiceUnavailable struct {
Message string
}

func (e *ServiceUnavailable) Error() string {
return e.Message
}

type Forbidden struct {
Message string
}

func (e *Forbidden) Error() string {
return e.Message
}

type Unauthorized struct {
Message string
}

func (e *Unauthorized) Error() string {
return e.Message
}
type BadRequest struct{ BaseError }
type NotFound struct{ BaseError }
type ServiceUnavailable struct{ BaseError }
type Forbidden struct{ BaseError }
type Unauthorized struct{ BaseError }
type MethodNotAllowed struct{ BaseError }
type Conflict struct{ BaseError }
type Gone struct{ BaseError }
type UnsupportedMediaType struct{ BaseError }
type UnprocessableEntity struct{ BaseError }
type TooManyRequests struct{ BaseError }
type InternalServerError struct{ BaseError }
type BadGateway struct{ BaseError }
type GatewayTimeout struct{ BaseError }
type RequestTimeout struct{ BaseError }
type NotImplemented struct{ BaseError }
Comment on lines +11 to +26
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the concrete error structs from struct { Message string } to struct{ BaseError } is a breaking public API change: callers can no longer construct these errors with composite literals like errors.BadRequest{Message: "..."} (promoted fields cannot be set in struct literals). If this module is intended to be semver-stable, consider keeping Message as a direct field on each error type (even if Error() is shared), or add exported constructor helpers (e.g. NewBadRequest(msg string)) and update all documented usage accordingly, ideally in a major version bump.

Suggested change
type BadRequest struct{ BaseError }
type NotFound struct{ BaseError }
type ServiceUnavailable struct{ BaseError }
type Forbidden struct{ BaseError }
type Unauthorized struct{ BaseError }
type MethodNotAllowed struct{ BaseError }
type Conflict struct{ BaseError }
type Gone struct{ BaseError }
type UnsupportedMediaType struct{ BaseError }
type UnprocessableEntity struct{ BaseError }
type TooManyRequests struct{ BaseError }
type InternalServerError struct{ BaseError }
type BadGateway struct{ BaseError }
type GatewayTimeout struct{ BaseError }
type RequestTimeout struct{ BaseError }
type NotImplemented struct{ BaseError }
type BadRequest struct {
Message string
}
func (e *BadRequest) Error() string {
return e.Message
}
type NotFound struct {
Message string
}
func (e *NotFound) Error() string {
return e.Message
}
type ServiceUnavailable struct {
Message string
}
func (e *ServiceUnavailable) Error() string {
return e.Message
}
type Forbidden struct {
Message string
}
func (e *Forbidden) Error() string {
return e.Message
}
type Unauthorized struct {
Message string
}
func (e *Unauthorized) Error() string {
return e.Message
}
type MethodNotAllowed struct {
Message string
}
func (e *MethodNotAllowed) Error() string {
return e.Message
}
type Conflict struct {
Message string
}
func (e *Conflict) Error() string {
return e.Message
}
type Gone struct {
Message string
}
func (e *Gone) Error() string {
return e.Message
}
type UnsupportedMediaType struct {
Message string
}
func (e *UnsupportedMediaType) Error() string {
return e.Message
}
type UnprocessableEntity struct {
Message string
}
func (e *UnprocessableEntity) Error() string {
return e.Message
}
type TooManyRequests struct {
Message string
}
func (e *TooManyRequests) Error() string {
return e.Message
}
type InternalServerError struct {
Message string
}
func (e *InternalServerError) Error() string {
return e.Message
}
type BadGateway struct {
Message string
}
func (e *BadGateway) Error() string {
return e.Message
}
type GatewayTimeout struct {
Message string
}
func (e *GatewayTimeout) Error() string {
return e.Message
}
type RequestTimeout struct {
Message string
}
func (e *RequestTimeout) Error() string {
return e.Message
}
type NotImplemented struct {
Message string
}
func (e *NotImplemented) Error() string {
return e.Message
}

Copilot uses AI. Check for mistakes.
108 changes: 54 additions & 54 deletions middlewares/error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,67 +20,67 @@ func (e *ErrorHandler) Wrap(handler func(w http.ResponseWriter, r *http.Request)
var serviceUnavailable *serviceErrors.ServiceUnavailable
var forbiddenError *serviceErrors.Forbidden
var unauthorizedError *serviceErrors.Unauthorized
var methodNotAllowedError *serviceErrors.MethodNotAllowed
var conflictError *serviceErrors.Conflict
var goneError *serviceErrors.Gone
var unsupportedMediaTypeError *serviceErrors.UnsupportedMediaType
var unprocessableEntityError *serviceErrors.UnprocessableEntity
var tooManyRequestsError *serviceErrors.TooManyRequests
var internalServerError *serviceErrors.InternalServerError
var badGatewayError *serviceErrors.BadGateway
var gatewayTimeoutError *serviceErrors.GatewayTimeout
var requestTimeoutError *serviceErrors.RequestTimeout
var notImplementedError *serviceErrors.NotImplemented

err := handler(w, r)

if (errors.As(err, &notFoundError)) || (errors.Is(err, storage.ErrNotFound)) {
render.Status(r, http.StatusNotFound)
response := types.ErrorResponse{
Status: http.StatusText(http.StatusNotFound),
Error: err.Error(),
}
render.JSON(w, r, response)
return
}

if errors.As(err, &badRequestError) {
render.Status(r, http.StatusBadRequest)
response := types.ErrorResponse{
Status: http.StatusText(http.StatusBadRequest),
Error: err.Error(),
}
render.JSON(w, r, response)
return
}

if errors.As(err, &serviceUnavailable) {
render.Status(r, http.StatusServiceUnavailable)
response := types.ErrorResponse{
Status: http.StatusText(http.StatusServiceUnavailable),
Error: err.Error(),
}
render.JSON(w, r, response)
if err == nil {
return
}

if errors.As(err, &forbiddenError) {
render.Status(r, http.StatusForbidden)
response := types.ErrorResponse{
Status: http.StatusText(http.StatusForbidden),
Error: err.Error(),
}
render.JSON(w, r, response)
return
}
status := http.StatusInternalServerError
message := err.Error()

if errors.As(err, &unauthorizedError) {
render.Status(r, http.StatusUnauthorized)
response := types.ErrorResponse{
Status: http.StatusText(http.StatusUnauthorized),
Error: err.Error(),
}
render.JSON(w, r, response)
return
switch {
case errors.As(err, &notFoundError) || errors.Is(err, storage.ErrNotFound):
status = http.StatusNotFound
case errors.As(err, &badRequestError):
status = http.StatusBadRequest
case errors.As(err, &serviceUnavailable):
status = http.StatusServiceUnavailable
case errors.As(err, &forbiddenError):
status = http.StatusForbidden
case errors.As(err, &unauthorizedError):
status = http.StatusUnauthorized
case errors.As(err, &methodNotAllowedError):
status = http.StatusMethodNotAllowed
case errors.As(err, &conflictError):
status = http.StatusConflict
case errors.As(err, &goneError):
status = http.StatusGone
case errors.As(err, &unsupportedMediaTypeError):
status = http.StatusUnsupportedMediaType
case errors.As(err, &unprocessableEntityError):
status = http.StatusUnprocessableEntity
case errors.As(err, &tooManyRequestsError):
status = http.StatusTooManyRequests
case errors.As(err, &internalServerError):
status = http.StatusInternalServerError
case errors.As(err, &badGatewayError):
status = http.StatusBadGateway
case errors.As(err, &gatewayTimeoutError):
status = http.StatusGatewayTimeout
case errors.As(err, &requestTimeoutError):
status = http.StatusRequestTimeout
case errors.As(err, &notImplementedError):
status = http.StatusNotImplemented
default:
message = "encountered an unexpected server error: " + err.Error()
}
Comment on lines +40 to 78
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error-to-HTTP-status mapping is now a long switch with many errors.As checks, and it will keep growing as new error types are added. Consider making these service error types implement a small interface (e.g., StatusCode() int) and then resolving the status with a single errors.As to that interface; this centralizes the mapping in the error types and avoids touching this middleware for every new status.

Copilot uses AI. Check for mistakes.

if err != nil {
render.Status(r, http.StatusInternalServerError)
response := types.ErrorResponse{
Status: http.StatusText(http.StatusInternalServerError),
Error: "encountered an unexpected server error: " + err.Error(),
}
render.JSON(w, r, response)
return
}
render.Status(r, status)
render.JSON(w, r, types.ErrorResponse{
Status: http.StatusText(status),
Error: message,
})
}
}
Loading