mockforge_grpc/reflection/
error_handling.rs1use serde::{Deserialize, Serialize};
4use std::time::Duration;
5use tokio::time::sleep;
6use tonic::Status;
7use tracing::{debug, error, warn};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ErrorConfig {
12 pub max_retries: u32,
14 pub base_delay_ms: u64,
16 pub max_delay_ms: u64,
18 pub exponential_backoff: bool,
20}
21
22impl Default for ErrorConfig {
23 fn default() -> Self {
24 Self {
25 max_retries: 3,
26 base_delay_ms: 100,
27 max_delay_ms: 5000,
28 exponential_backoff: true,
29 }
30 }
31}
32
33pub async fn handle_with_retry<F, Fut, T>(
35 mut operation: F,
36 config: &ErrorConfig,
37) -> Result<T, Status>
38where
39 F: FnMut() -> Fut,
40 Fut: std::future::Future<Output = Result<T, Status>>,
41{
42 let mut attempts = 0;
43 let mut delay = Duration::from_millis(config.base_delay_ms);
44
45 loop {
46 match operation().await {
47 Ok(result) => return Ok(result),
48 Err(status) => {
49 attempts += 1;
50
51 if attempts > config.max_retries {
53 error!("Operation failed after {} attempts: {}", attempts, status);
54 return Err(status);
55 }
56
57 match status.code() {
59 tonic::Code::InvalidArgument
61 | tonic::Code::NotFound
62 | tonic::Code::AlreadyExists
63 | tonic::Code::PermissionDenied
64 | tonic::Code::FailedPrecondition
65 | tonic::Code::Aborted
66 | tonic::Code::OutOfRange
67 | tonic::Code::Unimplemented => {
68 error!("Non-retryable error: {}", status);
69 return Err(status);
70 }
71 _ => {
73 warn!(
74 "Attempt {} failed: {}. Retrying in {:?}...",
75 attempts, status, delay
76 );
77
78 sleep(delay).await;
80
81 if config.exponential_backoff {
83 delay = Duration::from_millis(
84 (delay.as_millis() * 2).min(config.max_delay_ms as u128) as u64,
85 );
86 }
87 }
88 }
89 }
90 }
91 }
92}
93
94pub fn simulate_error(error_rate: f64) -> Result<(), Status> {
96 use rand::Rng;
97
98 let mut rng = rand::rng();
99 let random: f64 = rng.random();
100
101 if random < error_rate {
102 let error_type: u32 = rng.random_range(0..5);
104 match error_type {
105 0 => Err(Status::unavailable("Simulated service unavailable")),
106 1 => Err(Status::deadline_exceeded("Simulated timeout")),
107 2 => Err(Status::internal("Simulated internal error")),
108 3 => Err(Status::resource_exhausted("Simulated resource exhausted")),
109 _ => Err(Status::unknown("Simulated unknown error")),
110 }
111 } else {
112 Ok(())
113 }
114}
115
116pub async fn simulate_latency(latency_ms: u64) {
118 if latency_ms > 0 {
119 debug!("Simulating {}ms latency", latency_ms);
120 sleep(Duration::from_millis(latency_ms)).await;
121 }
122}
123
124#[cfg(test)]
125mod tests {
126
127 #[test]
128 fn test_module_compiles() {}
129}