1
mirror of https://github.com/rclone/rclone synced 2024-11-14 13:36:24 +01:00
rclone/rest/rest.go
2015-12-17 13:30:58 +00:00

145 lines
3.6 KiB
Go

// Package rest implements a simple REST wrapper
package rest
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/ncw/rclone/fs"
)
// Client contains the info to sustain the API
type Client struct {
c *http.Client
rootURL string
errorHandler func(resp *http.Response) error
}
// NewClient takes an oauth http.Client and makes a new api instance
func NewClient(c *http.Client, rootURL string) *Client {
return &Client{
c: c,
rootURL: rootURL,
errorHandler: defaultErrorHandler,
}
}
// defaultErrorHandler doesn't attempt to parse the http body
func defaultErrorHandler(resp *http.Response) (err error) {
defer checkClose(resp.Body, &err)
return fmt.Errorf("HTTP error %v (%v) returned", resp.StatusCode, resp.Status)
}
// SetErrorHandler sets the handler to decode an error response when
// the HTTP status code is not 2xx. The handler should close resp.Body.
func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) {
api.errorHandler = fn
}
// Opts contains parameters for Call, CallJSON etc
type Opts struct {
Method string
Path string
Absolute bool // Path is absolute
Body io.Reader
NoResponse bool // set to close Body
ContentType string
ContentLength *int64
ContentRange string
ExtraHeaders map[string]string
}
// checkClose is a utility function used to check the return from
// Close in a defer statement.
func checkClose(c io.Closer, err *error) {
cerr := c.Close()
if *err == nil {
*err = cerr
}
}
// DecodeJSON decodes resp.Body into result
func DecodeJSON(resp *http.Response, result interface{}) (err error) {
defer checkClose(resp.Body, &err)
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(result)
}
// Call makes the call and returns the http.Response
//
// if err != nil then resp.Body will need to be closed
//
// it will return resp if at all possible, even if err is set
func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
if opts == nil {
return nil, fmt.Errorf("call() called with nil opts")
}
var url string
if opts.Absolute {
url = opts.Path
} else {
url = api.rootURL + opts.Path
}
req, err := http.NewRequest(opts.Method, url, opts.Body)
if err != nil {
return
}
if opts.ContentType != "" {
req.Header.Add("Content-Type", opts.ContentType)
}
if opts.ContentLength != nil {
req.ContentLength = *opts.ContentLength
}
if opts.ContentRange != "" {
req.Header.Add("Content-Range", opts.ContentRange)
}
if opts.ExtraHeaders != nil {
for k, v := range opts.ExtraHeaders {
req.Header.Add(k, v)
}
}
req.Header.Add("User-Agent", fs.UserAgent)
resp, err = api.c.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return resp, api.errorHandler(resp)
}
if opts.NoResponse {
return resp, resp.Body.Close()
}
return resp, nil
}
// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
//
// If request is not nil then it will be JSON encoded as the body of the request
//
// It will return resp if at all possible, even if err is set
func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
// Set the body up as a JSON object if required
if opts.Body == nil && request != nil {
body, err := json.Marshal(request)
if err != nil {
return nil, err
}
var newOpts = *opts
newOpts.Body = bytes.NewBuffer(body)
newOpts.ContentType = "application/json"
opts = &newOpts
}
resp, err = api.Call(opts)
if err != nil {
return resp, err
}
if opts.NoResponse {
return resp, nil
}
err = DecodeJSON(resp, response)
return resp, err
}