errors.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package api
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "github.com/go-playground/validator/v10"
  6. "gorm.io/gorm"
  7. )
  8. const (
  9. appErrDataWrite = "data write error"
  10. appErrDataRead = "data read error"
  11. appErrFormDecoding = "could not process JSON body"
  12. appErrReadNotFound = "could not find requested object"
  13. )
  14. // HTTPError is the object returned when the API encounters an error: this
  15. // gets marshaled into JSON
  16. type HTTPError struct {
  17. Code ErrorCode `json:"code"`
  18. Errors []string `json:"errors"`
  19. }
  20. // ErrorCode is a custom Porter error code, useful for frontend messages
  21. type ErrorCode int64
  22. // ------------------------ Error helper functions ------------------------ //
  23. // sendExternalError marshals an HTTPError into JSON: this function will return an error if
  24. // a marshaling error occurs, but only after the internal error header has been sent to the
  25. // client.
  26. //
  27. // It then logs it via the app.logger and sends a formatted error to the client.
  28. func (app *App) sendExternalError(
  29. err error,
  30. code int,
  31. errExt HTTPError,
  32. w http.ResponseWriter,
  33. ) (intErr error) {
  34. respBytes, newErr := json.Marshal(errExt)
  35. if newErr != nil {
  36. app.handleErrorInternal(newErr, w)
  37. return newErr
  38. }
  39. respBody := string(respBytes)
  40. app.logger.Warn().Err(err).
  41. Str("errExt", respBody).
  42. Msg("")
  43. w.WriteHeader(code)
  44. w.Write(respBytes)
  45. return nil
  46. }
  47. // handleErrorFormDecoding handles an error in decoding process from JSON to the
  48. // construction of a Form model, and the conversion between a form model and a
  49. // gorm.Model.
  50. func (app *App) handleErrorFormDecoding(err error, code ErrorCode, w http.ResponseWriter) {
  51. errExt := HTTPError{
  52. Code: code,
  53. Errors: []string{appErrFormDecoding},
  54. }
  55. app.sendExternalError(err, http.StatusBadRequest, errExt, w)
  56. }
  57. // handleErrorFormValidation handles an error in the validation of form fields, and
  58. // sends a descriptive method about the incorrect fields to the client.
  59. func (app *App) handleErrorFormValidation(err error, code ErrorCode, w http.ResponseWriter) {
  60. // translate all validator errors
  61. errs := err.(validator.ValidationErrors)
  62. res := make([]string, 0)
  63. for _, field := range errs {
  64. valErr := field.Tag() + " validation failed"
  65. res = append(res, valErr)
  66. }
  67. errExt := HTTPError{
  68. Code: code,
  69. Errors: res,
  70. }
  71. app.sendExternalError(err, http.StatusUnprocessableEntity, errExt, w)
  72. }
  73. // handleErrorRead handles an error in reading a record from the DB. If the record is
  74. // not found, the error message is more descriptive; otherwise, a generic dataRead
  75. // error is sent.
  76. func (app *App) handleErrorRead(err error, code ErrorCode, w http.ResponseWriter) {
  77. // first check if the error is RecordNotFound -- send a more descriptive
  78. // message if that is the case
  79. if err == gorm.ErrRecordNotFound {
  80. errExt := HTTPError{
  81. Code: code,
  82. Errors: []string{appErrReadNotFound},
  83. }
  84. app.sendExternalError(err, http.StatusNotFound, errExt, w)
  85. return
  86. }
  87. app.handleErrorDataRead(err, code, w)
  88. }
  89. // handleErrorDataWrite handles a database write error due to either a connection
  90. // error with the database or failure to write that wasn't caught by the validators
  91. func (app *App) handleErrorDataWrite(err error, code ErrorCode, w http.ResponseWriter) {
  92. errExt := HTTPError{
  93. Code: code,
  94. Errors: []string{appErrDataWrite},
  95. }
  96. app.sendExternalError(err, http.StatusInternalServerError, errExt, w)
  97. }
  98. // handleErrorDataRead handles a database read error due to an internal error, such as
  99. // the database connection or gorm internals
  100. func (app *App) handleErrorDataRead(err error, code ErrorCode, w http.ResponseWriter) {
  101. errExt := HTTPError{
  102. Code: code,
  103. Errors: []string{appErrDataRead},
  104. }
  105. app.sendExternalError(err, http.StatusInternalServerError, errExt, w)
  106. }
  107. // handleErrorInternalError is a catch-all for internal errors that occur during the
  108. // processing of a request
  109. func (app *App) handleErrorInternal(err error, w http.ResponseWriter) {
  110. app.logger.Warn().Err(err).Msg("")
  111. w.WriteHeader(http.StatusInternalServerError)
  112. w.Write([]byte(`{"error": "Internal server error"}`))
  113. }