typeway_client/config.rs
1//! Client configuration with timeout, retry, interceptor, and header settings.
2
3use std::fmt;
4use std::time::Duration;
5
6use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT};
7
8use crate::interceptor::{RequestInterceptor, ResponseInterceptor};
9use crate::retry::RetryPolicy;
10
11/// Configuration for the [`Client`](crate::Client).
12///
13/// Controls per-request timeouts, TCP connect timeouts, retry behavior,
14/// request/response interceptors, default headers, and cookie persistence.
15///
16/// # Example
17///
18/// ```
19/// use typeway_client::{ClientConfig, RetryPolicy, RequestInterceptor};
20/// use std::sync::Arc;
21/// use std::time::Duration;
22///
23/// let config = ClientConfig::default()
24/// .timeout(Duration::from_secs(60))
25/// .retry_policy(RetryPolicy::none())
26/// .bearer_auth("my-token")
27/// .cookie_store(true)
28/// .request_interceptor(Arc::new(|req| {
29/// req.header("X-Request-Id", "abc123")
30/// }));
31/// ```
32pub struct ClientConfig {
33 /// Per-request timeout. `None` means no timeout.
34 pub timeout: Option<Duration>,
35 /// TCP connection timeout. `None` means no timeout.
36 pub connect_timeout: Option<Duration>,
37 /// Retry policy for failed requests.
38 pub retry_policy: RetryPolicy,
39 /// Interceptors applied to every outgoing request.
40 pub request_interceptors: Vec<RequestInterceptor>,
41 /// Interceptors called on every incoming response.
42 pub response_interceptors: Vec<ResponseInterceptor>,
43 /// Headers sent with every request.
44 pub default_headers: HeaderMap,
45 /// Whether to enable automatic cookie persistence across requests.
46 pub cookie_store: bool,
47 /// Whether to enable built-in request/response tracing via the `tracing` crate.
48 ///
49 /// When enabled, every request logs the HTTP method, URL, response status,
50 /// and elapsed time at `DEBUG` level.
51 pub enable_tracing: bool,
52}
53
54impl Default for ClientConfig {
55 fn default() -> Self {
56 let mut default_headers = HeaderMap::new();
57 default_headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
58 Self {
59 timeout: Some(Duration::from_secs(30)),
60 connect_timeout: Some(Duration::from_secs(10)),
61 retry_policy: RetryPolicy::default(),
62 request_interceptors: Vec::new(),
63 response_interceptors: Vec::new(),
64 default_headers,
65 cookie_store: false,
66 enable_tracing: false,
67 }
68 }
69}
70
71impl Clone for ClientConfig {
72 fn clone(&self) -> Self {
73 Self {
74 timeout: self.timeout,
75 connect_timeout: self.connect_timeout,
76 retry_policy: self.retry_policy.clone(),
77 request_interceptors: self.request_interceptors.clone(),
78 response_interceptors: self.response_interceptors.clone(),
79 default_headers: self.default_headers.clone(),
80 cookie_store: self.cookie_store,
81 enable_tracing: self.enable_tracing,
82 }
83 }
84}
85
86impl fmt::Debug for ClientConfig {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.debug_struct("ClientConfig")
89 .field("timeout", &self.timeout)
90 .field("connect_timeout", &self.connect_timeout)
91 .field("retry_policy", &self.retry_policy)
92 .field(
93 "request_interceptors",
94 &format!("[{} interceptor(s)]", self.request_interceptors.len()),
95 )
96 .field(
97 "response_interceptors",
98 &format!("[{} interceptor(s)]", self.response_interceptors.len()),
99 )
100 .field("default_headers", &self.default_headers)
101 .field("cookie_store", &self.cookie_store)
102 .field("enable_tracing", &self.enable_tracing)
103 .finish()
104 }
105}
106
107impl ClientConfig {
108 /// Set the per-request timeout.
109 pub fn timeout(mut self, d: Duration) -> Self {
110 self.timeout = Some(d);
111 self
112 }
113
114 /// Disable the per-request timeout.
115 pub fn no_timeout(mut self) -> Self {
116 self.timeout = None;
117 self
118 }
119
120 /// Set the TCP connect timeout.
121 pub fn connect_timeout(mut self, d: Duration) -> Self {
122 self.connect_timeout = Some(d);
123 self
124 }
125
126 /// Disable the TCP connect timeout.
127 pub fn no_connect_timeout(mut self) -> Self {
128 self.connect_timeout = None;
129 self
130 }
131
132 /// Set the retry policy.
133 pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
134 self.retry_policy = policy;
135 self
136 }
137
138 /// Add a request interceptor that modifies outgoing requests.
139 ///
140 /// Interceptors are applied in the order they are added.
141 ///
142 /// # Example
143 ///
144 /// ```
145 /// use typeway_client::{ClientConfig, RequestInterceptor};
146 /// use std::sync::Arc;
147 ///
148 /// let config = ClientConfig::default()
149 /// .request_interceptor(Arc::new(|req| {
150 /// req.header("X-Trace-Id", "abc")
151 /// }));
152 /// ```
153 pub fn request_interceptor(mut self, interceptor: RequestInterceptor) -> Self {
154 self.request_interceptors.push(interceptor);
155 self
156 }
157
158 /// Add a response interceptor that inspects incoming responses.
159 ///
160 /// Interceptors are called in the order they are added.
161 ///
162 /// # Example
163 ///
164 /// ```
165 /// use typeway_client::{ClientConfig, ResponseInterceptor};
166 /// use std::sync::Arc;
167 ///
168 /// let config = ClientConfig::default()
169 /// .response_interceptor(Arc::new(|resp| {
170 /// eprintln!("status: {}", resp.status());
171 /// }));
172 /// ```
173 pub fn response_interceptor(mut self, interceptor: ResponseInterceptor) -> Self {
174 self.response_interceptors.push(interceptor);
175 self
176 }
177
178 /// Add a default header sent with every request.
179 ///
180 /// # Panics
181 ///
182 /// Panics if `name` or `value` cannot be parsed as valid HTTP header
183 /// components.
184 pub fn default_header(mut self, name: HeaderName, value: HeaderValue) -> Self {
185 self.default_headers.insert(name, value);
186 self
187 }
188
189 /// Convenience method to set a `Bearer` authentication token.
190 ///
191 /// This adds an `Authorization: Bearer <token>` header to every request.
192 pub fn bearer_auth(self, token: &str) -> Self {
193 let value = HeaderValue::from_str(&format!("Bearer {token}"))
194 .expect("bearer token contains invalid header characters");
195 self.default_header(http::header::AUTHORIZATION, value)
196 }
197
198 /// Enable or disable automatic cookie persistence across requests.
199 ///
200 /// When enabled, the underlying HTTP client stores cookies from responses
201 /// and sends them back in subsequent requests, providing session-like
202 /// behavior.
203 pub fn cookie_store(mut self, enabled: bool) -> Self {
204 self.cookie_store = enabled;
205 self
206 }
207
208 /// Enable built-in request/response tracing.
209 ///
210 /// When enabled, every HTTP request logs the method, URL, response status,
211 /// and elapsed time at `DEBUG` level via the [`tracing`] crate.
212 pub fn enable_tracing(mut self) -> Self {
213 self.enable_tracing = true;
214 self
215 }
216}