refactor: api - error handling refactor
This commit is contained in:
parent
30be8bebc0
commit
a8b8ffea58
|
@ -16,6 +16,7 @@ type apiHTTPHandler struct {
|
||||||
versionSvc *app.VersionService
|
versionSvc *app.VersionService
|
||||||
serverSvc *app.ServerService
|
serverSvc *app.ServerService
|
||||||
tribeSvc *app.TribeService
|
tribeSvc *app.TribeService
|
||||||
|
errorRenderer apiErrorRenderer
|
||||||
openAPISchema func() (*openapi3.T, error)
|
openAPISchema func() (*openapi3.T, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,31 +74,23 @@ func NewAPIHTTPHandler(
|
||||||
h.tribeMiddleware,
|
h.tribeMiddleware,
|
||||||
},
|
},
|
||||||
ErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
|
ErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
h.errorRenderer.render(w, r, err)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *apiHTTPHandler) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
func (h *apiHTTPHandler) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||||
apiErrorRenderer{
|
h.errorRenderer.render(w, r, apiError{
|
||||||
errors: []error{
|
status: http.StatusNotFound,
|
||||||
apiError{
|
code: "route-not-found",
|
||||||
status: http.StatusNotFound,
|
message: "route not found",
|
||||||
code: "route-not-found",
|
})
|
||||||
message: "route not found",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}.render(w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *apiHTTPHandler) handleMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
func (h *apiHTTPHandler) handleMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||||
apiErrorRenderer{
|
h.errorRenderer.render(w, r, apiError{
|
||||||
errors: []error{
|
status: http.StatusMethodNotAllowed,
|
||||||
apiError{
|
code: "method-not-allowed",
|
||||||
status: http.StatusMethodNotAllowed,
|
message: "method not allowed",
|
||||||
code: "method-not-allowed",
|
})
|
||||||
message: "method not allowed",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}.render(w, r)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,12 +71,20 @@ type errorPathSegment struct {
|
||||||
index int // index may be <0 and this means that it is unset
|
index int // index may be <0 and this means that it is unset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type errorPathFormatter func(segments []errorPathSegment) []string
|
||||||
|
|
||||||
type apiErrorRenderer struct {
|
type apiErrorRenderer struct {
|
||||||
errors []error
|
// errorPathFormatter allows to override the default path formatter
|
||||||
// formatErrorPath allows to override the default path formatter
|
|
||||||
// for domain.ValidationError and domain.SliceElementValidationError.
|
// for domain.ValidationError and domain.SliceElementValidationError.
|
||||||
// If formatErrorPath returns an empty slice, an internal server error is rendered.
|
// If errorPathFormatter returns an empty slice, an internal server error is rendered.
|
||||||
formatErrorPath func(segments []errorPathSegment) []string
|
errorPathFormatter errorPathFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re apiErrorRenderer) withErrorPathFormatter(formatter errorPathFormatter) apiErrorRenderer {
|
||||||
|
// this assignment is done on purpose
|
||||||
|
//nolint:revive
|
||||||
|
re.errorPathFormatter = formatter
|
||||||
|
return re
|
||||||
}
|
}
|
||||||
|
|
||||||
var errAPIInternalServerError = apiError{
|
var errAPIInternalServerError = apiError{
|
||||||
|
@ -85,10 +93,14 @@ var errAPIInternalServerError = apiError{
|
||||||
message: "internal server error",
|
message: "internal server error",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request) {
|
func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request, errs ...error) {
|
||||||
errs := make(apiErrors, 0, len(re.errors))
|
apiErrs := make(apiErrors, 0, len(errs))
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for _, err := range re.errors {
|
|
||||||
var apiErr apiError
|
var apiErr apiError
|
||||||
var domainErr domain.Error
|
var domainErr domain.Error
|
||||||
var paramFormatErr *apimodel.InvalidParamFormatError
|
var paramFormatErr *apimodel.InvalidParamFormatError
|
||||||
|
@ -103,16 +115,16 @@ func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request) {
|
||||||
apiErr = errAPIInternalServerError
|
apiErr = errAPIInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = append(errs, apiErr)
|
apiErrs = append(apiErrs, apiErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSON(w, r, errs.status(), errs.toResponse())
|
renderJSON(w, r, apiErrs.status(), apiErrs.toResponse())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (re apiErrorRenderer) invalidParamFormatErrorToAPIError(
|
func (re apiErrorRenderer) invalidParamFormatErrorToAPIError(
|
||||||
paramFormatErr *apimodel.InvalidParamFormatError,
|
paramFormatErr *apimodel.InvalidParamFormatError,
|
||||||
) apiError {
|
) apiError {
|
||||||
var location string
|
location := "$unknown"
|
||||||
|
|
||||||
switch paramFormatErr.Location {
|
switch paramFormatErr.Location {
|
||||||
case runtime.ParamLocationPath:
|
case runtime.ParamLocationPath:
|
||||||
|
@ -124,9 +136,7 @@ func (re apiErrorRenderer) invalidParamFormatErrorToAPIError(
|
||||||
case runtime.ParamLocationCookie:
|
case runtime.ParamLocationCookie:
|
||||||
location = "$cookie"
|
location = "$cookie"
|
||||||
case runtime.ParamLocationUndefined:
|
case runtime.ParamLocationUndefined:
|
||||||
fallthrough
|
// do nothing
|
||||||
default:
|
|
||||||
location = "$unknown"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiError{
|
return apiError{
|
||||||
|
@ -171,11 +181,11 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
|
||||||
var path []string
|
var path []string
|
||||||
|
|
||||||
if len(pathSegments) > 0 {
|
if len(pathSegments) > 0 {
|
||||||
if re.formatErrorPath == nil {
|
if re.errorPathFormatter == nil {
|
||||||
return errAPIInternalServerError
|
return errAPIInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
path = re.formatErrorPath(pathSegments)
|
path = re.errorPathFormatter(pathSegments)
|
||||||
|
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return errAPIInternalServerError
|
return errAPIInternalServerError
|
||||||
|
@ -193,7 +203,7 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiError{
|
return apiError{
|
||||||
status: errorTypeToStatusCode(domainErr.Type()),
|
status: re.domainErrorTypeToStatusCode(domainErr.Type()),
|
||||||
code: domainErr.Code(),
|
code: domainErr.Code(),
|
||||||
path: path,
|
path: path,
|
||||||
params: cloned,
|
params: cloned,
|
||||||
|
@ -201,7 +211,7 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorTypeToStatusCode(code domain.ErrorType) int {
|
func (re apiErrorRenderer) domainErrorTypeToStatusCode(code domain.ErrorType) int {
|
||||||
switch code {
|
switch code {
|
||||||
case domain.ErrorTypeIncorrectInput:
|
case domain.ErrorTypeIncorrectInput:
|
||||||
return http.StatusBadRequest
|
return http.StatusBadRequest
|
||||||
|
|
|
@ -19,12 +19,12 @@ func (h *apiHTTPHandler) ListServers(
|
||||||
domainParams := domain.NewListServersParams()
|
domainParams := domain.NewListServersParams()
|
||||||
|
|
||||||
if err := domainParams.SetSort([]domain.ServerSort{domain.ServerSortOpenDESC, domain.ServerSortKeyASC}); err != nil {
|
if err := domainParams.SetSort([]domain.ServerSort{domain.ServerSortOpenDESC, domain.ServerSortKeyASC}); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListServersErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := domainParams.SetVersionCodes([]string{versionCode}); err != nil {
|
if err := domainParams.SetVersionCodes([]string{versionCode}); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListServersErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,28 +33,28 @@ func (h *apiHTTPHandler) ListServers(
|
||||||
Value: *params.Open,
|
Value: *params.Open,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListServersErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Limit != nil {
|
if params.Limit != nil {
|
||||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListServersErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Cursor != nil {
|
if params.Cursor != nil {
|
||||||
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListServersErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.serverSvc.List(r.Context(), domainParams)
|
res, err := h.serverSvc.List(r.Context(), domainParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
h.errorRenderer.render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ func (h *apiHTTPHandler) serverMiddleware(next http.Handler) http.Handler {
|
||||||
routeCtx.URLParams.Values[serverKeyIdx],
|
routeCtx.URLParams.Values[serverKeyIdx],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
h.errorRenderer.render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ func serverFromContext(ctx context.Context) (domain.Server, bool) {
|
||||||
return s, ok
|
return s, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatListServersParamsErrorPath(segments []errorPathSegment) []string {
|
func formatListServersErrorPath(segments []errorPathSegment) []string {
|
||||||
if segments[0].model != "ListServersParams" {
|
if segments[0].model != "ListServersParams" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package port
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -23,25 +22,25 @@ func (h *apiHTTPHandler) ListTribes(
|
||||||
domainParams := domain.NewListTribesParams()
|
domainParams := domain.NewListTribesParams()
|
||||||
|
|
||||||
if err := domainParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
|
if err := domainParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Sort != nil {
|
if params.Sort != nil {
|
||||||
if err := domainParams.PrependSortString(*params.Sort); err != nil {
|
if err := domainParams.PrependSortString(*params.Sort); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Tag != nil {
|
if params.Tag != nil {
|
||||||
if err := domainParams.SetTags(*params.Tag); err != nil {
|
if err := domainParams.SetTags(*params.Tag); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,28 +50,28 @@ func (h *apiHTTPHandler) ListTribes(
|
||||||
Value: *params.Deleted,
|
Value: *params.Deleted,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Limit != nil {
|
if params.Limit != nil {
|
||||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Cursor != nil {
|
if params.Cursor != nil {
|
||||||
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.tribeSvc.List(r.Context(), domainParams)
|
res, err := h.tribeSvc.List(r.Context(), domainParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
h.errorRenderer.render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +112,7 @@ func (h *apiHTTPHandler) tribeMiddleware(next http.Handler) http.Handler {
|
||||||
server.Key(),
|
server.Key(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
h.errorRenderer.withErrorPathFormatter(formatGetTribeErrorPath).render(w, r, err)
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatGetTribeErrorPath}.render(w, r)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +131,7 @@ func tribeFromContext(ctx context.Context) (domain.Tribe, bool) {
|
||||||
return t, ok
|
return t, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatListTribesParamsErrorPath(segments []errorPathSegment) []string {
|
func formatListTribesErrorPath(segments []errorPathSegment) []string {
|
||||||
if segments[0].model != "ListTribesParams" {
|
if segments[0].model != "ListTribesParams" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -163,11 +161,7 @@ func formatGetTribeErrorPath(segments []errorPathSegment) []string {
|
||||||
|
|
||||||
switch segments[0].field {
|
switch segments[0].field {
|
||||||
case "ids":
|
case "ids":
|
||||||
path := []string{"$path", "ids"}
|
return []string{"$path", "tribeId"}
|
||||||
if segments[0].index >= 0 {
|
|
||||||
path = append(path, strconv.Itoa(segments[0].index))
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,21 @@ func (h *apiHTTPHandler) ListVersions(w http.ResponseWriter, r *http.Request, pa
|
||||||
|
|
||||||
if params.Limit != nil {
|
if params.Limit != nil {
|
||||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListVersionsParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListVersionsErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Cursor != nil {
|
if params.Cursor != nil {
|
||||||
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListVersionsParamsErrorPath}.render(w, r)
|
h.errorRenderer.withErrorPathFormatter(formatListVersionsErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.versionSvc.List(r.Context(), domainParams)
|
res, err := h.versionSvc.List(r.Context(), domainParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
h.errorRenderer.render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ func (h *apiHTTPHandler) versionMiddleware(next http.Handler) http.Handler {
|
||||||
|
|
||||||
version, err := h.versionSvc.Get(ctx, routeCtx.URLParams.Values[idx])
|
version, err := h.versionSvc.Get(ctx, routeCtx.URLParams.Values[idx])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
h.errorRenderer.render(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ func versionFromContext(ctx context.Context) (domain.Version, bool) {
|
||||||
return v, ok
|
return v, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatListVersionsParamsErrorPath(segments []errorPathSegment) []string {
|
func formatListVersionsErrorPath(segments []errorPathSegment) []string {
|
||||||
if segments[0].model != "ListVersionsParams" {
|
if segments[0].model != "ListVersionsParams" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue