This repository was archived by the owner on Dec 9, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequest_builder.go
More file actions
114 lines (93 loc) · 2.8 KB
/
request_builder.go
File metadata and controls
114 lines (93 loc) · 2.8 KB
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
package form3api
import (
"bytes"
"context"
"io/ioutil"
"net/http"
)
// HTTPClient an interface to abstract the http client. Used for testing purposes.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// RequestBuilder builds and sends the API requests.
type RequestBuilder struct {
client HTTPClient
baseURL string
requestMethod string
serializer *jsonSerializer
}
// NewRequest creates new instance of RequestBuilder.
func NewRequest() *RequestBuilder {
return &RequestBuilder{client: &http.Client{}, serializer: &jsonSerializer{}}
}
// WithClient sets an http client
func (rb *RequestBuilder) WithClient(client HTTPClient) *RequestBuilder {
rb.client = client
return rb
}
// WithBaseURL sets endpoint url.
func (rb *RequestBuilder) WithBaseURL(baseURL string) *RequestBuilder {
rb.baseURL = baseURL
return rb
}
// WithMethod sets request method.
func (rb *RequestBuilder) WithMethod(method string) *RequestBuilder {
rb.requestMethod = method
return rb
}
// Exec builds and sends the request to endpoint url with the given method and params.
// Binds response body to res on success, returns an error on failure.
func (rb *RequestBuilder) Exec(ctx context.Context, params interface{}, res interface{}) error {
// prepare request
req, err := rb.buildRequest(ctx, params)
if err != nil {
return err
}
// exec request
resp, err := rb.client.Do(req)
if err != nil {
return err
}
// parse response
return rb.bindResponse(resp, res)
}
func (rb *RequestBuilder) buildRequest(ctx context.Context, params interface{}) (*http.Request, error) {
body, err := rb.serializer.Serialize(params)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, rb.requestMethod, rb.baseURL, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", rb.serializer.ContentType())
return req, nil
}
func (rb *RequestBuilder) bindResponse(resp *http.Response, res interface{}) error {
defer resp.Body.Close()
if err := rb.checkResponse(resp); err != nil {
return err
}
// delete response has no body
if resp.StatusCode == http.StatusNoContent {
return nil
}
return rb.serializer.Deserialize(resp.Body, res)
}
func (rb *RequestBuilder) checkResponse(resp *http.Response) error {
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
// read response body
body, err := ioutil.ReadAll(resp.Body)
// repopulate response body
// it maybe useful for non-structured API error responses to facilitate debugging
// it can also be used later to log the response
resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
apiErr := &APIError{Response: resp}
// try to parse known errors
if body != nil && err == nil {
_ = rb.serializer.Deserialize(bytes.NewBuffer(body), &apiErr)
}
return apiErr
}
return nil
}