forked from mistermoe/httpr
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttpr.go
More file actions
132 lines (107 loc) · 3.59 KB
/
Copy pathhttpr.go
File metadata and controls
132 lines (107 loc) · 3.59 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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package httpr
import (
"context"
"fmt"
"io"
"maps"
"net/http"
"strings"
"github.com/alecthomas/types/optional"
)
type Client struct {
httpClient *http.Client
baseURL optional.Option[string]
headers map[string]string
interceptors []Interceptor
requestBodyHandler optional.Option[requestBodyHandler]
responseBodyHandler optional.Option[responseBodyHandler]
}
func NewClient(options ...ClientOption) *Client {
c := &Client{
httpClient: &http.Client{},
}
for _, option := range options {
option.Client(c)
}
return c
}
// Get sends a GET request to the specified URL.
func (c *Client) Get(ctx context.Context, url string, options ...RequestOption) (*http.Response, error) {
return c.SendRequest(ctx, http.MethodGet, url, options...)
}
// Post sends a POST request to the specified URL.
func (c *Client) Post(ctx context.Context, url string, options ...RequestOption) (*http.Response, error) {
return c.SendRequest(ctx, http.MethodPost, url, options...)
}
// Put sends a PUT request to the specified URL.
func (c *Client) Put(ctx context.Context, url string, options ...RequestOption) (*http.Response, error) {
return c.SendRequest(ctx, http.MethodPut, url, options...)
}
// Delete sends a DELETE request to the specified URL.
func (c *Client) Delete(ctx context.Context, url string, options ...RequestOption) (*http.Response, error) {
return c.SendRequest(ctx, http.MethodDelete, url, options...)
}
// SendRequest sends a request to the specified URL with the specified method and options.
func (c *Client) SendRequest(ctx context.Context, method string, path string, options ...RequestOption) (resp *http.Response, err error) {
opts := requestOptions{
requestBody: c.requestBodyHandler,
responseBody: c.responseBodyHandler,
headers: maps.Clone(c.headers),
interceptors: c.interceptors,
}
for _, option := range options {
option.Request(&opts)
}
var bodyReader io.Reader
if requestBodyHandler, ok := opts.requestBody.Get(); ok {
var contentType string
var err error
bodyReader, contentType, err = requestBodyHandler()
if err != nil {
return nil, fmt.Errorf("failed to get request body: %w", err)
}
if contentType != "" {
Header("Content-Type", contentType).Request(&opts)
}
}
// check if the path is a fully qualified URL, if so, use it as is, otherwise prepend the base URL
// if it's set. otherwise use the path as provided.
url := path
if !strings.HasPrefix(path, "http://") && !strings.HasPrefix(path, "https://") {
url = c.baseURL.Default("") + path
}
if queryParams, ok := opts.queryParams.Get(); ok {
url += "?" + queryParams.Encode()
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
for key, value := range opts.headers {
req.Header.Add(key, value)
}
chain := Chain(append(opts.interceptors, c.do())...)
httpResponse, err := chain.Handle(ctx, req, nil)
if err != nil {
return nil, fmt.Errorf("failed to handle request: %w", err)
}
if responseBodyHandler, ok := opts.responseBody.Get(); ok {
err := responseBodyHandler(httpResponse)
if err != nil {
return nil, fmt.Errorf("failed to handle response body: %w", err)
}
}
return httpResponse, nil
}
func (c *Client) do() HandleFunc {
return func(_ context.Context, req *http.Request, _ Interceptor) (*http.Response, error) {
httpResponse, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send HTTP request: %w", err)
}
if httpResponse.Request == nil {
httpResponse.Request = req
}
return httpResponse, nil
}
}