sentinel_common/
types.rs

1//! Common type definitions for Sentinel proxy
2//!
3//! This module provides shared type definitions used throughout the platform,
4//! with a focus on type safety and operational clarity.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::str::FromStr;
9use uuid::Uuid;
10
11/// Unique correlation ID for request tracing across components
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct CorrelationId(String);
14
15impl CorrelationId {
16    /// Create a new random correlation ID
17    pub fn new() -> Self {
18        Self(Uuid::new_v4().to_string())
19    }
20
21    /// Create from an existing string
22    pub fn from_string(s: impl Into<String>) -> Self {
23        Self(s.into())
24    }
25
26    /// Get the inner string value
27    pub fn as_str(&self) -> &str {
28        &self.0
29    }
30
31    /// Convert to owned String
32    pub fn into_string(self) -> String {
33        self.0
34    }
35}
36
37impl Default for CorrelationId {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl fmt::Display for CorrelationId {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}", self.0)
46    }
47}
48
49impl From<String> for CorrelationId {
50    fn from(s: String) -> Self {
51        Self(s)
52    }
53}
54
55impl From<&str> for CorrelationId {
56    fn from(s: &str) -> Self {
57        Self(s.to_string())
58    }
59}
60
61/// Unique request ID for internal tracking
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub struct RequestId(String);
64
65impl RequestId {
66    /// Create a new random request ID
67    pub fn new() -> Self {
68        Self(Uuid::new_v4().to_string())
69    }
70
71    /// Get the inner string value
72    pub fn as_str(&self) -> &str {
73        &self.0
74    }
75}
76
77impl Default for RequestId {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl fmt::Display for RequestId {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        write!(f, "{}", self.0)
86    }
87}
88
89/// Route identifier
90#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
91pub struct RouteId(String);
92
93impl RouteId {
94    pub fn new(id: impl Into<String>) -> Self {
95        Self(id.into())
96    }
97
98    pub fn as_str(&self) -> &str {
99        &self.0
100    }
101}
102
103impl fmt::Display for RouteId {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(f, "{}", self.0)
106    }
107}
108
109/// Upstream identifier
110#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
111pub struct UpstreamId(String);
112
113impl UpstreamId {
114    pub fn new(id: impl Into<String>) -> Self {
115        Self(id.into())
116    }
117
118    pub fn as_str(&self) -> &str {
119        &self.0
120    }
121}
122
123impl fmt::Display for UpstreamId {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{}", self.0)
126    }
127}
128
129/// Agent identifier
130#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
131pub struct AgentId(String);
132
133impl AgentId {
134    pub fn new(id: impl Into<String>) -> Self {
135        Self(id.into())
136    }
137
138    pub fn as_str(&self) -> &str {
139        &self.0
140    }
141}
142
143impl fmt::Display for AgentId {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "{}", self.0)
146    }
147}
148
149/// HTTP method wrapper with validation
150#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
151pub enum HttpMethod {
152    GET,
153    POST,
154    PUT,
155    DELETE,
156    HEAD,
157    OPTIONS,
158    PATCH,
159    CONNECT,
160    TRACE,
161    #[serde(untagged)]
162    Custom(String),
163}
164
165impl FromStr for HttpMethod {
166    type Err = std::convert::Infallible;
167
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        Ok(match s.to_uppercase().as_str() {
170            "GET" => Self::GET,
171            "POST" => Self::POST,
172            "PUT" => Self::PUT,
173            "DELETE" => Self::DELETE,
174            "HEAD" => Self::HEAD,
175            "OPTIONS" => Self::OPTIONS,
176            "PATCH" => Self::PATCH,
177            "CONNECT" => Self::CONNECT,
178            "TRACE" => Self::TRACE,
179            other => Self::Custom(other.to_string()),
180        })
181    }
182}
183
184impl fmt::Display for HttpMethod {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        match self {
187            Self::GET => write!(f, "GET"),
188            Self::POST => write!(f, "POST"),
189            Self::PUT => write!(f, "PUT"),
190            Self::DELETE => write!(f, "DELETE"),
191            Self::HEAD => write!(f, "HEAD"),
192            Self::OPTIONS => write!(f, "OPTIONS"),
193            Self::PATCH => write!(f, "PATCH"),
194            Self::CONNECT => write!(f, "CONNECT"),
195            Self::TRACE => write!(f, "TRACE"),
196            Self::Custom(method) => write!(f, "{}", method),
197        }
198    }
199}
200
201/// TLS version
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
203pub enum TlsVersion {
204    #[serde(rename = "TLS1.2")]
205    Tls12,
206    #[serde(rename = "TLS1.3")]
207    Tls13,
208}
209
210impl fmt::Display for TlsVersion {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        match self {
213            Self::Tls12 => write!(f, "TLS1.2"),
214            Self::Tls13 => write!(f, "TLS1.3"),
215        }
216    }
217}
218
219/// Load balancing algorithm
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
221#[serde(rename_all = "snake_case")]
222pub enum LoadBalancingAlgorithm {
223    RoundRobin,
224    LeastConnections,
225    Random,
226    IpHash,
227    Weighted,
228    ConsistentHash,
229    PowerOfTwoChoices,
230    Adaptive,
231}
232
233/// Health check type
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(rename_all = "snake_case")]
236pub enum HealthCheckType {
237    Http {
238        path: String,
239        expected_status: u16,
240        #[serde(skip_serializing_if = "Option::is_none")]
241        host: Option<String>,
242    },
243    Tcp,
244    Grpc {
245        service: String,
246    },
247}
248
249/// Retry policy
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct RetryPolicy {
252    pub max_attempts: u32,
253    pub timeout_ms: u64,
254    pub backoff_base_ms: u64,
255    pub backoff_max_ms: u64,
256    pub retryable_status_codes: Vec<u16>,
257}
258
259impl Default for RetryPolicy {
260    fn default() -> Self {
261        Self {
262            max_attempts: 3,
263            timeout_ms: 30000,
264            backoff_base_ms: 100,
265            backoff_max_ms: 10000,
266            retryable_status_codes: vec![502, 503, 504],
267        }
268    }
269}
270
271/// Circuit breaker configuration
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct CircuitBreakerConfig {
274    pub failure_threshold: u32,
275    pub success_threshold: u32,
276    pub timeout_seconds: u64,
277    pub half_open_max_requests: u32,
278}
279
280impl Default for CircuitBreakerConfig {
281    fn default() -> Self {
282        Self {
283            failure_threshold: 5,
284            success_threshold: 2,
285            timeout_seconds: 30,
286            half_open_max_requests: 1,
287        }
288    }
289}
290
291/// Circuit breaker state
292#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
293#[serde(rename_all = "snake_case")]
294pub enum CircuitBreakerState {
295    Closed,
296    Open,
297    HalfOpen,
298}
299
300/// Request priority
301#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
302#[serde(rename_all = "snake_case")]
303pub enum Priority {
304    Low = 0,
305    Normal = 1,
306    High = 2,
307    Critical = 3,
308}
309
310impl Default for Priority {
311    fn default() -> Self {
312        Self::Normal
313    }
314}
315
316/// Time window for rate limiting and metrics
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
318pub struct TimeWindow {
319    pub seconds: u64,
320}
321
322impl TimeWindow {
323    pub fn new(seconds: u64) -> Self {
324        Self { seconds }
325    }
326
327    pub fn as_duration(&self) -> std::time::Duration {
328        std::time::Duration::from_secs(self.seconds)
329    }
330}
331
332/// Byte size with human-readable serialization
333#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
334pub struct ByteSize(pub usize);
335
336impl ByteSize {
337    pub const KB: usize = 1024;
338    pub const MB: usize = 1024 * 1024;
339    pub const GB: usize = 1024 * 1024 * 1024;
340
341    pub fn from_kb(kb: usize) -> Self {
342        Self(kb * Self::KB)
343    }
344
345    pub fn from_mb(mb: usize) -> Self {
346        Self(mb * Self::MB)
347    }
348
349    pub fn from_gb(gb: usize) -> Self {
350        Self(gb * Self::GB)
351    }
352
353    pub fn as_bytes(&self) -> usize {
354        self.0
355    }
356}
357
358impl fmt::Display for ByteSize {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        if self.0 >= Self::GB {
361            write!(f, "{:.2}GB", self.0 as f64 / Self::GB as f64)
362        } else if self.0 >= Self::MB {
363            write!(f, "{:.2}MB", self.0 as f64 / Self::MB as f64)
364        } else if self.0 >= Self::KB {
365            write!(f, "{:.2}KB", self.0 as f64 / Self::KB as f64)
366        } else {
367            write!(f, "{}B", self.0)
368        }
369    }
370}
371
372impl Serialize for ByteSize {
373    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
374    where
375        S: serde::Serializer,
376    {
377        serializer.serialize_str(&self.to_string())
378    }
379}
380
381impl<'de> Deserialize<'de> for ByteSize {
382    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
383    where
384        D: serde::Deserializer<'de>,
385    {
386        let s = String::deserialize(deserializer)?;
387        Self::from_str(&s).map_err(serde::de::Error::custom)
388    }
389}
390
391impl FromStr for ByteSize {
392    type Err = String;
393
394    fn from_str(s: &str) -> Result<Self, Self::Err> {
395        let s = s.trim();
396        if s.is_empty() {
397            return Err("Empty byte size string".to_string());
398        }
399
400        // Try to parse as plain number (bytes)
401        if let Ok(bytes) = s.parse::<usize>() {
402            return Ok(Self(bytes));
403        }
404
405        // Parse with unit suffix
406        let (num_part, unit_part) = s
407            .chars()
408            .position(|c| c.is_alphabetic())
409            .map(|i| s.split_at(i))
410            .ok_or_else(|| format!("Invalid byte size format: {}", s))?;
411
412        let value: f64 = num_part
413            .trim()
414            .parse()
415            .map_err(|_| format!("Invalid number: {}", num_part))?;
416
417        let multiplier = match unit_part.to_uppercase().as_str() {
418            "B" => 1,
419            "KB" | "K" => Self::KB,
420            "MB" | "M" => Self::MB,
421            "GB" | "G" => Self::GB,
422            _ => return Err(format!("Invalid unit: {}", unit_part)),
423        };
424
425        Ok(Self((value * multiplier as f64) as usize))
426    }
427}
428
429/// IP address wrapper with additional metadata
430#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
431pub struct ClientIp {
432    pub address: std::net::IpAddr,
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub forwarded_for: Option<Vec<std::net::IpAddr>>,
435}
436
437impl fmt::Display for ClientIp {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        write!(f, "{}", self.address)
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn test_correlation_id() {
449        let id1 = CorrelationId::new();
450        let id2 = CorrelationId::from_string("test-id");
451
452        assert_ne!(id1, id2);
453        assert_eq!(id2.as_str(), "test-id");
454    }
455
456    #[test]
457    fn test_http_method_parsing() {
458        assert_eq!(HttpMethod::from_str("GET").unwrap(), HttpMethod::GET);
459        assert_eq!(HttpMethod::from_str("post").unwrap(), HttpMethod::POST);
460        assert_eq!(
461            HttpMethod::from_str("PROPFIND").unwrap(),
462            HttpMethod::Custom("PROPFIND".to_string())
463        );
464    }
465
466    #[test]
467    fn test_byte_size_parsing() {
468        assert_eq!(ByteSize::from_str("1024").unwrap().0, 1024);
469        assert_eq!(ByteSize::from_str("10KB").unwrap().0, 10 * 1024);
470        assert_eq!(
471            ByteSize::from_str("5.5MB").unwrap().0,
472            (5.5 * 1024.0 * 1024.0) as usize
473        );
474        assert_eq!(ByteSize::from_str("2GB").unwrap().0, 2 * 1024 * 1024 * 1024);
475        assert_eq!(ByteSize::from_str("100 B").unwrap().0, 100);
476    }
477
478    #[test]
479    fn test_byte_size_display() {
480        assert_eq!(ByteSize(512).to_string(), "512B");
481        assert_eq!(ByteSize(2048).to_string(), "2.00KB");
482        assert_eq!(ByteSize(1024 * 1024).to_string(), "1.00MB");
483        assert_eq!(ByteSize(1024 * 1024 * 1024).to_string(), "1.00GB");
484    }
485}