logtail_rust/http_client/
mod.rs1mod base_client;
2pub mod service;
3
4pub use base_client::ReqwestClient;
5
6use reqwest::header::HeaderMap;
7use serde_json::Value;
8use std::future::Future;
9use std::time::Duration;
10
11#[derive(Debug, thiserror::Error)]
12pub enum LogtailError {
13 #[error("HTTP {status}: {message}")]
14 Http { status: u16, message: String },
15 #[error("serialization failed: {0}")]
16 Serialization(#[from] serde_json::Error),
17 #[error("network error: {0}")]
18 Network(#[from] reqwest::Error),
19}
20
21impl LogtailError {
22 pub fn is_retryable(&self) -> bool {
23 match self {
24 LogtailError::Http { status, .. } => *status >= 500,
25 LogtailError::Network(_) => true,
26 LogtailError::Serialization(_) => false,
27 }
28 }
29}
30
31pub struct RetryConfig {
32 pub max_retries: u32,
33 pub base_delay: Duration,
34 pub max_delay: Duration,
35 pub jitter: bool,
36}
37
38impl Default for RetryConfig {
39 fn default() -> Self {
40 Self {
41 max_retries: 3,
42 base_delay: Duration::from_secs(1),
43 max_delay: Duration::from_secs(5),
44 jitter: true,
45 }
46 }
47}
48
49pub trait HttpClient: Send + Sync {
50 fn post_json(
51 &self,
52 url: &str,
53 body: &Value,
54 extra_headers: Option<HeaderMap>,
55 ) -> impl Future<Output = Result<Option<Value>, LogtailError>> + Send;
56}
57
58#[cfg(test)]
59pub(crate) mod mock;
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
68 fn http_5xx_is_retryable() {
69 let err = LogtailError::Http {
70 status: 500,
71 message: "internal".to_string(),
72 };
73 assert!(err.is_retryable());
74 }
75
76 #[test]
77 fn http_502_is_retryable() {
78 let err = LogtailError::Http {
79 status: 502,
80 message: "bad gateway".to_string(),
81 };
82 assert!(err.is_retryable());
83 }
84
85 #[test]
86 fn http_4xx_is_not_retryable() {
87 let err = LogtailError::Http {
88 status: 400,
89 message: "bad request".to_string(),
90 };
91 assert!(!err.is_retryable());
92 }
93
94 #[test]
95 fn http_404_is_not_retryable() {
96 let err = LogtailError::Http {
97 status: 404,
98 message: "not found".to_string(),
99 };
100 assert!(!err.is_retryable());
101 }
102
103 #[test]
104 fn serialization_is_not_retryable() {
105 let serde_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
106 let err = LogtailError::Serialization(serde_err);
107 assert!(!err.is_retryable());
108 }
109
110 #[test]
113 fn retry_config_default_values() {
114 let config = RetryConfig::default();
115 assert_eq!(config.max_retries, 3);
116 assert_eq!(config.base_delay, Duration::from_secs(1));
117 assert_eq!(config.max_delay, Duration::from_secs(5));
118 assert!(config.jitter);
119 }
120}