errors.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. // ErrorUpgradeWebsocket describes an error while upgrading http to a websocket endpoint.
  18. ErrorUpgradeWebsocket = HTTPError{
  19. Code: 500,
  20. Errors: []string{
  21. "could not upgrade to websocket",
  22. },
  23. }
  24. // ErrorDataWrite describes an error in writing to the database
  25. ErrorDataWrite = HTTPError{
  26. Code: 500,
  27. Errors: []string{
  28. "could not write to database",
  29. },
  30. }
  31. // ErrorWebsocketWrite describes an error in writing to websocket connection
  32. ErrorWebsocketWrite = HTTPError{
  33. Code: 500,
  34. Errors: []string{
  35. "could not write data via websocket",
  36. },
  37. }
  38. // ErrorDataRead describes an error when reading from the database
  39. ErrorDataRead = HTTPError{
  40. Code: 500,
  41. Errors: []string{
  42. "could not read from database",
  43. },
  44. }
  45. // ErrorInternal describes a generic internal server error
  46. ErrorInternal = HTTPError{
  47. Code: 500,
  48. Errors: []string{
  49. "internal server error",
  50. },
  51. }
  52. )
  53. // ------------------------ Error helper functions ------------------------ //
  54. // sendExternalError marshals an HTTPError into JSON: this function will return an error if
  55. // a marshaling error occurs, but only after the internal error header has been sent to the
  56. // client.
  57. //
  58. // It then logs it via the app.logger and sends a formatted error to the client.
  59. func (app *App) sendExternalError(
  60. err error,
  61. code int,
  62. errExt HTTPError,
  63. w http.ResponseWriter,
  64. ) (intErr error) {
  65. respBytes, _ := json.Marshal(errExt)
  66. respBody := string(respBytes)
  67. app.logger.Warn().Err(err).
  68. Str("errExt", respBody).
  69. Msg("")
  70. w.WriteHeader(code)
  71. w.Write(respBytes)
  72. return nil
  73. }
  74. // handleErrorFormDecoding handles an error in decoding process from JSON to the
  75. // construction of a Form model, and the conversion between a form model and a
  76. // gorm.Model.
  77. func (app *App) handleErrorFormDecoding(err error, code ErrorCode, w http.ResponseWriter) {
  78. errExt := HTTPError{
  79. Code: code,
  80. Errors: []string{"could not process request"},
  81. }
  82. app.sendExternalError(err, http.StatusBadRequest, errExt, w)
  83. }
  84. // handleErrorFormValidation handles an error in the validation of form fields, and
  85. // sends a descriptive method about the incorrect fields to the client.
  86. func (app *App) handleErrorFormValidation(err error, code ErrorCode, w http.ResponseWriter) {
  87. // translate all validator errors
  88. errs := err.(validator.ValidationErrors)
  89. res := make([]string, 0)
  90. for _, field := range errs {
  91. valErr := field.Tag() + " validation failed"
  92. res = append(res, valErr)
  93. }
  94. errExt := HTTPError{
  95. Code: code,
  96. Errors: res,
  97. }
  98. app.sendExternalError(err, http.StatusUnprocessableEntity, errExt, w)
  99. }
  100. // handleErrorRead handles an error in reading a record from the DB. If the record is
  101. // not found, the error message is more descriptive; otherwise, a generic dataRead
  102. // error is sent.
  103. func (app *App) handleErrorRead(err error, code ErrorCode, w http.ResponseWriter) {
  104. // first check if the error is RecordNotFound -- send a more descriptive
  105. // message if that is the case
  106. if err == gorm.ErrRecordNotFound {
  107. errExt := HTTPError{
  108. Code: code,
  109. Errors: []string{"could not find requested object"},
  110. }
  111. app.sendExternalError(err, http.StatusNotFound, errExt, w)
  112. return
  113. }
  114. app.handleErrorDataRead(err, w)
  115. }
  116. // handleErrorDataWrite handles a database write error due to either a connection
  117. // error with the database or failure to write that wasn't caught by the validators
  118. func (app *App) handleErrorDataWrite(err error, w http.ResponseWriter) {
  119. app.sendExternalError(err, http.StatusInternalServerError, ErrorDataWrite, w)
  120. }
  121. // handleErrorWebsocketWrite handles an error from websocket.WriteMessage
  122. func (app *App) handleErrorWebsocketWrite(err error, w http.ResponseWriter) {
  123. app.sendExternalError(err, http.StatusInternalServerError, ErrorWebsocketWrite, w)
  124. }
  125. // handleErrorUpgradeWebsocket handles error in upgrading a http endpoint to websocket conn
  126. func (app *App) handleErrorUpgradeWebsocket(err error, w http.ResponseWriter) {
  127. app.sendExternalError(err, http.StatusInternalServerError, ErrorUpgradeWebsocket, w)
  128. }
  129. // handleErrorDataRead handles a database read error due to an internal error, such as
  130. // the database connection or gorm internals
  131. func (app *App) handleErrorDataRead(err error, w http.ResponseWriter) {
  132. app.sendExternalError(err, http.StatusInternalServerError, ErrorDataRead, w)
  133. }
  134. // handleErrorInternalError is a catch-all for internal errors that occur during the
  135. // processing of a request
  136. func (app *App) handleErrorInternal(err error, w http.ResponseWriter) {
  137. app.sendExternalError(err, http.StatusInternalServerError, ErrorInternal, w)
  138. }