package runtime
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// Client is the transport-layer HTTP client shared by all APIs.
type Client struct {
baseURL string
httpClient *http.Client
authenticator Authenticator
defaultHeaders http.Header
}
// Option configures a Client.
type Option func(*Client)
// NewClient builds a Client rooted at baseURL. Options override defaults.
func NewClient(baseURL string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(baseURL, "/"),
httpClient: http.DefaultClient,
defaultHeaders: make(http.Header),
}
for _, opt := range opts {
opt(c)
}
return c
}
// WithHTTPClient swaps the underlying *http.Client.
func WithHTTPClient(hc *http.Client) Option {
return func(c *Client) {
if hc != nil {
c.httpClient = hc
}
}
}
// WithAuth installs an Authenticator applied to every request.
func WithAuth(a Authenticator) Option {
return func(c *Client) { c.authenticator = a }
}
// WithDefaultHeader sets a header applied to every request. Per-request
// headers take precedence.
func WithDefaultHeader(key, value string) Option {
return func(c *Client) { c.defaultHeaders.Set(key, value) }
}
// BaseURL returns the base URL configured on the client.
func (c *Client) BaseURL() string { return c.baseURL }
// HTTPClient returns the underlying *http.Client.
func (c *Client) HTTPClient() *http.Client { return c.httpClient }
// NewRequest builds an *http.Request joined to the client's base URL. Query
// pairs (flat key=value list) are appended when non-empty.
func (c *Client) NewRequest(
ctx context.Context,
method, path string,
query url.Values,
body io.Reader,
) (*http.Request, error) {
full := c.baseURL + path
if len(query) > 0 {
full += "?" + query.Encode()
}
req, err := http.NewRequestWithContext(ctx, method, full, body)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
for k, vals := range c.defaultHeaders {
for _, v := range vals {
req.Header.Add(k, v)
}
}
return req, nil
}
// Do authenticates and executes a request.
func (c *Client) Do(req *http.Request) (*http.Response, error) {
if c.authenticator != nil {
if err := c.authenticator.AuthenticateRequest(req); err != nil {
return nil, fmt.Errorf("authenticate: %w", err)
}
}
return c.httpClient.Do(req)
}