errors.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "github.com/go-playground/validator/v10"
  7. )
  8. const (
  9. appErrDataWrite = "data write error"
  10. appErrFormDecoding = "could not process JSON body"
  11. )
  12. // HTTPError is the object returned when the API encounters an error: this
  13. // gets marshaled into JSON
  14. type HTTPError struct {
  15. Code ErrorCode `json:"code"`
  16. Errors []string `json:"errors"`
  17. }
  18. // ErrorCode is a custom Porter error code, useful for frontend messages
  19. type ErrorCode int64
  20. // Enumeration of API error codes, represented as int64
  21. const (
  22. ErrUserDecode ErrorCode = iota
  23. ErrUserValidateFields
  24. ErrUserDataWrite
  25. )
  26. // ------------------------ Error helper functions ------------------------ //
  27. // sendExternalError marshals an HTTPError into JSON: this function will return an error if
  28. // a marshaling error occurs, but only after the internal error header has been sent to the
  29. // client.
  30. //
  31. // It then logs it via the app.logger and sends a formatted error to the client.
  32. func (app *App) sendExternalError(err error, errExt HTTPError, w http.ResponseWriter) (intErr error) {
  33. respBytes, newErr := json.Marshal(errExt)
  34. if newErr != nil {
  35. app.handleErrorInternalError(newErr, w)
  36. return newErr
  37. }
  38. respBody := string(respBytes)
  39. app.logger.Warn().Err(err).
  40. Str("errExt", respBody).
  41. Msg("")
  42. fmt.Fprintf(w, respBody)
  43. return nil
  44. }
  45. // handleErrorFormDecoding handles an error in decoding process from JSON to the
  46. // construction of a Form model, and the conversion between a form model and a
  47. // gorm.Model.
  48. func (app *App) handleErrorFormDecoding(err error, code ErrorCode, w http.ResponseWriter) {
  49. errExt := HTTPError{
  50. Code: code,
  51. Errors: []string{appErrFormDecoding},
  52. }
  53. intErr := app.sendExternalError(err, errExt, w)
  54. if intErr == nil {
  55. w.WriteHeader(http.StatusUnprocessableEntity)
  56. }
  57. }
  58. // handleErrorFormValidation handles an error in the validation of form fields, and
  59. // sends a descriptive method about the incorrect fields to the client.
  60. func (app *App) handleErrorFormValidation(err error, code ErrorCode, w http.ResponseWriter) {
  61. // translate all validator errors
  62. errs := err.(validator.ValidationErrors)
  63. res := make([]string, 0)
  64. for _, field := range errs {
  65. valErr := field.Tag() + " validation failed"
  66. res = append(res, valErr)
  67. }
  68. errExt := HTTPError{
  69. Code: code,
  70. Errors: res,
  71. }
  72. intErr := app.sendExternalError(err, errExt, w)
  73. if intErr == nil {
  74. w.WriteHeader(http.StatusUnprocessableEntity)
  75. }
  76. }
  77. // handleErrorDataWrite handles a database write error
  78. func (app *App) handleErrorDataWrite(err error, code ErrorCode, w http.ResponseWriter) {
  79. errExt := HTTPError{
  80. Code: code,
  81. Errors: []string{appErrDataWrite},
  82. }
  83. intErr := app.sendExternalError(err, errExt, w)
  84. if intErr == nil {
  85. w.WriteHeader(http.StatusUnprocessableEntity)
  86. }
  87. }
  88. // handleErrorInternalError is a catch-all for internal errors that occur during the
  89. // processing of a request
  90. func (app *App) handleErrorInternalError(err error, w http.ResponseWriter) {
  91. app.logger.Warn().Err(err).Msg("")
  92. w.WriteHeader(http.StatusInternalServerError)
  93. fmt.Fprintf(w, `{"error": "Internal server error"}`)
  94. }