errors.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package errcode
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. )
  7. // ErrorCoder is the base interface for ErrorCode and Error allowing
  8. // users of each to just call ErrorCode to get the real ID of each
  9. type ErrorCoder interface {
  10. ErrorCode() ErrorCode
  11. }
  12. // ErrorCode represents the error type. The errors are serialized via strings
  13. // and the integer format may change and should *never* be exported.
  14. type ErrorCode int
  15. var _ error = ErrorCode(0)
  16. // ErrorCode just returns itself
  17. func (ec ErrorCode) ErrorCode() ErrorCode {
  18. return ec
  19. }
  20. // Error returns the ID/Value
  21. func (ec ErrorCode) Error() string {
  22. // NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
  23. return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
  24. }
  25. // Descriptor returns the descriptor for the error code.
  26. func (ec ErrorCode) Descriptor() ErrorDescriptor {
  27. d, ok := errorCodeToDescriptors[ec]
  28. if !ok {
  29. return ErrorCodeUnknown.Descriptor()
  30. }
  31. return d
  32. }
  33. // String returns the canonical identifier for this error code.
  34. func (ec ErrorCode) String() string {
  35. return ec.Descriptor().Value
  36. }
  37. // Message returned the human-readable error message for this error code.
  38. func (ec ErrorCode) Message() string {
  39. return ec.Descriptor().Message
  40. }
  41. // MarshalText encodes the receiver into UTF-8-encoded text and returns the
  42. // result.
  43. func (ec ErrorCode) MarshalText() (text []byte, err error) {
  44. return []byte(ec.String()), nil
  45. }
  46. // UnmarshalText decodes the form generated by MarshalText.
  47. func (ec *ErrorCode) UnmarshalText(text []byte) error {
  48. desc, ok := idToDescriptors[string(text)]
  49. if !ok {
  50. desc = ErrorCodeUnknown.Descriptor()
  51. }
  52. *ec = desc.Code
  53. return nil
  54. }
  55. // WithMessage creates a new Error struct based on the passed-in info and
  56. // overrides the Message property.
  57. func (ec ErrorCode) WithMessage(message string) Error {
  58. return Error{
  59. Code: ec,
  60. Message: message,
  61. }
  62. }
  63. // WithDetail creates a new Error struct based on the passed-in info and
  64. // set the Detail property appropriately
  65. func (ec ErrorCode) WithDetail(detail interface{}) Error {
  66. return Error{
  67. Code: ec,
  68. Message: ec.Message(),
  69. }.WithDetail(detail)
  70. }
  71. // WithArgs creates a new Error struct and sets the Args slice
  72. func (ec ErrorCode) WithArgs(args ...interface{}) Error {
  73. return Error{
  74. Code: ec,
  75. Message: ec.Message(),
  76. }.WithArgs(args...)
  77. }
  78. // Error provides a wrapper around ErrorCode with extra Details provided.
  79. type Error struct {
  80. Code ErrorCode `json:"code"`
  81. Message string `json:"message"`
  82. Detail interface{} `json:"detail,omitempty"`
  83. // TODO(duglin): See if we need an "args" property so we can do the
  84. // variable substitution right before showing the message to the user
  85. }
  86. var _ error = Error{}
  87. // ErrorCode returns the ID/Value of this Error
  88. func (e Error) ErrorCode() ErrorCode {
  89. return e.Code
  90. }
  91. // Error returns a human readable representation of the error.
  92. func (e Error) Error() string {
  93. return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
  94. }
  95. // WithDetail will return a new Error, based on the current one, but with
  96. // some Detail info added
  97. func (e Error) WithDetail(detail interface{}) Error {
  98. return Error{
  99. Code: e.Code,
  100. Message: e.Message,
  101. Detail: detail,
  102. }
  103. }
  104. // WithArgs uses the passed-in list of interface{} as the substitution
  105. // variables in the Error's Message string, but returns a new Error
  106. func (e Error) WithArgs(args ...interface{}) Error {
  107. return Error{
  108. Code: e.Code,
  109. Message: fmt.Sprintf(e.Code.Message(), args...),
  110. Detail: e.Detail,
  111. }
  112. }
  113. // ErrorDescriptor provides relevant information about a given error code.
  114. type ErrorDescriptor struct {
  115. // Code is the error code that this descriptor describes.
  116. Code ErrorCode
  117. // Value provides a unique, string key, often captilized with
  118. // underscores, to identify the error code. This value is used as the
  119. // keyed value when serializing api errors.
  120. Value string
  121. // Message is a short, human readable decription of the error condition
  122. // included in API responses.
  123. Message string
  124. // Description provides a complete account of the errors purpose, suitable
  125. // for use in documentation.
  126. Description string
  127. // HTTPStatusCode provides the http status code that is associated with
  128. // this error condition.
  129. HTTPStatusCode int
  130. }
  131. // ParseErrorCode returns the value by the string error code.
  132. // `ErrorCodeUnknown` will be returned if the error is not known.
  133. func ParseErrorCode(value string) ErrorCode {
  134. ed, ok := idToDescriptors[value]
  135. if ok {
  136. return ed.Code
  137. }
  138. return ErrorCodeUnknown
  139. }
  140. // Errors provides the envelope for multiple errors and a few sugar methods
  141. // for use within the application.
  142. type Errors []error
  143. var _ error = Errors{}
  144. func (errs Errors) Error() string {
  145. switch len(errs) {
  146. case 0:
  147. return "<nil>"
  148. case 1:
  149. return errs[0].Error()
  150. default:
  151. msg := "errors:\n"
  152. for _, err := range errs {
  153. msg += err.Error() + "\n"
  154. }
  155. return msg
  156. }
  157. }
  158. // Len returns the current number of errors.
  159. func (errs Errors) Len() int {
  160. return len(errs)
  161. }
  162. // MarshalJSON converts slice of error, ErrorCode or Error into a
  163. // slice of Error - then serializes
  164. func (errs Errors) MarshalJSON() ([]byte, error) {
  165. var tmpErrs struct {
  166. Errors []Error `json:"errors,omitempty"`
  167. }
  168. for _, daErr := range errs {
  169. var err Error
  170. switch daErr.(type) {
  171. case ErrorCode:
  172. err = daErr.(ErrorCode).WithDetail(nil)
  173. case Error:
  174. err = daErr.(Error)
  175. default:
  176. err = ErrorCodeUnknown.WithDetail(daErr)
  177. }
  178. // If the Error struct was setup and they forgot to set the
  179. // Message field (meaning its "") then grab it from the ErrCode
  180. msg := err.Message
  181. if msg == "" {
  182. msg = err.Code.Message()
  183. }
  184. tmpErrs.Errors = append(tmpErrs.Errors, Error{
  185. Code: err.Code,
  186. Message: msg,
  187. Detail: err.Detail,
  188. })
  189. }
  190. return json.Marshal(tmpErrs)
  191. }
  192. // UnmarshalJSON deserializes []Error and then converts it into slice of
  193. // Error or ErrorCode
  194. func (errs *Errors) UnmarshalJSON(data []byte) error {
  195. var tmpErrs struct {
  196. Errors []Error
  197. }
  198. if err := json.Unmarshal(data, &tmpErrs); err != nil {
  199. return err
  200. }
  201. var newErrs Errors
  202. for _, daErr := range tmpErrs.Errors {
  203. // If Message is empty or exactly matches the Code's message string
  204. // then just use the Code, no need for a full Error struct
  205. if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
  206. // Error's w/o details get converted to ErrorCode
  207. newErrs = append(newErrs, daErr.Code)
  208. } else {
  209. // Error's w/ details are untouched
  210. newErrs = append(newErrs, Error{
  211. Code: daErr.Code,
  212. Message: daErr.Message,
  213. Detail: daErr.Detail,
  214. })
  215. }
  216. }
  217. *errs = newErrs
  218. return nil
  219. }