package rest
import (
"net/http"
"io"
"io/ioutil"
"encoding/json"
"fmt"
)
// Bind decodes a request body and executes the Binder method of the
// payload structure.
func Bind(r *http.Request, v interface{}) error {
return DecodeJSON(r, v)
}
// DecodeJSON decodes any request body as JSON that matches a JSON content-type.
func DecodeJSON(r *http.Request, v interface{}) error {
h := r.Header.Get("Content-Type")
if h == "" {
h = "application/json"
}
ct := ParseContentType(h)
if !(ct.Type == "application" && (ct.Subtype == "json" || ct.Suffix == "json")) {
return ErrUnsupportedMediaType
}
if err := decodeJSON(r.Body, v); err != nil {
return ErrMalformedPayload(err)
}
return nil
}
// decodeJSON decodes a request body as JSON and discards the remainder.
func decodeJSON(r io.Reader, v interface{}) error {
defer func() {
_, err := io.Copy(ioutil.Discard, r)
fmt.Errorf("decodeJSON: %#v", err)
}()
return json.NewDecoder(r).Decode(v)
}
// CheckJSONResponse returns an error (of type *ResponseError) if the response
// status code is not 2xx.
//
// It gracefully degrades by ignoring errors while attemping to read the body.
// It is the caller's responsibility to close res.Body.
func CheckJSONResponse(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
body, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20)) // 1MiB
if err == nil {
var resterr Error
err = json.Unmarshal(body, resterr)
if err == nil {
if resterr.Code == 0 {
resterr.Code = res.StatusCode
}
return resterr
}
}
return &ResponseError{
Code: res.StatusCode,
Header: res.Header,
Body: string(body),
}
}
// CheckResponse returns an error (of type *ResponseError) if the response
// status code is not 2xx. Unlike CheckJSONResponse it does not assume the body
// is a JSON error document.
//
// It gracefully degrades by ignoring errors while attemping to read the body.
// It is the caller's responsibility to close res.Body.
func CheckResponse(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
body, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20)) // 1MiB
return &ResponseError{
Code: res.StatusCode,
Header: res.Header,
Body: string(body),
}
}
// ResponseError contains an error response from the server.
type ResponseError struct {
// Code is the HTTP response status code and will always be populated.
Code int `json:"code"`
// Header contains the response header fields from the server.
Header http.Header
// Body is the raw response returned by the server.
// It is often but not always JSON, depending on how the request fails.
Body string
}
func (e *ResponseError) Error() string {
return fmt.Sprintf("response error %d", e.Code)
}