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) }