mirror of
https://github.com/rclone/rclone
synced 2024-11-14 13:36:24 +01:00
136 lines
3.5 KiB
Go
136 lines
3.5 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 fs.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
|
|
}
|
|
|
|
// DecodeJSON decodes resp.Body into result
|
|
func DecodeJSON(resp *http.Response, result interface{}) (err error) {
|
|
defer fs.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
|
|
}
|