-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.go
More file actions
191 lines (164 loc) · 6.35 KB
/
api.go
File metadata and controls
191 lines (164 loc) · 6.35 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
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 httpclient
import (
"context"
"net/http"
"net/url"
)
// API stores configuration and defaults for HTTP requests to a specific service.
// It provides common attributes (headers, response handlers) applied to all requests.
//
// API instances are safe for concurrent use as they create new builders for each request.
type API struct {
client Doer
serverAddress url.URL
defaultRequestHeaders http.Header
defaultRequestOverrideFunc RequestOverrideFunc
defaultResponseHandlers ResponseStatusHandlers
defaultResponseBodySizeReadLimit int64
}
// NewAPI creates an API instance with the provided client and server address.
//
// The client must implement the Doer interface (http.Client does).
// Endpoint paths are appended to the server address.
func NewAPI(client Doer, serverAddress url.URL) *API {
const defaultResponseBodySizeReadLimit = 1 << 16 // 64KB
return &API{
client: client,
serverAddress: serverAddress,
defaultRequestHeaders: make(http.Header),
defaultResponseHandlers: make(map[int]ResponseHandler),
defaultResponseBodySizeReadLimit: defaultResponseBodySizeReadLimit,
}
}
// Clone creates a deep copy with independent configuration.
//
// Headers, response handlers, and settings are copied.
// The HTTP client is shared.
func (api *API) Clone() *API {
clone := &API{
client: api.client,
serverAddress: *api.URL(""),
defaultRequestHeaders: make(http.Header),
defaultResponseHandlers: make(map[int]ResponseHandler),
defaultResponseBodySizeReadLimit: api.defaultResponseBodySizeReadLimit,
}
for key, value := range api.defaultRequestHeaders {
clone.defaultRequestHeaders[key] = value
}
for key, value := range api.defaultResponseHandlers {
clone.defaultResponseHandlers[key] = value
}
return clone
}
// WithRequestOverrideFunc sets a function called for every request to modify
// the final http.Request before execution.
//
// Useful for authentication, signing, or consistent request modifications.
func (api *API) WithRequestOverrideFunc(overrideFunc RequestOverrideFunc) *API {
api.defaultRequestOverrideFunc = overrideFunc
return api
}
// WithRequestHeaders adds headers to all requests.
// Request-specific headers take precedence over API-level headers.
//
// Provided headers are merged into existing headers, not replaced.
// Common uses: authentication, user agents, ...
func (api *API) WithRequestHeaders(headers http.Header) *API {
for key, value := range headers {
api.defaultRequestHeaders[key] = value
}
return api
}
// WithResponseHandler sets a default handler for the specified status code.
// Called automatically unless overridden by request-specific handlers.
//
// Useful for consistent error handling (e.g., 401 as authentication error).
func (api *API) WithResponseHandler(status int, handler ResponseHandler) *API {
api.defaultResponseHandlers[status] = handler
return api
}
// WithResponseBodySizeReadLimit sets maximum bytes to read from response bodies.
// Security feature preventing memory exhaustion attacks.
//
// Behavior:
// - Larger Content-Length causes failure
// - Unknown Content-Length stops at limit
// - 0 uses Content-Length as limit
// - Negative disables limit (use with caution)
func (api *API) WithResponseBodySizeReadLimit(bodySizeReadLimit int64) *API {
api.defaultResponseBodySizeReadLimit = bodySizeReadLimit
return api
}
// URL constructs absolute URL by combining endpoint with server address.
func (api *API) URL(endpoint string) *url.URL {
var user *url.Userinfo
if api.serverAddress.User != nil {
u := *api.serverAddress.User
user = &u
}
u := api.serverAddress
u.User = user
u.Path += endpoint
return &u
}
// Head creates a HEAD request builder with default headers and settings.
func (api *API) Head(endpoint string) *RequestBuilder {
return NewRequest(http.MethodHead, api.URL(endpoint).String()).
Client(api.client).
SetHeaders(api.defaultRequestHeaders).
SetOverrideFunc(api.defaultRequestOverrideFunc)
}
// Get creates a GET request builder with default headers and settings.
func (api *API) Get(endpoint string) *RequestBuilder {
return NewRequest(http.MethodGet, api.URL(endpoint).String()).
Client(api.client).
SetHeaders(api.defaultRequestHeaders).
SetOverrideFunc(api.defaultRequestOverrideFunc)
}
// Post creates a POST request builder with default headers and settings.
func (api *API) Post(endpoint string) *RequestBuilder {
return NewRequest(http.MethodPost, api.URL(endpoint).String()).
Client(api.client).
SetHeaders(api.defaultRequestHeaders).
SetOverrideFunc(api.defaultRequestOverrideFunc)
}
// Put creates a PUT request builder with default headers and settings.
func (api *API) Put(endpoint string) *RequestBuilder {
return NewRequest(http.MethodPut, api.URL(endpoint).String()).
Client(api.client).
SetHeaders(api.defaultRequestHeaders).
SetOverrideFunc(api.defaultRequestOverrideFunc)
}
// Patch creates a PATCH request builder with default headers and settings.
func (api *API) Patch(endpoint string) *RequestBuilder {
return NewRequest(http.MethodPatch, api.URL(endpoint).String()).
Client(api.client).
SetHeaders(api.defaultRequestHeaders).
SetOverrideFunc(api.defaultRequestOverrideFunc)
}
// Delete creates a DELETE request builder with default headers and settings.
func (api *API) Delete(endpoint string) *RequestBuilder {
return NewRequest(http.MethodDelete, api.URL(endpoint).String()).
Client(api.client).
SetHeaders(api.defaultRequestHeaders).
SetOverrideFunc(api.defaultRequestOverrideFunc)
}
// Do executes the request and returns a response builder with API defaults.
//
// Applies default response handlers and body size limits.
// Use for custom response handling beyond Execute().
func (api *API) Do(ctx context.Context, req *RequestBuilder) *ResponseBuilder {
resp := req.Do(ctx)
resp = resp.BodySizeReadLimit(api.defaultResponseBodySizeReadLimit)
for httpStatus, responseHandler := range api.defaultResponseHandlers {
resp = resp.OnStatus(httpStatus, responseHandler)
}
return resp
}
// Execute performs the request using API defaults and returns any error.
//
// Convenience method for simple success/failure handling.
// Use Do() for complex response processing.
func (api *API) Execute(ctx context.Context, req *RequestBuilder) error {
return api.Do(ctx, req).Error()
}