errors.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package ierrors
  2. import (
  3. "fmt"
  4. "net/http"
  5. "runtime"
  6. "strings"
  7. )
  8. type Option func(*Error)
  9. type Error struct {
  10. err error
  11. prefix string
  12. statusCode int
  13. publicMessage string
  14. shouldReport bool
  15. stack []uintptr
  16. }
  17. func (e *Error) Error() string {
  18. if len(e.prefix) > 0 {
  19. return fmt.Sprintf("%s: %s", e.prefix, e.err.Error())
  20. }
  21. return e.err.Error()
  22. }
  23. func (e *Error) Unwrap() error {
  24. return e.err
  25. }
  26. func (e *Error) Cause() error {
  27. return e.err
  28. }
  29. func (e *Error) StatusCode() int {
  30. if e.statusCode <= 0 {
  31. return http.StatusInternalServerError
  32. }
  33. return e.statusCode
  34. }
  35. func (e *Error) PublicMessage() string {
  36. if len(e.publicMessage) == 0 {
  37. return "Internal error"
  38. }
  39. return e.publicMessage
  40. }
  41. func (e *Error) ShouldReport() bool {
  42. return e.shouldReport
  43. }
  44. func (e *Error) StackTrace() []uintptr {
  45. return e.stack
  46. }
  47. func (e *Error) Callers() []uintptr {
  48. return e.stack
  49. }
  50. func (e *Error) FormatStackLines() []string {
  51. lines := make([]string, len(e.stack))
  52. for i, pc := range e.stack {
  53. f := runtime.FuncForPC(pc)
  54. file, line := f.FileLine(pc)
  55. lines[i] = fmt.Sprintf("%s:%d %s", file, line, f.Name())
  56. }
  57. return lines
  58. }
  59. func (e *Error) FormatStack() string {
  60. return strings.Join(e.FormatStackLines(), "\n")
  61. }
  62. func Wrap(err error, stackSkip int, opts ...Option) *Error {
  63. if err == nil {
  64. return nil
  65. }
  66. var e *Error
  67. if ierr, ok := err.(*Error); ok {
  68. // if we have some options, we need to copy the error to not modify the original one
  69. if len(opts) > 0 {
  70. ecopy := *ierr
  71. e = &ecopy
  72. } else {
  73. return ierr
  74. }
  75. } else {
  76. e = &Error{
  77. err: err,
  78. shouldReport: true,
  79. }
  80. }
  81. for _, opt := range opts {
  82. opt(e)
  83. }
  84. if len(e.stack) == 0 {
  85. e.stack = callers(stackSkip + 1)
  86. }
  87. return e
  88. }
  89. func WithStatusCode(code int) Option {
  90. return func(e *Error) {
  91. e.statusCode = code
  92. }
  93. }
  94. func WithPublicMessage(msg string) Option {
  95. return func(e *Error) {
  96. e.publicMessage = msg
  97. }
  98. }
  99. func WithPrefix(prefix string) Option {
  100. return func(e *Error) {
  101. if len(e.prefix) > 0 {
  102. e.prefix = fmt.Sprintf("%s: %s", prefix, e.prefix)
  103. } else {
  104. e.prefix = prefix
  105. }
  106. }
  107. }
  108. func WithShouldReport(report bool) Option {
  109. return func(e *Error) {
  110. e.shouldReport = report
  111. }
  112. }
  113. func callers(skip int) []uintptr {
  114. stack := make([]uintptr, 10)
  115. n := runtime.Callers(skip+2, stack)
  116. return stack[:n]
  117. }