errors.go 4.4 KB

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