Skip to content

Commit 4a7382d

Browse files
authored
client: WithHTTPClient and WithTransport options (#25)
Two new Option constructors so callers can plug their own HTTP client or transport without mutating fields after NewClient returns. WithHTTPClient swaps in a caller-supplied *http.Client. Useful when the application already maintains a shared connection pool with mTLS config or auth-injecting transport that the existing WithTimeout / WithMaxRetries options can't express. WithTransport replaces the underlying http.RoundTripper while preserving the Client's other settings (timeout, cookie jar, ...). Useful for stacking middleware — auth headers, request logging, external retry, rate-limit — without rewriting the default Client. Both options compose with each other and with WithUserAgent, WithTimeout, WithMaxRetries, and WithRateLimiter.
1 parent d54f633 commit 4a7382d

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

client/client.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,27 @@ func NewClient(opts ...Option) *Client {
217217
}
218218
return c
219219
}
220+
221+
// WithHTTPClient swaps in a caller-supplied *http.Client. Use this
222+
// when the application has its own connection pool, mTLS config, or
223+
// auth-injecting transport that other Options like WithTimeout can't
224+
// express. The supplied client's Timeout, Jar, and Transport are
225+
// preserved as-is.
226+
func WithHTTPClient(h *http.Client) Option {
227+
return func(c *Client) {
228+
c.HTTPClient = h
229+
}
230+
}
231+
232+
// WithTransport replaces the underlying http.RoundTripper without
233+
// touching the Client's other settings (timeout, jar, etc.). Useful
234+
// for wrapping the transport in middleware (auth, logging, retry,
235+
// rate-limit) while keeping the rest of the configured shape intact.
236+
func WithTransport(rt http.RoundTripper) Option {
237+
return func(c *Client) {
238+
if c.HTTPClient == nil {
239+
c.HTTPClient = &http.Client{Timeout: defaultTimeout}
240+
}
241+
c.HTTPClient.Transport = rt
242+
}
243+
}

client/options_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package client
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
"time"
7+
)
8+
9+
func TestWithHTTPClient(t *testing.T) {
10+
custom := &http.Client{Timeout: 7 * time.Second}
11+
c := NewClient(WithHTTPClient(custom))
12+
if c.HTTPClient != custom {
13+
t.Errorf("HTTPClient = %p, want %p", c.HTTPClient, custom)
14+
}
15+
if c.HTTPClient.Timeout != 7*time.Second {
16+
t.Errorf("supplied Timeout not preserved: got %v", c.HTTPClient.Timeout)
17+
}
18+
}
19+
20+
type stubTransport struct{ called bool }
21+
22+
func (s *stubTransport) RoundTrip(_ *http.Request) (*http.Response, error) {
23+
s.called = true
24+
return nil, http.ErrServerClosed
25+
}
26+
27+
func TestWithTransport(t *testing.T) {
28+
rt := &stubTransport{}
29+
c := NewClient(WithTransport(rt))
30+
if c.HTTPClient.Transport != rt {
31+
t.Errorf("Transport not set; got %v", c.HTTPClient.Transport)
32+
}
33+
// Other Client defaults should remain.
34+
if c.HTTPClient.Timeout == 0 {
35+
t.Error("WithTransport should preserve default timeout")
36+
}
37+
}
38+
39+
// WithTransport over a Client whose HTTPClient was nilled out (via an
40+
// earlier custom option) should still attach without panicking.
41+
func TestWithTransport_NilHTTPClient(t *testing.T) {
42+
c := DefaultClient()
43+
c.HTTPClient = nil
44+
rt := &stubTransport{}
45+
WithTransport(rt)(c)
46+
if c.HTTPClient == nil || c.HTTPClient.Transport != rt {
47+
t.Errorf("WithTransport should backfill HTTPClient; got %+v", c.HTTPClient)
48+
}
49+
}

0 commit comments

Comments
 (0)