errors.go 4.0 KB

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