forked from sourcegraph/thesrc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
191 lines (159 loc) · 4.45 KB
/
client.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package thesrc
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"github.com/google/go-querystring/query"
"github.com/sourcegraph/thesrc/router"
)
// A Client communicates with thesrc's HTTP API.
type Client struct {
Posts PostsService
// BaseURL for HTTP requests to thesrc's API.
BaseURL *url.URL
//UserAgent used for HTTP requests to thesrc's API.
UserAgent string
httpClient *http.Client
}
const (
libraryVersion = "0.0.1"
userAgent = "thesrc-client/" + libraryVersion
)
// NewClient creates a new HTTP API client for thesrc. If httpClient == nil,
// then http.DefaultClient is used.
func NewClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = http.DefaultClient
}
c := &Client{
BaseURL: &url.URL{Scheme: "http", Host: "thesrc.org", Path: "/api/"},
UserAgent: userAgent,
httpClient: httpClient,
}
c.Posts = &postsService{c}
return c
}
// ListOptions specifies general pagination options for fetching a list of
// results.
type ListOptions struct {
PerPage int `url:",omitempty" json:",omitempty"`
Page int `url:",omitempty" json:",omitempty"`
}
func (o ListOptions) PageOrDefault() int {
if o.Page <= 0 {
return 1
}
return o.Page
}
func (o ListOptions) Offset() int {
return (o.PageOrDefault() - 1) * o.PerPageOrDefault()
}
func (o ListOptions) PerPageOrDefault() int {
if o.PerPage <= 0 {
return DefaultPerPage
}
return o.PerPage
}
// DefaultPerPage is the default number of items to return per page in
// a paginated result set.
const DefaultPerPage = 10
// apiRouter is used to generate URLs for thesrc's HTTP API.
var apiRouter = router.API()
// url generates the URL to the named thesrc API endpoint, using the
// specified route variables and query options.
func (c *Client) url(apiRouteName string, routeVars map[string]string, opt interface{}) (*url.URL, error) {
route := apiRouter.Get(apiRouteName)
if route == nil {
return nil, fmt.Errorf("no API route named %q", apiRouteName)
}
routeVarsList := make([]string, 2*len(routeVars))
i := 0
for name, val := range routeVars {
routeVarsList[i*2] = name
routeVarsList[i*2+1] = val
i++
}
url, err := route.URL(routeVarsList...)
if err != nil {
return nil, err
}
// make the route URL path relative to BaseURL by trimming the leading "/"
url.Path = strings.TrimPrefix(url.Path, "/")
if opt != nil {
err = addOptions(url, opt)
if err != nil {
return nil, err
}
}
return url, nil
}
// NewRequest creates an API request. A relative URL can be provided in urlStr,
// in which case it is resolved relative to the BaseURL of the Client. Relative
// URLs should always be specified without a preceding slash. If specified, the
// value pointed to by body is JSON encoded and included as the request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
u := c.BaseURL.ResolveReference(rel)
buf := new(bytes.Buffer)
if body != nil {
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
req.Header.Add("User-Agent", c.UserAgent)
return req, nil
}
// Do sends an API request and returns the API response. The API response is
// JSON-decoded and stored in the value pointed to by v, or returned as an error
// if an API error has occurred.
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
err = CheckResponse(resp)
if err != nil {
// even though there was an error, we still return the response
// in case the caller wants to inspect it further
return resp, err
}
if v != nil {
if bp, ok := v.(*[]byte); ok {
*bp, err = ioutil.ReadAll(resp.Body)
} else {
err = json.NewDecoder(resp.Body).Decode(v)
}
}
if err != nil {
return nil, fmt.Errorf("error reading response from %s %s: %s", req.Method, req.URL.RequestURI(), err)
}
return resp, nil
}
// addOptions adds the parameters in opt as URL query parameters to u. opt
// must be a struct whose fields may contain "url" tags.
func addOptions(u *url.URL, opt interface{}) error {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return nil
}
qs, err := query.Values(opt)
if err != nil {
return err
}
u.RawQuery = qs.Encode()
return nil
}