2
0

errors.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package apierrors
  2. import (
  3. "context"
  4. "encoding/json"
  5. "net/http"
  6. "github.com/porter-dev/porter/api/server/shared/config"
  7. "github.com/porter-dev/porter/api/types"
  8. "github.com/porter-dev/porter/internal/models"
  9. "github.com/rs/zerolog"
  10. )
  11. type RequestError interface {
  12. Error() string
  13. ExternalError() string
  14. InternalError() string
  15. GetStatusCode() int
  16. }
  17. type ErrInternal struct {
  18. err error
  19. }
  20. func NewErrInternal(err error) RequestError {
  21. return &ErrInternal{err}
  22. }
  23. func (e *ErrInternal) Error() string {
  24. return e.err.Error()
  25. }
  26. func (e *ErrInternal) InternalError() string {
  27. return e.err.Error()
  28. }
  29. func (e *ErrInternal) ExternalError() string {
  30. return "An internal error occurred."
  31. }
  32. func (e *ErrInternal) GetStatusCode() int {
  33. return http.StatusInternalServerError
  34. }
  35. type ErrForbidden struct {
  36. err error
  37. }
  38. func NewErrForbidden(err error) RequestError {
  39. return &ErrForbidden{err}
  40. }
  41. func (e *ErrForbidden) Error() string {
  42. return e.err.Error()
  43. }
  44. func (e *ErrForbidden) InternalError() string {
  45. return e.err.Error()
  46. }
  47. func (e *ErrForbidden) ExternalError() string {
  48. return "Forbidden"
  49. }
  50. func (e *ErrForbidden) GetStatusCode() int {
  51. return http.StatusForbidden
  52. }
  53. // errors that should be passed directly, with no filter
  54. type ErrPassThroughToClient struct {
  55. err error
  56. statusCode int
  57. }
  58. func NewErrPassThroughToClient(err error, statusCode int) RequestError {
  59. return &ErrPassThroughToClient{err, statusCode}
  60. }
  61. func (e *ErrPassThroughToClient) Error() string {
  62. return e.err.Error()
  63. }
  64. func (e *ErrPassThroughToClient) InternalError() string {
  65. return e.err.Error()
  66. }
  67. func (e *ErrPassThroughToClient) ExternalError() string {
  68. return e.err.Error()
  69. }
  70. func (e *ErrPassThroughToClient) GetStatusCode() int {
  71. return e.statusCode
  72. }
  73. func HandleAPIError(
  74. config *config.Config,
  75. w http.ResponseWriter,
  76. r *http.Request,
  77. err RequestError,
  78. ) {
  79. // if the status code is internal server error, use alerter
  80. if err.GetStatusCode() == http.StatusInternalServerError && config.Alerter != nil {
  81. config.Alerter.SendAlert(r.Context(), err)
  82. }
  83. extErrorStr := err.ExternalError()
  84. // log the internal error
  85. event := config.Logger.Warn().
  86. Str("internal_error", err.InternalError()).
  87. Str("external_error", extErrorStr)
  88. addLoggingScopes(r.Context(), event)
  89. addLoggingRequestMeta(r, event)
  90. event.Msg("")
  91. // send the external error
  92. resp := &types.ExternalError{
  93. Error: extErrorStr,
  94. }
  95. // write the status code
  96. w.WriteHeader(err.GetStatusCode())
  97. writerErr := json.NewEncoder(w).Encode(resp)
  98. if writerErr != nil {
  99. event := config.Logger.Error().
  100. Err(writerErr)
  101. addLoggingScopes(r.Context(), event)
  102. addLoggingRequestMeta(r, event)
  103. event.Msg("")
  104. }
  105. return
  106. }
  107. func addLoggingScopes(ctx context.Context, event *zerolog.Event) {
  108. // case on the context values that exist, add them to event
  109. if userVal := ctx.Value(types.UserScope); userVal != nil {
  110. if userModel, ok := userVal.(*models.User); ok {
  111. event.Uint("user_id", userModel.ID)
  112. }
  113. }
  114. // if this is a project-scoped route, add various scopes
  115. if reqScopesVal := ctx.Value(types.RequestScopeCtxKey); reqScopesVal != nil {
  116. if reqScopes, ok := reqScopesVal.(map[types.PermissionScope]*types.RequestAction); ok {
  117. for key, scope := range reqScopes {
  118. if scope.Resource.Name != "" {
  119. event.Str(string(key), scope.Resource.Name)
  120. }
  121. if scope.Resource.UInt != 0 {
  122. event.Uint(string(key), scope.Resource.UInt)
  123. }
  124. }
  125. }
  126. }
  127. }
  128. func addLoggingRequestMeta(r *http.Request, event *zerolog.Event) {
  129. event.Str("method", r.Method)
  130. event.Str("url", r.URL.String())
  131. }