errors.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package errors
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. )
  7. const (
  8. UnknownCode = http.StatusInternalServerError
  9. UnknownReason = ""
  10. UnknownMessage = "internal error"
  11. )
  12. type Status struct {
  13. Code int32 `json:"code"`
  14. Reason string `json:"reason,omitempty"`
  15. Message string `json:"message"`
  16. Metadata map[string]string `json:"metadata,omitempty"`
  17. }
  18. // ApplicationError is the standard error type used to control HTTP responses.
  19. //
  20. // Code is expected to be an HTTP status code (e.g. 400/401/403/404/409/500).
  21. type ApplicationError struct {
  22. Status
  23. cause error
  24. }
  25. // Error is kept for backwards compatibility within this package.
  26. type Error = ApplicationError
  27. func (e *ApplicationError) Error() string {
  28. if e == nil {
  29. return "<nil>"
  30. }
  31. if e.cause == nil {
  32. return fmt.Sprintf("error: code=%d reason=%q message=%q metadata=%v", e.Code, e.Reason, e.Message, e.Metadata)
  33. }
  34. return fmt.Sprintf("error: code=%d reason=%q message=%q metadata=%v cause=%v", e.Code, e.Reason, e.Message, e.Metadata, e.cause)
  35. }
  36. // Unwrap provides compatibility for Go 1.13 error chains.
  37. func (e *ApplicationError) Unwrap() error { return e.cause }
  38. // Is matches each error in the chain with the target value.
  39. func (e *ApplicationError) Is(err error) bool {
  40. if se := new(ApplicationError); errors.As(err, &se) {
  41. return se.Code == e.Code && se.Reason == e.Reason
  42. }
  43. return false
  44. }
  45. // WithCause attaches the underlying cause of the error.
  46. func (e *ApplicationError) WithCause(cause error) *ApplicationError {
  47. err := Clone(e)
  48. err.cause = cause
  49. return err
  50. }
  51. // WithMetadata deep-copies the given metadata map.
  52. func (e *ApplicationError) WithMetadata(md map[string]string) *ApplicationError {
  53. err := Clone(e)
  54. if md == nil {
  55. err.Metadata = nil
  56. return err
  57. }
  58. err.Metadata = make(map[string]string, len(md))
  59. for k, v := range md {
  60. err.Metadata[k] = v
  61. }
  62. return err
  63. }
  64. // New returns an error object for the code, message.
  65. func New(code int, reason, message string) *ApplicationError {
  66. return &ApplicationError{
  67. Status: Status{
  68. Code: int32(code),
  69. Message: message,
  70. Reason: reason,
  71. },
  72. }
  73. }
  74. // Newf New(code fmt.Sprintf(format, a...))
  75. func Newf(code int, reason, format string, a ...any) *ApplicationError {
  76. return New(code, reason, fmt.Sprintf(format, a...))
  77. }
  78. // Errorf returns an error object for the code, message and error info.
  79. func Errorf(code int, reason, format string, a ...any) error {
  80. return New(code, reason, fmt.Sprintf(format, a...))
  81. }
  82. // Code returns the http code for an error.
  83. // It supports wrapped errors.
  84. func Code(err error) int {
  85. if err == nil {
  86. return http.StatusOK
  87. }
  88. return int(FromError(err).Code)
  89. }
  90. // Reason returns the reason for a particular error.
  91. // It supports wrapped errors.
  92. func Reason(err error) string {
  93. if err == nil {
  94. return UnknownReason
  95. }
  96. return FromError(err).Reason
  97. }
  98. // Message returns the message for a particular error.
  99. // It supports wrapped errors.
  100. func Message(err error) string {
  101. if err == nil {
  102. return ""
  103. }
  104. return FromError(err).Message
  105. }
  106. // Clone deep clone error to a new error.
  107. func Clone(err *ApplicationError) *ApplicationError {
  108. if err == nil {
  109. return nil
  110. }
  111. var metadata map[string]string
  112. if err.Metadata != nil {
  113. metadata = make(map[string]string, len(err.Metadata))
  114. for k, v := range err.Metadata {
  115. metadata[k] = v
  116. }
  117. }
  118. return &ApplicationError{
  119. cause: err.cause,
  120. Status: Status{
  121. Code: err.Code,
  122. Reason: err.Reason,
  123. Message: err.Message,
  124. Metadata: metadata,
  125. },
  126. }
  127. }
  128. // FromError tries to convert an error to *ApplicationError.
  129. // It supports wrapped errors.
  130. func FromError(err error) *ApplicationError {
  131. if err == nil {
  132. return nil
  133. }
  134. if se := new(ApplicationError); errors.As(err, &se) {
  135. return se
  136. }
  137. // Fall back to a generic internal error.
  138. return New(UnknownCode, UnknownReason, UnknownMessage).WithCause(err)
  139. }