openzeppelin_monitor/utils/
http.rs

1use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
2use reqwest_retry::{
3	policies::ExponentialBackoff, Jitter, RetryTransientMiddleware, RetryableStrategy,
4};
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8/// --- Default values for retry configuration settings ---
9fn default_max_attempts() -> u32 {
10	3
11}
12
13fn default_initial_backoff() -> Duration {
14	Duration::from_millis(250)
15}
16
17fn default_max_backoff() -> Duration {
18	Duration::from_secs(10)
19}
20
21fn default_base_for_backoff() -> u32 {
22	2
23}
24
25/// Serializable setting for jitter in retry policies
26#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
27#[serde(rename_all = "lowercase")]
28enum JitterSetting {
29	/// No jitter applied to the backoff duration
30	None,
31	/// Full jitter applied, randomizing the backoff duration
32	#[default]
33	Full,
34}
35
36/// Configuration for HTTP retry policies
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
38pub struct HttpRetryConfig {
39	/// Maximum number of retries for transient errors
40	#[serde(default = "default_max_attempts")]
41	pub max_retries: u32,
42	/// Base duration for exponential backoff calculations
43	#[serde(default = "default_base_for_backoff")]
44	pub base_for_backoff: u32,
45	/// Initial backoff duration before the first retry
46	#[serde(default = "default_initial_backoff")]
47	pub initial_backoff: Duration,
48	/// Maximum backoff duration for retries
49	#[serde(default = "default_max_backoff")]
50	pub max_backoff: Duration,
51	/// Jitter to apply to the backoff duration
52	#[serde(default)]
53	jitter: JitterSetting,
54}
55
56impl Default for HttpRetryConfig {
57	/// Creates a default configuration with reasonable retry settings
58	fn default() -> Self {
59		Self {
60			max_retries: default_max_attempts(),
61			base_for_backoff: default_base_for_backoff(),
62			initial_backoff: default_initial_backoff(),
63			max_backoff: default_max_backoff(),
64			jitter: JitterSetting::default(),
65		}
66	}
67}
68
69/// Creates a retryable HTTP client with middleware for a single URL
70///
71/// # Parameters:
72/// - `config`: Configuration for retry policies
73/// - `base_client`: The base HTTP client to use
74/// - `custom_strategy`: Optional custom retry strategy, complementing the default retry behavior
75///
76/// # Returns
77/// A `ClientWithMiddleware` that includes retry capabilities
78///
79pub fn create_retryable_http_client<S>(
80	config: &HttpRetryConfig,
81	base_client: reqwest::Client,
82	custom_strategy: Option<S>,
83) -> ClientWithMiddleware
84where
85	S: RetryableStrategy + Send + Sync + 'static,
86{
87	// Determine the jitter setting and create the policy builder accordingly
88	let policy_builder = match config.jitter {
89		JitterSetting::None => ExponentialBackoff::builder().jitter(Jitter::None),
90		JitterSetting::Full => ExponentialBackoff::builder().jitter(Jitter::Full),
91	};
92
93	// Create the retry policy based on the provided configuration
94	let retry_policy = policy_builder
95		.base(config.base_for_backoff)
96		.retry_bounds(config.initial_backoff, config.max_backoff)
97		.build_with_max_retries(config.max_retries);
98
99	// If a custom strategy is provided, use it with the retry policy; otherwise, use the retry policy with the default strategy.
100	if let Some(strategy) = custom_strategy {
101		ClientBuilder::new(base_client).with(
102			RetryTransientMiddleware::new_with_policy_and_strategy(retry_policy, strategy),
103		)
104	} else {
105		ClientBuilder::new(base_client)
106			.with(RetryTransientMiddleware::new_with_policy(retry_policy))
107	}
108	.build()
109}