package router import ( "encoding/json" "errors" "fmt" "net/http" "allaboutapps.dev/aw/go-starter/internal/api/httperrors" "allaboutapps.dev/aw/go-starter/internal/config" "allaboutapps.dev/aw/go-starter/internal/types" "allaboutapps.dev/aw/go-starter/internal/util" "github.com/go-openapi/swag" "github.com/labstack/echo/v4" "github.com/timewasted/go-accept-headers" ) var ( DefaultHTTPErrorHandlerConfig = HTTPErrorHandlerConfig{ HideInternalServerErrorDetails: true, } ) type HTTPErrorHandlerConfig struct { HideInternalServerErrorDetails bool } func HTTPErrorHandler() echo.HTTPErrorHandler { return HTTPErrorHandlerWithConfig(DefaultHTTPErrorHandlerConfig) } func HTTPErrorHandlerWithConfig(config HTTPErrorHandlerConfig) echo.HTTPErrorHandler { return func(err error, c echo.Context) { var code int64 var resultErr error var httpError *httperrors.HTTPError var httpValidationError *httperrors.HTTPValidationError var echoHTTPError *echo.HTTPError switch { case errors.As(err, &httpError): code = *httpError.Code resultErr = httpError if code == http.StatusInternalServerError && config.HideInternalServerErrorDetails { if httpError.Internal == nil { //nolint:errorlint httpError.Internal = fmt.Errorf("internal error: %s", httpError) } httpError.Title = swag.String(http.StatusText(http.StatusInternalServerError)) } case errors.As(err, &httpValidationError): code = *httpValidationError.Code resultErr = httpValidationError if code == http.StatusInternalServerError && config.HideInternalServerErrorDetails { if httpValidationError.Internal == nil { //nolint:errorlint httpValidationError.Internal = fmt.Errorf("internal error: %s", httpValidationError) } httpValidationError.Title = swag.String(http.StatusText(http.StatusInternalServerError)) } case errors.As(err, &echoHTTPError): code = int64(echoHTTPError.Code) //nolint:nestif if code == http.StatusInternalServerError && config.HideInternalServerErrorDetails { if echoHTTPError.Internal == nil { //nolint:errorlint echoHTTPError.Internal = fmt.Errorf("internal error: %s", echoHTTPError) } resultErr = &httperrors.HTTPError{ PublicHTTPError: types.PublicHTTPError{ Code: swag.Int64(int64(echoHTTPError.Code)), Title: swag.String(http.StatusText(http.StatusInternalServerError)), Type: types.PublicHTTPErrorTypeGeneric.Pointer(), }, Internal: echoHTTPError.Internal, } } else { msg, ok := echoHTTPError.Message.(string) if !ok { if m, errr := json.Marshal(msg); err == nil { msg = string(m) } else { msg = fmt.Sprintf("failed to marshal HTTP error message: %v", errr) } } resultErr = &httperrors.HTTPError{ PublicHTTPError: types.PublicHTTPError{ Code: swag.Int64(int64(echoHTTPError.Code)), Title: &msg, Type: types.PublicHTTPErrorTypeGeneric.Pointer(), }, Internal: echoHTTPError.Internal, } } default: code = http.StatusInternalServerError if config.HideInternalServerErrorDetails { resultErr = &httperrors.HTTPError{ PublicHTTPError: types.PublicHTTPError{ Code: swag.Int64(int64(http.StatusInternalServerError)), Title: swag.String(http.StatusText(http.StatusInternalServerError)), Type: types.PublicHTTPErrorTypeGeneric.Pointer(), }, Internal: err, } } else { resultErr = &httperrors.HTTPError{ PublicHTTPError: types.PublicHTTPError{ Code: swag.Int64(int64(http.StatusInternalServerError)), Title: swag.String(err.Error()), Type: types.PublicHTTPErrorTypeGeneric.Pointer(), }, } } } if !c.Response().Committed { if c.Request().Method == http.MethodHead { err = c.NoContent(int(code)) } else { err = c.JSON(int(code), resultErr) } if err != nil { util.LogFromEchoContext(c).Warn().Err(err).AnErr("http_err", err).Msg("Failed to handle HTTP error") } } } } func NotFoundHandler(config config.Server) func(c echo.Context) error { return func(c echo.Context) error { accepted := accept.Parse(c.Request().Header.Get(echo.HeaderAccept)) if accepted.Accepts(echo.MIMETextHTML) { return c.HTML(http.StatusNotFound, fmt.Sprintf(`

Page Not Found

The page you are looking for does not exist. Did you mean to visit %s?

`, config.Frontend.BaseURL, config.Frontend.BaseURL)) } return echo.ErrNotFound } }