Skip to main content

litellm_rs/core/router/
error.rs

1//! Router error types
2//!
3//! This module defines error types for the router system including
4//! routing errors and cooldown triggers.
5
6/// Cooldown trigger reason
7///
8/// Defines the reasons why a deployment enters cooldown state.
9/// Different reasons may have different cooldown behaviors and durations.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CooldownReason {
12    /// Rate limit (429) - immediate cooldown
13    RateLimit,
14    /// Authentication error (401) - immediate cooldown
15    AuthError,
16    /// Not found (404) - immediate cooldown
17    NotFound,
18    /// Timeout (408) - immediate cooldown
19    Timeout,
20    /// Consecutive failures exceeded threshold
21    ConsecutiveFailures,
22    /// High failure rate (>50%)
23    HighFailureRate,
24    /// Manual cooldown
25    Manual,
26}
27
28/// Router error types
29///
30/// Defines errors that can occur during routing operations.
31#[derive(Debug, Clone, thiserror::Error)]
32pub enum RouterError {
33    /// Model not found in router configuration
34    #[error("Model not found: {0}")]
35    ModelNotFound(String),
36
37    /// No available deployment for the requested model
38    #[error("No available deployment for model: {0}")]
39    NoAvailableDeployment(String),
40
41    /// Deployment not found by ID
42    #[error("Deployment not found: {0}")]
43    DeploymentNotFound(String),
44
45    /// All deployments are in cooldown state
46    #[error("All deployments in cooldown for model: {0}")]
47    AllDeploymentsInCooldown(String),
48
49    /// Rate limit exceeded for model
50    #[error("Rate limit exceeded for model: {0}")]
51    RateLimitExceeded(String),
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    // ==================== CooldownReason Tests ====================
59
60    #[test]
61    fn test_cooldown_reason_rate_limit() {
62        let reason = CooldownReason::RateLimit;
63        assert_eq!(reason, CooldownReason::RateLimit);
64    }
65
66    #[test]
67    fn test_cooldown_reason_auth_error() {
68        let reason = CooldownReason::AuthError;
69        assert_eq!(reason, CooldownReason::AuthError);
70    }
71
72    #[test]
73    fn test_cooldown_reason_not_found() {
74        let reason = CooldownReason::NotFound;
75        assert_eq!(reason, CooldownReason::NotFound);
76    }
77
78    #[test]
79    fn test_cooldown_reason_timeout() {
80        let reason = CooldownReason::Timeout;
81        assert_eq!(reason, CooldownReason::Timeout);
82    }
83
84    #[test]
85    fn test_cooldown_reason_consecutive_failures() {
86        let reason = CooldownReason::ConsecutiveFailures;
87        assert_eq!(reason, CooldownReason::ConsecutiveFailures);
88    }
89
90    #[test]
91    fn test_cooldown_reason_high_failure_rate() {
92        let reason = CooldownReason::HighFailureRate;
93        assert_eq!(reason, CooldownReason::HighFailureRate);
94    }
95
96    #[test]
97    fn test_cooldown_reason_manual() {
98        let reason = CooldownReason::Manual;
99        assert_eq!(reason, CooldownReason::Manual);
100    }
101
102    #[test]
103    fn test_cooldown_reason_clone() {
104        let reason = CooldownReason::RateLimit;
105        let cloned = reason;
106        assert_eq!(reason, cloned);
107    }
108
109    #[test]
110    fn test_cooldown_reason_copy() {
111        let reason = CooldownReason::Timeout;
112        let copied = reason;
113        assert_eq!(reason, copied);
114    }
115
116    #[test]
117    fn test_cooldown_reason_debug() {
118        let reason = CooldownReason::ConsecutiveFailures;
119        let debug_str = format!("{:?}", reason);
120        assert_eq!(debug_str, "ConsecutiveFailures");
121    }
122
123    #[test]
124    fn test_cooldown_reason_equality() {
125        assert_eq!(CooldownReason::RateLimit, CooldownReason::RateLimit);
126        assert_ne!(CooldownReason::RateLimit, CooldownReason::AuthError);
127    }
128
129    #[test]
130    fn test_cooldown_reason_all_variants() {
131        let reasons = [
132            CooldownReason::RateLimit,
133            CooldownReason::AuthError,
134            CooldownReason::NotFound,
135            CooldownReason::Timeout,
136            CooldownReason::ConsecutiveFailures,
137            CooldownReason::HighFailureRate,
138            CooldownReason::Manual,
139        ];
140
141        assert_eq!(reasons.len(), 7);
142        // Verify all are unique
143        for (i, r1) in reasons.iter().enumerate() {
144            for (j, r2) in reasons.iter().enumerate() {
145                if i != j {
146                    assert_ne!(r1, r2);
147                }
148            }
149        }
150    }
151
152    // ==================== RouterError Tests ====================
153
154    #[test]
155    fn test_router_error_model_not_found() {
156        let error = RouterError::ModelNotFound("gpt-5".to_string());
157        assert_eq!(error.to_string(), "Model not found: gpt-5");
158    }
159
160    #[test]
161    fn test_router_error_no_available_deployment() {
162        let error = RouterError::NoAvailableDeployment("gpt-4".to_string());
163        assert_eq!(
164            error.to_string(),
165            "No available deployment for model: gpt-4"
166        );
167    }
168
169    #[test]
170    fn test_router_error_deployment_not_found() {
171        let error = RouterError::DeploymentNotFound("dep-123".to_string());
172        assert_eq!(error.to_string(), "Deployment not found: dep-123");
173    }
174
175    #[test]
176    fn test_router_error_all_deployments_in_cooldown() {
177        let error = RouterError::AllDeploymentsInCooldown("claude-3".to_string());
178        assert_eq!(
179            error.to_string(),
180            "All deployments in cooldown for model: claude-3"
181        );
182    }
183
184    #[test]
185    fn test_router_error_rate_limit_exceeded() {
186        let error = RouterError::RateLimitExceeded("gpt-4-turbo".to_string());
187        assert_eq!(
188            error.to_string(),
189            "Rate limit exceeded for model: gpt-4-turbo"
190        );
191    }
192
193    #[test]
194    fn test_router_error_clone() {
195        let error = RouterError::ModelNotFound("test".to_string());
196        let cloned = error.clone();
197        assert_eq!(error.to_string(), cloned.to_string());
198    }
199
200    #[test]
201    fn test_router_error_debug() {
202        let error = RouterError::RateLimitExceeded("model".to_string());
203        let debug_str = format!("{:?}", error);
204        assert!(debug_str.contains("RateLimitExceeded"));
205        assert!(debug_str.contains("model"));
206    }
207
208    #[test]
209    fn test_router_error_is_error_trait() {
210        let error = RouterError::ModelNotFound("test".to_string());
211        let _: &dyn std::error::Error = &error;
212    }
213
214    #[test]
215    fn test_router_error_empty_string() {
216        let error = RouterError::ModelNotFound("".to_string());
217        assert_eq!(error.to_string(), "Model not found: ");
218    }
219
220    #[test]
221    fn test_router_error_special_characters() {
222        let error = RouterError::ModelNotFound("model/with:special-chars".to_string());
223        assert_eq!(
224            error.to_string(),
225            "Model not found: model/with:special-chars"
226        );
227    }
228
229    #[test]
230    fn test_router_error_unicode() {
231        let error = RouterError::DeploymentNotFound("部署-123".to_string());
232        assert_eq!(error.to_string(), "Deployment not found: 部署-123");
233    }
234
235    #[test]
236    fn test_router_error_all_variants() {
237        let errors = vec![
238            RouterError::ModelNotFound("a".to_string()),
239            RouterError::NoAvailableDeployment("b".to_string()),
240            RouterError::DeploymentNotFound("c".to_string()),
241            RouterError::AllDeploymentsInCooldown("d".to_string()),
242            RouterError::RateLimitExceeded("e".to_string()),
243        ];
244
245        assert_eq!(errors.len(), 5);
246        for error in errors {
247            assert!(!error.to_string().is_empty());
248        }
249    }
250}