package common import ( "encoding/json" "io" "net/http" "gopkg.in/go-playground/validator.v9" "github.com/pkg/errors" ) type ErrorResponse struct { Success bool `json:"success,omitempty"` // Err is the error message if Success is false Err string `json:"error,omitempty"` // Code is set if Success is false Code int `json:"code,omitempty"` } // ErrorWithCode makes an ErrorResponse with the // provided err's Error() content, and status code. // It panics if err is nil. func ErrorWithCode(err error, code int) *ErrorResponse { return &ErrorResponse{ Err: err.Error(), Code: code, } } // Ensure that ErrorResponse implements error var _ error = (*ErrorResponse)(nil) func (er *ErrorResponse) Error() string { return er.Err } // Ensure that ErrorResponse implements httpCoder var _ httpCoder = (*ErrorResponse)(nil) func (er *ErrorResponse) HTTPCode() int { return er.Code } var errNilBody = errors.Errorf("expecting a non-nil body") // FparseJSON unmarshals into save, the body of the provided reader. // Since it uses json.Unmarshal, save must be of a pointer type // or compatible with json.Unmarshal. func FparseJSON(r io.Reader, save interface{}) error { if r == nil { return errors.Wrap(errNilBody, "Reader") } dec := json.NewDecoder(r) if err := dec.Decode(save); err != nil { return errors.Wrap(err, "Decode/Unmarshal") } return nil } // ParseRequestJSON unmarshals into save, the body of the // request. It closes the body of the request after parsing. // Since it uses json.Unmarshal, save must be of a pointer type // or compatible with json.Unmarshal. func ParseRequestJSON(r *http.Request, save interface{}) error { if r == nil || r.Body == nil { return errNilBody } defer r.Body.Close() return FparseJSON(r.Body, save) } // ParseRequestAndValidateJSON unmarshals into save, the body of the // request and invokes a validator on the saved content. To ensure // validation, make sure to set tags "validate" on your struct as // per https://godoc.org/gopkg.in/go-playground/validator.v9. // It closes the body of the request after parsing. // Since it uses json.Unmarshal, save must be of a pointer type // or compatible with json.Unmarshal. func ParseRequestAndValidateJSON(r *http.Request, save interface{}) error { if r == nil || r.Body == nil { return errNilBody } defer r.Body.Close() return FparseAndValidateJSON(r.Body, save) } // FparseAndValidateJSON like FparseJSON unmarshals into save, // the body of the provided reader. However, it invokes the validator // to check the set validators on your struct fields as per // per https://godoc.org/gopkg.in/go-playground/validator.v9. // Since it uses json.Unmarshal, save must be of a pointer type // or compatible with json.Unmarshal. func FparseAndValidateJSON(r io.Reader, save interface{}) error { if err := FparseJSON(r, save); err != nil { return err } return validate(save) } var theValidator = validator.New() func validate(obj interface{}) error { return errors.Wrap(theValidator.Struct(obj), "Validate") } // WriteSuccess JSON marshals the content provided, to an HTTP // response, setting the provided status code and setting header // "Content-Type" to "application/json". func WriteSuccess(w http.ResponseWriter, data interface{}) { WriteCode(w, data, 200) } // WriteCode JSON marshals content, to an HTTP response, // setting the provided status code, and setting header // "Content-Type" to "application/json". If JSON marshalling fails // with an error, WriteCode instead writes out the error invoking // WriteError. func WriteCode(w http.ResponseWriter, out interface{}, code int) { blob, err := json.MarshalIndent(out, "", " ") if err != nil { WriteError(w, err) } else { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) w.Write(blob) } } type httpCoder interface { HTTPCode() int } // WriteError is a convenience function to write out an // error to an http.ResponseWriter, to send out an error // that's structured as JSON i.e the form // {"error": sss, "code": ddd} // If err implements the interface HTTPCode() int, // it will use that status code otherwise, it will // set code to be http.StatusBadRequest func WriteError(w http.ResponseWriter, err error) { code := http.StatusBadRequest if httpC, ok := err.(httpCoder); ok { code = httpC.HTTPCode() } WriteCode(w, ErrorWithCode(err, code), code) }