214 lines
5.4 KiB
Go
214 lines
5.4 KiB
Go
|
package oneandone
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
p_url "net/url"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type restClient struct {
|
||
|
token string
|
||
|
}
|
||
|
|
||
|
func newRestClient(token string) *restClient {
|
||
|
restClient := new(restClient)
|
||
|
restClient.token = token
|
||
|
return restClient
|
||
|
}
|
||
|
|
||
|
func (c *restClient) Get(url string, result interface{}, expectedStatus int) error {
|
||
|
return c.doRequest(url, "GET", nil, result, expectedStatus)
|
||
|
}
|
||
|
|
||
|
func (c *restClient) Delete(url string, requestBody interface{}, result interface{}, expectedStatus int) error {
|
||
|
return c.doRequest(url, "DELETE", requestBody, result, expectedStatus)
|
||
|
}
|
||
|
|
||
|
func (c *restClient) Post(url string, requestBody interface{}, result interface{}, expectedStatus int) error {
|
||
|
return c.doRequest(url, "POST", requestBody, result, expectedStatus)
|
||
|
}
|
||
|
|
||
|
func (c *restClient) Put(url string, requestBody interface{}, result interface{}, expectedStatus int) error {
|
||
|
return c.doRequest(url, "PUT", requestBody, result, expectedStatus)
|
||
|
}
|
||
|
|
||
|
func (c *restClient) doRequest(url string, method string, requestBody interface{}, result interface{}, expectedStatus int) error {
|
||
|
var bodyData io.Reader
|
||
|
if requestBody != nil {
|
||
|
data, _ := json.Marshal(requestBody)
|
||
|
bodyData = bytes.NewBuffer(data)
|
||
|
}
|
||
|
|
||
|
request, err := http.NewRequest(method, url, bodyData)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
request.Header.Add("X-Token", c.token)
|
||
|
request.Header.Add("Content-Type", "application/json")
|
||
|
client := http.Client{}
|
||
|
response, err := client.Do(request)
|
||
|
if err = isError(response, expectedStatus, err); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
defer response.Body.Close()
|
||
|
body, err := ioutil.ReadAll(response.Body)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return c.unmarshal(body, result)
|
||
|
}
|
||
|
|
||
|
func (c *restClient) unmarshal(data []byte, result interface{}) error {
|
||
|
err := json.Unmarshal(data, result)
|
||
|
if err != nil {
|
||
|
// handle the case when the result is an empty array instead of an object
|
||
|
switch err.(type) {
|
||
|
case *json.UnmarshalTypeError:
|
||
|
var ra []interface{}
|
||
|
e := json.Unmarshal(data, &ra)
|
||
|
if e != nil {
|
||
|
return e
|
||
|
} else if len(ra) > 0 {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
default:
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func isError(response *http.Response, expectedStatus int, err error) error {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if response != nil {
|
||
|
if response.StatusCode == expectedStatus {
|
||
|
// we got a response with the expected HTTP status code, hence no error
|
||
|
return nil
|
||
|
}
|
||
|
body, _ := ioutil.ReadAll(response.Body)
|
||
|
// extract the API's error message to be returned later
|
||
|
er_resp := new(errorResponse)
|
||
|
err = json.Unmarshal(body, er_resp)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return apiError{response.StatusCode, fmt.Sprintf("Type: %s; Message: %s", er_resp.Type, er_resp.Message)}
|
||
|
}
|
||
|
return errors.New("Generic error - no response from the REST API service.")
|
||
|
}
|
||
|
|
||
|
func createUrl(api *API, sections ...interface{}) string {
|
||
|
url := api.Endpoint
|
||
|
for _, section := range sections {
|
||
|
url += "/" + fmt.Sprint(section)
|
||
|
}
|
||
|
return url
|
||
|
}
|
||
|
|
||
|
func makeParameterMap(args ...interface{}) (map[string]interface{}, error) {
|
||
|
qps := make(map[string]interface{}, len(args))
|
||
|
var is_true bool
|
||
|
var page, per_page int
|
||
|
var sort, query, fields string
|
||
|
|
||
|
for i, p := range args {
|
||
|
switch i {
|
||
|
case 0:
|
||
|
page, is_true = p.(int)
|
||
|
if !is_true {
|
||
|
return nil, errors.New("1st parameter must be a page number (integer).")
|
||
|
} else if page > 0 {
|
||
|
qps["page"] = page
|
||
|
}
|
||
|
case 1:
|
||
|
per_page, is_true = p.(int)
|
||
|
if !is_true {
|
||
|
return nil, errors.New("2nd parameter must be a per_page number (integer).")
|
||
|
} else if per_page > 0 {
|
||
|
qps["per_page"] = per_page
|
||
|
}
|
||
|
case 2:
|
||
|
sort, is_true = p.(string)
|
||
|
if !is_true {
|
||
|
return nil, errors.New("3rd parameter must be a sorting property string (e.g. 'name' or '-name').")
|
||
|
} else if sort != "" {
|
||
|
qps["sort"] = sort
|
||
|
}
|
||
|
case 3:
|
||
|
query, is_true = p.(string)
|
||
|
if !is_true {
|
||
|
return nil, errors.New("4th parameter must be a query string to look for the response.")
|
||
|
} else if query != "" {
|
||
|
qps["q"] = query
|
||
|
}
|
||
|
case 4:
|
||
|
fields, is_true = p.(string)
|
||
|
if !is_true {
|
||
|
return nil, errors.New("5th parameter must be fields properties string (e.g. 'id,name').")
|
||
|
} else if fields != "" {
|
||
|
qps["fields"] = fields
|
||
|
}
|
||
|
default:
|
||
|
return nil, errors.New("Wrong number of parameters.")
|
||
|
}
|
||
|
}
|
||
|
return qps, nil
|
||
|
}
|
||
|
|
||
|
func processQueryParams(url string, args ...interface{}) (string, error) {
|
||
|
if len(args) > 0 {
|
||
|
params, err := makeParameterMap(args...)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
url = appendQueryParams(url, params)
|
||
|
}
|
||
|
return url, nil
|
||
|
}
|
||
|
|
||
|
func processQueryParamsExt(url string, period string, sd *time.Time, ed *time.Time, args ...interface{}) (string, error) {
|
||
|
var qm map[string]interface{}
|
||
|
var err error
|
||
|
if len(args) > 0 {
|
||
|
qm, err = makeParameterMap(args...)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
} else {
|
||
|
qm = make(map[string]interface{}, 3)
|
||
|
}
|
||
|
qm["period"] = period
|
||
|
if sd != nil && ed != nil {
|
||
|
if sd.After(*ed) {
|
||
|
return "", errors.New("Start date cannot be after end date.")
|
||
|
}
|
||
|
qm["start_date"] = sd.Format(time.RFC3339)
|
||
|
qm["end_date"] = ed.Format(time.RFC3339)
|
||
|
}
|
||
|
url = appendQueryParams(url, qm)
|
||
|
return url, nil
|
||
|
}
|
||
|
|
||
|
func appendQueryParams(url string, params map[string]interface{}) string {
|
||
|
queryUrl, _ := p_url.Parse(url)
|
||
|
parameters := p_url.Values{}
|
||
|
for key, value := range params {
|
||
|
parameters.Add(key, fmt.Sprintf("%v", value))
|
||
|
}
|
||
|
queryUrl.RawQuery = parameters.Encode()
|
||
|
return queryUrl.String()
|
||
|
}
|