1use serde::{Deserialize, Serialize};
9use std::fmt;
10use std::str::FromStr;
11use std::time::Duration;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum HttpMethod {
16 GET,
17 POST,
18 PUT,
19 DELETE,
20 HEAD,
21 OPTIONS,
22 PATCH,
23 CONNECT,
24 TRACE,
25 #[serde(untagged)]
26 Custom(String),
27}
28
29impl FromStr for HttpMethod {
30 type Err = std::convert::Infallible;
31
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 Ok(match s.to_uppercase().as_str() {
34 "GET" => Self::GET,
35 "POST" => Self::POST,
36 "PUT" => Self::PUT,
37 "DELETE" => Self::DELETE,
38 "HEAD" => Self::HEAD,
39 "OPTIONS" => Self::OPTIONS,
40 "PATCH" => Self::PATCH,
41 "CONNECT" => Self::CONNECT,
42 "TRACE" => Self::TRACE,
43 other => Self::Custom(other.to_string()),
44 })
45 }
46}
47
48impl fmt::Display for HttpMethod {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::GET => write!(f, "GET"),
52 Self::POST => write!(f, "POST"),
53 Self::PUT => write!(f, "PUT"),
54 Self::DELETE => write!(f, "DELETE"),
55 Self::HEAD => write!(f, "HEAD"),
56 Self::OPTIONS => write!(f, "OPTIONS"),
57 Self::PATCH => write!(f, "PATCH"),
58 Self::CONNECT => write!(f, "CONNECT"),
59 Self::TRACE => write!(f, "TRACE"),
60 Self::Custom(method) => write!(f, "{}", method),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub enum TlsVersion {
68 #[serde(rename = "TLS1.2")]
69 Tls12,
70 #[serde(rename = "TLS1.3")]
71 Tls13,
72}
73
74impl fmt::Display for TlsVersion {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::Tls12 => write!(f, "TLS1.2"),
78 Self::Tls13 => write!(f, "TLS1.3"),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum TraceIdFormat {
107 #[default]
109 TinyFlake,
110
111 Uuid,
113}
114
115impl TraceIdFormat {
116 pub fn from_str_loose(s: &str) -> Self {
118 match s.to_lowercase().as_str() {
119 "uuid" | "uuid4" | "uuidv4" => TraceIdFormat::Uuid,
120 _ => TraceIdFormat::TinyFlake, }
122 }
123}
124
125impl fmt::Display for TraceIdFormat {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self {
128 TraceIdFormat::TinyFlake => write!(f, "tinyflake"),
129 TraceIdFormat::Uuid => write!(f, "uuid"),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
136#[serde(rename_all = "snake_case")]
137pub enum LoadBalancingAlgorithm {
138 RoundRobin,
139 LeastConnections,
140 Random,
141 IpHash,
142 Weighted,
143 ConsistentHash,
144 PowerOfTwoChoices,
145 Adaptive,
146 LeastTokensQueued,
152 Maglev,
158 LocalityAware,
164 PeakEwma,
170 DeterministicSubset,
176 WeightedLeastConnections,
182 Sticky,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(rename_all = "snake_case")]
194pub enum HealthCheckType {
195 Http {
196 path: String,
197 expected_status: u16,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 host: Option<String>,
200 },
201 Tcp,
202 Grpc {
203 service: String,
204 },
205 Inference {
211 endpoint: String,
213 #[serde(default, skip_serializing_if = "Vec::is_empty")]
215 expected_models: Vec<String>,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
218 readiness: Option<Box<crate::inference::InferenceReadinessConfig>>,
219 },
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct RetryPolicy {
225 pub max_attempts: u32,
227
228 #[serde(default)]
236 pub retryable_status_codes: Vec<u16>,
237
238 #[serde(default = "default_retry_backoff")]
241 pub backoff: Duration,
242
243 #[serde(default = "default_retry_max_backoff")]
245 pub max_backoff: Duration,
246
247 #[serde(default)]
253 pub per_attempt_timeout: Option<Duration>,
254
255 #[serde(default)]
260 pub retry_non_idempotent: bool,
261}
262
263fn default_retry_backoff() -> Duration {
264 Duration::from_millis(100)
265}
266
267fn default_retry_max_backoff() -> Duration {
268 Duration::from_secs(2)
269}
270
271impl Default for RetryPolicy {
272 fn default() -> Self {
273 Self {
274 max_attempts: 3,
275 retryable_status_codes: Vec::new(),
276 backoff: default_retry_backoff(),
277 max_backoff: default_retry_max_backoff(),
278 per_attempt_timeout: None,
279 retry_non_idempotent: false,
280 }
281 }
282}
283
284impl RetryPolicy {
285 pub fn backoff_for(&self, attempt: u32) -> Duration {
288 if attempt <= 1 {
289 return Duration::ZERO;
290 }
291 let doublings = attempt.saturating_sub(2).min(16);
292 let scaled = self
293 .backoff
294 .saturating_mul(1u32.checked_shl(doublings).unwrap_or(u32::MAX));
295 scaled.min(self.max_backoff)
296 }
297
298 pub fn is_retryable_status(&self, status: u16) -> bool {
300 self.retryable_status_codes.contains(&status)
301 }
302
303 pub fn may_retry_method(&self, method: &str) -> bool {
309 if self.retry_non_idempotent {
310 return true;
311 }
312 matches!(
313 method.to_ascii_uppercase().as_str(),
314 "GET" | "HEAD" | "PUT" | "DELETE" | "OPTIONS" | "TRACE"
315 )
316 }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
321pub struct CircuitBreakerConfig {
322 pub failure_threshold: u32,
323 pub success_threshold: u32,
324 pub timeout_seconds: u64,
325 pub half_open_max_requests: u32,
326}
327
328impl Default for CircuitBreakerConfig {
329 fn default() -> Self {
330 Self {
331 failure_threshold: 5,
332 success_threshold: 2,
333 timeout_seconds: 30,
334 half_open_max_requests: 1,
335 }
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
341#[serde(rename_all = "snake_case")]
342pub enum CircuitBreakerState {
343 Closed,
344 Open,
345 HalfOpen,
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
371#[serde(transparent)]
372pub struct Priority(pub i32);
373
374impl Priority {
375 pub const LOW: Self = Self(10);
377 pub const NORMAL: Self = Self(50);
380 pub const HIGH: Self = Self(100);
382 pub const CRITICAL: Self = Self(1000);
385
386 #[inline]
388 pub const fn as_i32(self) -> i32 {
389 self.0
390 }
391}
392
393impl Default for Priority {
394 fn default() -> Self {
395 Self::NORMAL
396 }
397}
398
399impl std::fmt::Display for Priority {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 write!(f, "{}", self.0)
402 }
403}
404
405impl From<i32> for Priority {
406 fn from(value: i32) -> Self {
407 Self(value)
408 }
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
413pub struct TimeWindow {
414 pub seconds: u64,
415}
416
417impl TimeWindow {
418 pub fn new(seconds: u64) -> Self {
419 Self { seconds }
420 }
421
422 pub fn as_duration(&self) -> std::time::Duration {
423 std::time::Duration::from_secs(self.seconds)
424 }
425}
426
427#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
429pub struct ByteSize(pub usize);
430
431impl ByteSize {
432 pub const KB: usize = 1024;
433 pub const MB: usize = 1024 * 1024;
434 pub const GB: usize = 1024 * 1024 * 1024;
435
436 pub fn from_kb(kb: usize) -> Self {
437 Self(kb * Self::KB)
438 }
439
440 pub fn from_mb(mb: usize) -> Self {
441 Self(mb * Self::MB)
442 }
443
444 pub fn from_gb(gb: usize) -> Self {
445 Self(gb * Self::GB)
446 }
447
448 pub fn as_bytes(&self) -> usize {
449 self.0
450 }
451}
452
453impl fmt::Display for ByteSize {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 if self.0 >= Self::GB {
456 write!(f, "{:.2}GB", self.0 as f64 / Self::GB as f64)
457 } else if self.0 >= Self::MB {
458 write!(f, "{:.2}MB", self.0 as f64 / Self::MB as f64)
459 } else if self.0 >= Self::KB {
460 write!(f, "{:.2}KB", self.0 as f64 / Self::KB as f64)
461 } else {
462 write!(f, "{}B", self.0)
463 }
464 }
465}
466
467impl Serialize for ByteSize {
468 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
469 where
470 S: serde::Serializer,
471 {
472 serializer.serialize_str(&self.to_string())
473 }
474}
475
476impl<'de> Deserialize<'de> for ByteSize {
477 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
478 where
479 D: serde::Deserializer<'de>,
480 {
481 let s = String::deserialize(deserializer)?;
482 Self::from_str(&s).map_err(serde::de::Error::custom)
483 }
484}
485
486impl FromStr for ByteSize {
487 type Err = String;
488
489 fn from_str(s: &str) -> Result<Self, Self::Err> {
490 let s = s.trim();
491 if s.is_empty() {
492 return Err("Empty byte size string".to_string());
493 }
494
495 if let Ok(bytes) = s.parse::<usize>() {
497 return Ok(Self(bytes));
498 }
499
500 let (num_part, unit_part) = s
502 .chars()
503 .position(|c| c.is_alphabetic())
504 .map(|i| s.split_at(i))
505 .ok_or_else(|| format!("Invalid byte size format: {}", s))?;
506
507 let value: f64 = num_part
508 .trim()
509 .parse()
510 .map_err(|_| format!("Invalid number: {}", num_part))?;
511
512 let multiplier = match unit_part.to_uppercase().as_str() {
513 "B" => 1,
514 "KB" | "K" => Self::KB,
515 "MB" | "M" => Self::MB,
516 "GB" | "G" => Self::GB,
517 _ => return Err(format!("Invalid unit: {}", unit_part)),
518 };
519
520 Ok(Self((value * multiplier as f64) as usize))
521 }
522}
523
524#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
526pub struct ClientIp {
527 pub address: std::net::IpAddr,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub forwarded_for: Option<Vec<std::net::IpAddr>>,
530}
531
532impl fmt::Display for ClientIp {
533 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
534 write!(f, "{}", self.address)
535 }
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541
542 #[test]
543 fn test_http_method_parsing() {
544 assert_eq!(HttpMethod::from_str("GET").unwrap(), HttpMethod::GET);
545 assert_eq!(HttpMethod::from_str("post").unwrap(), HttpMethod::POST);
546 assert_eq!(
547 HttpMethod::from_str("PROPFIND").unwrap(),
548 HttpMethod::Custom("PROPFIND".to_string())
549 );
550 }
551
552 #[test]
553 fn test_byte_size_parsing() {
554 assert_eq!(ByteSize::from_str("1024").unwrap().0, 1024);
555 assert_eq!(ByteSize::from_str("10KB").unwrap().0, 10 * 1024);
556 assert_eq!(
557 ByteSize::from_str("5.5MB").unwrap().0,
558 (5.5 * 1024.0 * 1024.0) as usize
559 );
560 assert_eq!(ByteSize::from_str("2GB").unwrap().0, 2 * 1024 * 1024 * 1024);
561 assert_eq!(ByteSize::from_str("100 B").unwrap().0, 100);
562 }
563
564 #[test]
565 fn test_byte_size_display() {
566 assert_eq!(ByteSize(512).to_string(), "512B");
567 assert_eq!(ByteSize(2048).to_string(), "2.00KB");
568 assert_eq!(ByteSize(1024 * 1024).to_string(), "1.00MB");
569 assert_eq!(ByteSize(1024 * 1024 * 1024).to_string(), "1.00GB");
570 }
571
572 #[test]
573 fn test_trace_id_format() {
574 assert_eq!(TraceIdFormat::from_str_loose("uuid"), TraceIdFormat::Uuid);
575 assert_eq!(
576 TraceIdFormat::from_str_loose("tinyflake"),
577 TraceIdFormat::TinyFlake
578 );
579 assert_eq!(
580 TraceIdFormat::from_str_loose("unknown"),
581 TraceIdFormat::TinyFlake
582 );
583 }
584}
585
586#[cfg(test)]
587mod retry_policy_tests {
588 use super::*;
589
590 fn policy() -> RetryPolicy {
591 RetryPolicy {
592 backoff: Duration::from_millis(100),
593 max_backoff: Duration::from_secs(2),
594 ..RetryPolicy::default()
595 }
596 }
597
598 #[test]
604 fn backoff_doubles_from_the_second_attempt() {
605 let p = policy();
606 assert_eq!(p.backoff_for(1), Duration::ZERO);
607 assert_eq!(p.backoff_for(2), Duration::from_millis(100));
608 assert_eq!(p.backoff_for(3), Duration::from_millis(200));
609 assert_eq!(p.backoff_for(4), Duration::from_millis(400));
610 }
611
612 #[test]
613 fn backoff_is_capped() {
614 let p = policy();
615 assert_eq!(p.backoff_for(20), Duration::from_secs(2));
616 assert_eq!(p.backoff_for(u32::MAX), Duration::from_secs(2));
618 }
619
620 #[test]
621 fn zero_backoff_stays_zero() {
622 let p = RetryPolicy {
623 backoff: Duration::ZERO,
624 ..RetryPolicy::default()
625 };
626 assert_eq!(p.backoff_for(3), Duration::ZERO);
627 }
628
629 #[test]
630 fn only_configured_status_codes_are_retryable() {
631 let p = RetryPolicy {
632 retryable_status_codes: vec![502, 503],
633 ..RetryPolicy::default()
634 };
635 assert!(p.is_retryable_status(503));
636 assert!(!p.is_retryable_status(500));
637 assert!(!p.is_retryable_status(200));
638 }
639
640 #[test]
643 fn no_status_code_is_retryable_by_default() {
644 let p = RetryPolicy::default();
645 for status in [500, 502, 503, 504] {
646 assert!(!p.is_retryable_status(status));
647 }
648 }
649
650 #[test]
651 fn only_idempotent_methods_are_replayed_by_default() {
652 let p = RetryPolicy::default();
653 for method in ["GET", "HEAD", "PUT", "DELETE", "OPTIONS", "TRACE", "get"] {
654 assert!(p.may_retry_method(method), "{method} should be retryable");
655 }
656 for method in ["POST", "PATCH", "post", "LOCK"] {
657 assert!(
658 !p.may_retry_method(method),
659 "{method} must not be replayed without opting in"
660 );
661 }
662 }
663
664 #[test]
665 fn opting_in_allows_replaying_any_method() {
666 let p = RetryPolicy {
667 retry_non_idempotent: true,
668 ..RetryPolicy::default()
669 };
670 assert!(p.may_retry_method("POST"));
671 assert!(p.may_retry_method("PATCH"));
672 }
673}