errors.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. extErrorStr := err.ExternalError()
  80. // log the internal error
  81. event := config.Logger.Warn().
  82. Str("internal_error", err.InternalError()).
  83. Str("external_error", extErrorStr)
  84. data := addLoggingScopes(r.Context(), event)
  85. addLoggingRequestMeta(r, event)
  86. event.Send()
  87. // if the status code is internal server error, use alerter
  88. if err.GetStatusCode() == http.StatusInternalServerError && config.Alerter != nil {
  89. data["method"] = r.Method
  90. data["url"] = r.URL.String()
  91. config.Alerter.SendAlert(r.Context(), err, data)
  92. }
  93. // send the external error
  94. resp := &types.ExternalError{
  95. Error: extErrorStr,
  96. }
  97. // write the status code
  98. w.WriteHeader(err.GetStatusCode())
  99. writerErr := json.NewEncoder(w).Encode(resp)
  100. if writerErr != nil {
  101. event := config.Logger.Error().
  102. Err(writerErr)
  103. addLoggingScopes(r.Context(), event)
  104. addLoggingRequestMeta(r, event)
  105. event.Send()
  106. }
  107. return
  108. }
  109. func addLoggingScopes(ctx context.Context, event *zerolog.Event) map[string]interface{} {
  110. res := make(map[string]interface{})
  111. // case on the context values that exist, add them to event
  112. if userVal := ctx.Value(types.UserScope); userVal != nil {
  113. if userModel, ok := userVal.(*models.User); ok {
  114. event.Uint("user_id", userModel.ID)
  115. res["user_id"] = userModel.ID
  116. }
  117. }
  118. // if this is a project-scoped route, add various scopes
  119. if reqScopesVal := ctx.Value(types.RequestScopeCtxKey); reqScopesVal != nil {
  120. if reqScopes, ok := reqScopesVal.(map[types.PermissionScope]*types.RequestAction); ok {
  121. for key, scope := range reqScopes {
  122. if scope.Resource.Name != "" {
  123. event.Str(string(key), scope.Resource.Name)
  124. res[string(key)] = scope.Resource.Name
  125. }
  126. if scope.Resource.UInt != 0 {
  127. event.Uint(string(key), scope.Resource.UInt)
  128. res[string(key)] = scope.Resource.UInt
  129. }
  130. }
  131. }
  132. }
  133. return res
  134. }
  135. func addLoggingRequestMeta(r *http.Request, event *zerolog.Event) {
  136. event.Str("method", r.Method)
  137. event.Str("url", r.URL.String())
  138. }