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//!
6//! For identifier types (CorrelationId, RequestId, etc.), see the `ids` module.
7
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use std::str::FromStr;
11
12/// HTTP method wrapper with validation
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum HttpMethod {
15    GET,
16    POST,
17    PUT,
18    DELETE,
19    HEAD,
20    OPTIONS,
21    PATCH,
22    CONNECT,
23    TRACE,
24    #[serde(untagged)]
25    Custom(String),
26}
27
28impl FromStr for HttpMethod {
29    type Err = std::convert::Infallible;
30
31    fn from_str(s: &str) -> Result<Self, Self::Err> {
32        Ok(match s.to_uppercase().as_str() {
33            "GET" => Self::GET,
34            "POST" => Self::POST,
35            "PUT" => Self::PUT,
36            "DELETE" => Self::DELETE,
37            "HEAD" => Self::HEAD,
38            "OPTIONS" => Self::OPTIONS,
39            "PATCH" => Self::PATCH,
40            "CONNECT" => Self::CONNECT,
41            "TRACE" => Self::TRACE,
42            other => Self::Custom(other.to_string()),
43        })
44    }
45}
46
47impl fmt::Display for HttpMethod {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::GET => write!(f, "GET"),
51            Self::POST => write!(f, "POST"),
52            Self::PUT => write!(f, "PUT"),
53            Self::DELETE => write!(f, "DELETE"),
54            Self::HEAD => write!(f, "HEAD"),
55            Self::OPTIONS => write!(f, "OPTIONS"),
56            Self::PATCH => write!(f, "PATCH"),
57            Self::CONNECT => write!(f, "CONNECT"),
58            Self::TRACE => write!(f, "TRACE"),
59            Self::Custom(method) => write!(f, "{}", method),
60        }
61    }
62}
63
64/// TLS version
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub enum TlsVersion {
67    #[serde(rename = "TLS1.2")]
68    Tls12,
69    #[serde(rename = "TLS1.3")]
70    Tls13,
71}
72
73impl fmt::Display for TlsVersion {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::Tls12 => write!(f, "TLS1.2"),
77            Self::Tls13 => write!(f, "TLS1.3"),
78        }
79    }
80}
81
82/// Trace ID format selection.
83///
84/// Controls how trace IDs are generated for request tracing.
85///
86/// # Formats
87///
88/// - **TinyFlake** (default): 11-character Base58 encoded ID with time prefix.
89///   Operator-friendly format designed for easy copying and log correlation.
90///   Example: `k7BxR3nVp2Ym`
91///
92/// - **UUID**: Standard 36-character UUID v4 format with dashes.
93///   Guaranteed unique, widely compatible.
94///   Example: `550e8400-e29b-41d4-a716-446655440000`
95///
96/// # Configuration
97///
98/// ```kdl
99/// server {
100///     trace-id-format "tinyflake"  // or "uuid"
101/// }
102/// ```
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
104#[serde(rename_all = "lowercase")]
105pub enum TraceIdFormat {
106    /// TinyFlake format: 11-char Base58, time-prefixed (default)
107    #[default]
108    TinyFlake,
109
110    /// UUID v4 format: 36-char with dashes
111    Uuid,
112}
113
114impl TraceIdFormat {
115    /// Parse format from string (case-insensitive)
116    pub fn from_str_loose(s: &str) -> Self {
117        match s.to_lowercase().as_str() {
118            "uuid" | "uuid4" | "uuidv4" => TraceIdFormat::Uuid,
119            _ => TraceIdFormat::TinyFlake, // Default to TinyFlake
120        }
121    }
122}
123
124impl fmt::Display for TraceIdFormat {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            TraceIdFormat::TinyFlake => write!(f, "tinyflake"),
128            TraceIdFormat::Uuid => write!(f, "uuid"),
129        }
130    }
131}
132
133/// Load balancing algorithm
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
135#[serde(rename_all = "snake_case")]
136pub enum LoadBalancingAlgorithm {
137    RoundRobin,
138    LeastConnections,
139    Random,
140    IpHash,
141    Weighted,
142    ConsistentHash,
143    PowerOfTwoChoices,
144    Adaptive,
145}
146
147/// Health check type
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(rename_all = "snake_case")]
150pub enum HealthCheckType {
151    Http {
152        path: String,
153        expected_status: u16,
154        #[serde(skip_serializing_if = "Option::is_none")]
155        host: Option<String>,
156    },
157    Tcp,
158    Grpc {
159        service: String,
160    },
161}
162
163/// Retry policy
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct RetryPolicy {
166    pub max_attempts: u32,
167    pub timeout_ms: u64,
168    pub backoff_base_ms: u64,
169    pub backoff_max_ms: u64,
170    pub retryable_status_codes: Vec<u16>,
171}
172
173impl Default for RetryPolicy {
174    fn default() -> Self {
175        Self {
176            max_attempts: 3,
177            timeout_ms: 30000,
178            backoff_base_ms: 100,
179            backoff_max_ms: 10000,
180            retryable_status_codes: vec![502, 503, 504],
181        }
182    }
183}
184
185/// Circuit breaker configuration
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct CircuitBreakerConfig {
188    pub failure_threshold: u32,
189    pub success_threshold: u32,
190    pub timeout_seconds: u64,
191    pub half_open_max_requests: u32,
192}
193
194impl Default for CircuitBreakerConfig {
195    fn default() -> Self {
196        Self {
197            failure_threshold: 5,
198            success_threshold: 2,
199            timeout_seconds: 30,
200            half_open_max_requests: 1,
201        }
202    }
203}
204
205/// Circuit breaker state
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207#[serde(rename_all = "snake_case")]
208pub enum CircuitBreakerState {
209    Closed,
210    Open,
211    HalfOpen,
212}
213
214/// Request priority
215#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
216#[serde(rename_all = "snake_case")]
217pub enum Priority {
218    Low = 0,
219    Normal = 1,
220    High = 2,
221    Critical = 3,
222}
223
224impl Default for Priority {
225    fn default() -> Self {
226        Self::Normal
227    }
228}
229
230/// Time window for rate limiting and metrics
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232pub struct TimeWindow {
233    pub seconds: u64,
234}
235
236impl TimeWindow {
237    pub fn new(seconds: u64) -> Self {
238        Self { seconds }
239    }
240
241    pub fn as_duration(&self) -> std::time::Duration {
242        std::time::Duration::from_secs(self.seconds)
243    }
244}
245
246/// Byte size with human-readable serialization
247#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
248pub struct ByteSize(pub usize);
249
250impl ByteSize {
251    pub const KB: usize = 1024;
252    pub const MB: usize = 1024 * 1024;
253    pub const GB: usize = 1024 * 1024 * 1024;
254
255    pub fn from_kb(kb: usize) -> Self {
256        Self(kb * Self::KB)
257    }
258
259    pub fn from_mb(mb: usize) -> Self {
260        Self(mb * Self::MB)
261    }
262
263    pub fn from_gb(gb: usize) -> Self {
264        Self(gb * Self::GB)
265    }
266
267    pub fn as_bytes(&self) -> usize {
268        self.0
269    }
270}
271
272impl fmt::Display for ByteSize {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        if self.0 >= Self::GB {
275            write!(f, "{:.2}GB", self.0 as f64 / Self::GB as f64)
276        } else if self.0 >= Self::MB {
277            write!(f, "{:.2}MB", self.0 as f64 / Self::MB as f64)
278        } else if self.0 >= Self::KB {
279            write!(f, "{:.2}KB", self.0 as f64 / Self::KB as f64)
280        } else {
281            write!(f, "{}B", self.0)
282        }
283    }
284}
285
286impl Serialize for ByteSize {
287    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
288    where
289        S: serde::Serializer,
290    {
291        serializer.serialize_str(&self.to_string())
292    }
293}
294
295impl<'de> Deserialize<'de> for ByteSize {
296    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
297    where
298        D: serde::Deserializer<'de>,
299    {
300        let s = String::deserialize(deserializer)?;
301        Self::from_str(&s).map_err(serde::de::Error::custom)
302    }
303}
304
305impl FromStr for ByteSize {
306    type Err = String;
307
308    fn from_str(s: &str) -> Result<Self, Self::Err> {
309        let s = s.trim();
310        if s.is_empty() {
311            return Err("Empty byte size string".to_string());
312        }
313
314        // Try to parse as plain number (bytes)
315        if let Ok(bytes) = s.parse::<usize>() {
316            return Ok(Self(bytes));
317        }
318
319        // Parse with unit suffix
320        let (num_part, unit_part) = s
321            .chars()
322            .position(|c| c.is_alphabetic())
323            .map(|i| s.split_at(i))
324            .ok_or_else(|| format!("Invalid byte size format: {}", s))?;
325
326        let value: f64 = num_part
327            .trim()
328            .parse()
329            .map_err(|_| format!("Invalid number: {}", num_part))?;
330
331        let multiplier = match unit_part.to_uppercase().as_str() {
332            "B" => 1,
333            "KB" | "K" => Self::KB,
334            "MB" | "M" => Self::MB,
335            "GB" | "G" => Self::GB,
336            _ => return Err(format!("Invalid unit: {}", unit_part)),
337        };
338
339        Ok(Self((value * multiplier as f64) as usize))
340    }
341}
342
343/// IP address wrapper with additional metadata
344#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
345pub struct ClientIp {
346    pub address: std::net::IpAddr,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub forwarded_for: Option<Vec<std::net::IpAddr>>,
349}
350
351impl fmt::Display for ClientIp {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        write!(f, "{}", self.address)
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    #[test]
362    fn test_http_method_parsing() {
363        assert_eq!(HttpMethod::from_str("GET").unwrap(), HttpMethod::GET);
364        assert_eq!(HttpMethod::from_str("post").unwrap(), HttpMethod::POST);
365        assert_eq!(
366            HttpMethod::from_str("PROPFIND").unwrap(),
367            HttpMethod::Custom("PROPFIND".to_string())
368        );
369    }
370
371    #[test]
372    fn test_byte_size_parsing() {
373        assert_eq!(ByteSize::from_str("1024").unwrap().0, 1024);
374        assert_eq!(ByteSize::from_str("10KB").unwrap().0, 10 * 1024);
375        assert_eq!(
376            ByteSize::from_str("5.5MB").unwrap().0,
377            (5.5 * 1024.0 * 1024.0) as usize
378        );
379        assert_eq!(ByteSize::from_str("2GB").unwrap().0, 2 * 1024 * 1024 * 1024);
380        assert_eq!(ByteSize::from_str("100 B").unwrap().0, 100);
381    }
382
383    #[test]
384    fn test_byte_size_display() {
385        assert_eq!(ByteSize(512).to_string(), "512B");
386        assert_eq!(ByteSize(2048).to_string(), "2.00KB");
387        assert_eq!(ByteSize(1024 * 1024).to_string(), "1.00MB");
388        assert_eq!(ByteSize(1024 * 1024 * 1024).to_string(), "1.00GB");
389    }
390
391    #[test]
392    fn test_trace_id_format() {
393        assert_eq!(TraceIdFormat::from_str_loose("uuid"), TraceIdFormat::Uuid);
394        assert_eq!(
395            TraceIdFormat::from_str_loose("tinyflake"),
396            TraceIdFormat::TinyFlake
397        );
398        assert_eq!(
399            TraceIdFormat::from_str_loose("unknown"),
400            TraceIdFormat::TinyFlake
401        );
402    }
403}