-
Notifications
You must be signed in to change notification settings - Fork 2
/
route4me.go
147 lines (129 loc) · 3.58 KB
/
route4me.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package route4me
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strings"
"time"
"github.com/route4me/route4me-go-sdk/utils"
)
const (
DefaultTimeout time.Duration = time.Minute * 30
BaseURL = "https://www.route4me.com"
)
var InvalidStatusCode = errors.New("Invalid status code")
type Client struct {
APIKey string
Client *http.Client
BaseURL string
}
func NewClientWithOptions(APIKey string, timeout time.Duration, baseURL string) *Client {
return &Client{
APIKey: APIKey,
Client: &http.Client{Timeout: timeout},
BaseURL: baseURL,
}
}
// NewClient creates a route4me client
func NewClient(APIKey string) *Client {
return NewClientWithOptions(APIKey, DefaultTimeout, BaseURL)
}
func (c *Client) constructBody(data interface{}) (contentType string, reader bytes.Buffer, err error) {
//Check if the data struct has any postform data to pass to the body
params := utils.StructToURLValues("form", data)
// if there are no form parameters, it's likely the request is a json
if len(params) == 0 {
if err = json.NewEncoder(&reader).Encode(data); err != nil {
return
}
contentType = "application/json"
return
}
//otherwise we encode the form as a multipart form
w := multipart.NewWriter(&reader)
defer w.Close()
for key, vals := range params {
for _, v := range vals {
err = w.WriteField(key, v)
if err != nil {
return
}
}
}
contentType = w.FormDataContentType()
return
}
func (c *Client) DoNoDecode(method string, endpoint string, data interface{}) (response []byte, err error) {
var requestBody bytes.Buffer
var contentType string
//We might change this to == for better accuracy
if method != http.MethodGet && method != http.MethodOptions {
if contentType, requestBody, err = c.constructBody(data); err != nil {
return response, err
}
}
request, err := http.NewRequest(method, c.BaseURL+endpoint, &requestBody)
if err != nil {
return response, err
}
request.Header.Set("Content-Type", contentType)
params := url.Values{}
if data != nil {
//Prepare query string
params = utils.StructToURLValues("http", data)
}
params.Add("api_key", c.APIKey)
params.Add("format", "json")
request.URL.RawQuery = params.Encode()
// b, err := httputil.DumpRequestOut(request, true)
// if err != nil {
// panic(err)
// }
// fmt.Println(string(b))
resp, err := c.Client.Do(request)
// b, err = httputil.DumpResponse(resp, true)
// if err != nil {
// panic(err)
// }
// fmt.Println(string(b))
if err != nil {
return response, err
}
defer resp.Body.Close()
response, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return response, InvalidStatusCode
}
return response, err
}
func (c *Client) Do(method string, endpoint string, data interface{}, out interface{}) error {
read, err := c.DoNoDecode(method, endpoint, data)
//Error handling is a bit weird
if err != nil {
return c.parseErrors(read, err)
}
if out == nil {
return err
}
err = json.Unmarshal(read, out)
return err
}
func (c *Client) parseErrors(data []byte, err error) error {
//Check if invalid status code - errors:[] response is returned only when statuscode is not 200
if err == InvalidStatusCode {
errs := &ErrorResponse{}
//Try to parse to ErrorResponse
unmerr := json.Unmarshal(data, errs)
//Sometimes (status code: 500,404) errors:[] might not be returned, we return the err from the request when it happens
if unmerr != nil || len(errs.Errors) == 0 {
return err
}
//Join all errors in the ErrorResponse
return errors.New(strings.Join(errs.Errors, ","))
}
return err
}