You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
4.3 KiB

  1. package common
  2. import (
  3. "encoding/json"
  4. "io"
  5. "net/http"
  6. "gopkg.in/go-playground/validator.v9"
  7. "github.com/pkg/errors"
  8. )
  9. type ErrorResponse struct {
  10. Success bool `json:"success,omitempty"`
  11. // Err is the error message if Success is false
  12. Err string `json:"error,omitempty"`
  13. // Code is set if Success is false
  14. Code int `json:"code,omitempty"`
  15. }
  16. // ErrorWithCode makes an ErrorResponse with the
  17. // provided err's Error() content, and status code.
  18. // It panics if err is nil.
  19. func ErrorWithCode(err error, code int) *ErrorResponse {
  20. return &ErrorResponse{
  21. Err: err.Error(),
  22. Code: code,
  23. }
  24. }
  25. // Ensure that ErrorResponse implements error
  26. var _ error = (*ErrorResponse)(nil)
  27. func (er *ErrorResponse) Error() string {
  28. return er.Err
  29. }
  30. // Ensure that ErrorResponse implements httpCoder
  31. var _ httpCoder = (*ErrorResponse)(nil)
  32. func (er *ErrorResponse) HTTPCode() int {
  33. return er.Code
  34. }
  35. var errNilBody = errors.Errorf("expecting a non-nil body")
  36. // FparseJSON unmarshals into save, the body of the provided reader.
  37. // Since it uses json.Unmarshal, save must be of a pointer type
  38. // or compatible with json.Unmarshal.
  39. func FparseJSON(r io.Reader, save interface{}) error {
  40. if r == nil {
  41. return errors.Wrap(errNilBody, "Reader")
  42. }
  43. dec := json.NewDecoder(r)
  44. if err := dec.Decode(save); err != nil {
  45. return errors.Wrap(err, "Decode/Unmarshal")
  46. }
  47. return nil
  48. }
  49. // ParseRequestJSON unmarshals into save, the body of the
  50. // request. It closes the body of the request after parsing.
  51. // Since it uses json.Unmarshal, save must be of a pointer type
  52. // or compatible with json.Unmarshal.
  53. func ParseRequestJSON(r *http.Request, save interface{}) error {
  54. if r == nil || r.Body == nil {
  55. return errNilBody
  56. }
  57. defer r.Body.Close()
  58. return FparseJSON(r.Body, save)
  59. }
  60. // ParseRequestAndValidateJSON unmarshals into save, the body of the
  61. // request and invokes a validator on the saved content. To ensure
  62. // validation, make sure to set tags "validate" on your struct as
  63. // per https://godoc.org/gopkg.in/go-playground/validator.v9.
  64. // It closes the body of the request after parsing.
  65. // Since it uses json.Unmarshal, save must be of a pointer type
  66. // or compatible with json.Unmarshal.
  67. func ParseRequestAndValidateJSON(r *http.Request, save interface{}) error {
  68. if r == nil || r.Body == nil {
  69. return errNilBody
  70. }
  71. defer r.Body.Close()
  72. return FparseAndValidateJSON(r.Body, save)
  73. }
  74. // FparseAndValidateJSON like FparseJSON unmarshals into save,
  75. // the body of the provided reader. However, it invokes the validator
  76. // to check the set validators on your struct fields as per
  77. // per https://godoc.org/gopkg.in/go-playground/validator.v9.
  78. // Since it uses json.Unmarshal, save must be of a pointer type
  79. // or compatible with json.Unmarshal.
  80. func FparseAndValidateJSON(r io.Reader, save interface{}) error {
  81. if err := FparseJSON(r, save); err != nil {
  82. return err
  83. }
  84. return validate(save)
  85. }
  86. var theValidator = validator.New()
  87. func validate(obj interface{}) error {
  88. return errors.Wrap(theValidator.Struct(obj), "Validate")
  89. }
  90. // WriteSuccess JSON marshals the content provided, to an HTTP
  91. // response, setting the provided status code and setting header
  92. // "Content-Type" to "application/json".
  93. func WriteSuccess(w http.ResponseWriter, data interface{}) {
  94. WriteCode(w, data, 200)
  95. }
  96. // WriteCode JSON marshals content, to an HTTP response,
  97. // setting the provided status code, and setting header
  98. // "Content-Type" to "application/json". If JSON marshalling fails
  99. // with an error, WriteCode instead writes out the error invoking
  100. // WriteError.
  101. func WriteCode(w http.ResponseWriter, out interface{}, code int) {
  102. blob, err := json.MarshalIndent(out, "", " ")
  103. if err != nil {
  104. WriteError(w, err)
  105. } else {
  106. w.Header().Set("Content-Type", "application/json")
  107. w.WriteHeader(code)
  108. w.Write(blob)
  109. }
  110. }
  111. type httpCoder interface {
  112. HTTPCode() int
  113. }
  114. // WriteError is a convenience function to write out an
  115. // error to an http.ResponseWriter, to send out an error
  116. // that's structured as JSON i.e the form
  117. // {"error": sss, "code": ddd}
  118. // If err implements the interface HTTPCode() int,
  119. // it will use that status code otherwise, it will
  120. // set code to be http.StatusBadRequest
  121. func WriteError(w http.ResponseWriter, err error) {
  122. code := http.StatusBadRequest
  123. if httpC, ok := err.(httpCoder); ok {
  124. code = httpC.HTTPCode()
  125. }
  126. WriteCode(w, ErrorWithCode(err, code), code)
  127. }