1#[cfg(not(target_arch = "wasm32"))]
4use dapi_grpc::tonic::transport::Certificate;
5use std::time::Duration;
6
7const DEFAULT_CONNECT_TIMEOUT: Option<Duration> = None;
9const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
10const DEFAULT_RETRIES: usize = 5;
11const DEFAULT_BAN_FAILED_ADDRESS: bool = true;
12
13#[derive(Debug, Clone, Copy, Default)]
21pub struct RequestSettings {
22 pub connect_timeout: Option<Duration>,
24 pub timeout: Option<Duration>,
29 pub retries: Option<usize>,
32 pub ban_failed_address: Option<bool>,
34 pub max_decoding_message_size: Option<usize>,
36}
37
38impl RequestSettings {
39 pub const fn default() -> Self {
42 RequestSettings {
43 connect_timeout: None,
44 timeout: None,
45 retries: None,
46 ban_failed_address: None,
47 max_decoding_message_size: None,
48 }
49 }
50
51 pub fn override_by(self, rhs: RequestSettings) -> Self {
55 RequestSettings {
56 connect_timeout: rhs.connect_timeout.or(self.connect_timeout),
57 timeout: rhs.timeout.or(self.timeout),
58 retries: rhs.retries.or(self.retries),
59 ban_failed_address: rhs.ban_failed_address.or(self.ban_failed_address),
60 max_decoding_message_size: rhs
61 .max_decoding_message_size
62 .or(self.max_decoding_message_size),
63 }
64 }
65
66 pub fn finalize(self) -> AppliedRequestSettings {
68 AppliedRequestSettings {
69 connect_timeout: self.connect_timeout.or(DEFAULT_CONNECT_TIMEOUT),
70 timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
71 retries: self.retries.unwrap_or(DEFAULT_RETRIES),
72 ban_failed_address: self
73 .ban_failed_address
74 .unwrap_or(DEFAULT_BAN_FAILED_ADDRESS),
75 max_decoding_message_size: self.max_decoding_message_size,
76 #[cfg(not(target_arch = "wasm32"))]
77 ca_certificate: None,
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
88pub struct AppliedRequestSettings {
89 pub connect_timeout: Option<Duration>,
91 pub timeout: Duration,
93 pub retries: usize,
95 pub ban_failed_address: bool,
97 pub max_decoding_message_size: Option<usize>,
99 #[cfg(not(target_arch = "wasm32"))]
101 pub ca_certificate: Option<Certificate>,
102}
103impl AppliedRequestSettings {
104 #[cfg(not(target_arch = "wasm32"))]
108 pub fn with_ca_certificate(mut self, ca_cert: Option<Certificate>) -> Self {
109 self.ca_certificate = ca_cert;
110 self
111 }
112
113 pub(crate) fn connection_key(&self) -> String {
120 let Self {
123 #[cfg(not(target_arch = "wasm32"))]
124 connect_timeout,
125 #[cfg(target_arch = "wasm32")]
126 connect_timeout: _,
127 timeout: _,
128 retries: _,
129 ban_failed_address: _,
130 max_decoding_message_size,
131 #[cfg(not(target_arch = "wasm32"))]
132 ca_certificate,
133 } = self;
134
135 #[cfg(target_arch = "wasm32")]
139 let connect_timeout = None::<Duration>;
140
141 #[cfg(not(target_arch = "wasm32"))]
145 let ca_certificate = ca_certificate.as_ref().map(|cert| {
146 use std::fmt::Write;
147 let bytes = cert.as_ref();
148 let mut hex = String::with_capacity(bytes.len() * 2);
149 for byte in bytes {
150 write!(hex, "{byte:02x}").expect("writing to a String cannot fail");
151 }
152 hex
153 });
154 #[cfg(target_arch = "wasm32")]
155 let ca_certificate: Option<String> = None;
156
157 format!(
158 "connect_timeout={:?},max_decoding_message_size={:?},ca_certificate={:?}",
159 connect_timeout, max_decoding_message_size, ca_certificate
160 )
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_request_settings_override_by() {
170 let base = RequestSettings {
171 timeout: Some(Duration::from_secs(5)),
172 retries: Some(3),
173 connect_timeout: Some(Duration::from_secs(2)),
174 ban_failed_address: Some(true),
175 max_decoding_message_size: Some(1024),
176 };
177
178 let override_settings = RequestSettings {
180 timeout: Some(Duration::from_secs(10)),
181 retries: None,
182 connect_timeout: None,
183 ban_failed_address: None,
184 max_decoding_message_size: None,
185 };
186
187 let result = base.override_by(override_settings);
188 assert_eq!(result.timeout, Some(Duration::from_secs(10))); assert_eq!(result.retries, Some(3)); assert_eq!(result.connect_timeout, Some(Duration::from_secs(2))); assert_eq!(result.ban_failed_address, Some(true)); assert_eq!(result.max_decoding_message_size, Some(1024)); }
194
195 #[test]
196 fn test_request_settings_override_by_empty() {
197 let base = RequestSettings {
198 timeout: Some(Duration::from_secs(5)),
199 retries: Some(3),
200 connect_timeout: None,
201 ban_failed_address: None,
202 max_decoding_message_size: None,
203 };
204
205 let result = base.override_by(RequestSettings::default());
206 assert_eq!(result.timeout, Some(Duration::from_secs(5)));
207 assert_eq!(result.retries, Some(3));
208 }
209
210 #[test]
211 fn test_request_settings_finalize_defaults() {
212 let settings = RequestSettings::default();
213 let applied = settings.finalize();
214
215 assert_eq!(applied.connect_timeout, None);
216 assert_eq!(applied.timeout, Duration::from_secs(10));
217 assert_eq!(applied.retries, 5);
218 assert!(applied.ban_failed_address);
219 assert!(applied.max_decoding_message_size.is_none());
220 }
221
222 #[test]
223 fn test_request_settings_finalize_custom() {
224 let settings = RequestSettings {
225 connect_timeout: Some(Duration::from_secs(3)),
226 timeout: Some(Duration::from_secs(30)),
227 retries: Some(10),
228 ban_failed_address: Some(false),
229 max_decoding_message_size: Some(4096),
230 };
231
232 let applied = settings.finalize();
233 assert_eq!(applied.connect_timeout, Some(Duration::from_secs(3)));
234 assert_eq!(applied.timeout, Duration::from_secs(30));
235 assert_eq!(applied.retries, 10);
236 assert!(!applied.ban_failed_address);
237 assert_eq!(applied.max_decoding_message_size, Some(4096));
238 }
239
240 #[cfg(not(target_arch = "wasm32"))]
241 #[test]
242 fn test_applied_settings_with_ca_certificate_none() {
243 let applied = RequestSettings::default().finalize();
244 let result = applied.with_ca_certificate(None);
245 assert!(result.ca_certificate.is_none());
246 }
247
248 #[cfg(not(target_arch = "wasm32"))]
249 #[test]
250 fn test_applied_settings_with_ca_certificate_some() {
251 let applied = RequestSettings::default().finalize();
252 let cert = Certificate::from_pem("fake-pem-data");
253 let result = applied.with_ca_certificate(Some(cert));
254 assert!(result.ca_certificate.is_some());
255 }
256
257 #[test]
258 fn test_connection_key_ignores_per_request_settings() {
259 let custom = RequestSettings {
260 timeout: Some(Duration::from_secs(30)),
261 retries: Some(1),
262 ban_failed_address: Some(false),
263 ..RequestSettings::default()
264 }
265 .finalize();
266 let default = RequestSettings::default().finalize();
267
268 assert_eq!(
269 custom.connection_key(),
270 default.connection_key(),
271 "timeout/retries/banning must not split pooled connections"
272 );
273 }
274
275 #[test]
276 fn test_connection_key_differs_on_connection_settings() {
277 let default = RequestSettings::default().finalize();
278
279 let connect_timeout = RequestSettings {
280 connect_timeout: Some(Duration::from_secs(3)),
281 ..RequestSettings::default()
282 }
283 .finalize();
284 assert_ne!(default.connection_key(), connect_timeout.connection_key());
285
286 let decode_limit = RequestSettings {
287 max_decoding_message_size: Some(16 * 1024 * 1024),
288 ..RequestSettings::default()
289 }
290 .finalize();
291 assert_ne!(default.connection_key(), decode_limit.connection_key());
292 }
293
294 #[cfg(not(target_arch = "wasm32"))]
295 #[test]
296 fn test_connection_key_differs_on_ca_certificate() {
297 let default = RequestSettings::default().finalize();
298 let with_ca = RequestSettings::default()
299 .finalize()
300 .with_ca_certificate(Some(Certificate::from_pem("fake-pem-data")));
301
302 assert_ne!(default.connection_key(), with_ca.connection_key());
303
304 let with_other_ca = RequestSettings::default()
305 .finalize()
306 .with_ca_certificate(Some(Certificate::from_pem("other-pem-data")));
307 assert_ne!(with_ca.connection_key(), with_other_ca.connection_key());
308 }
309}