1#[derive(Debug, thiserror::Error)]
8pub enum ProxyError {
9 #[error("unknown model: {0}")]
10 UnknownModel(String),
11 #[error("no healthy deployment for route {0}")]
12 NoHealthyDeployment(String),
13 #[error("upstream {status}: {body}")]
14 Upstream {
15 status: u16,
16 body: String,
17 retry_after_secs: Option<u64>,
20 },
21 #[error("rate limit exceeded")]
22 RateLimited,
23 #[error("budget exceeded")]
24 BudgetExceeded,
25 #[error("model not allowed for this key")]
26 ModelNotAllowed,
27 #[error("transport error: {0}")]
28 Transport(String),
29 #[error("invalid api key")]
30 Unauthorized,
31 #[error("bad request: {0}")]
32 BadRequest(String),
33 #[error("internal: {0}")]
34 Internal(#[from] anyhow::Error),
35}
36
37impl ProxyError {
38 pub fn upstream(status: u16, body: String) -> Self {
39 ProxyError::Upstream {
40 status,
41 body,
42 retry_after_secs: None,
43 }
44 }
45
46 pub fn log_status(&self) -> i64 {
51 match self {
52 ProxyError::Upstream { status, .. } => *status as i64,
53 ProxyError::RateLimited => 429,
54 ProxyError::Unauthorized => 401,
55 _ => 500,
56 }
57 }
58
59 pub fn http_status(&self) -> u16 {
63 match self {
64 ProxyError::Unauthorized => 401,
65 ProxyError::RateLimited => 429,
66 ProxyError::BudgetExceeded => 403,
67 ProxyError::ModelNotAllowed => 403,
68 ProxyError::UnknownModel(_) => 404,
69 ProxyError::NoHealthyDeployment(_) => 503,
70 ProxyError::Transport(_) => 502,
71 ProxyError::Upstream { status, .. } => *status,
72 ProxyError::BadRequest(_) => 400,
73 ProxyError::Internal(_) => 500,
74 }
75 }
76}
77
78#[cfg(feature = "axum")]
85pub fn error_response(err: &ProxyError) -> axum::response::Response {
86 use axum::Json;
87 use axum::http::StatusCode;
88 use axum::response::IntoResponse;
89
90 let status = StatusCode::from_u16(err.http_status()).unwrap_or(StatusCode::BAD_GATEWAY);
91 let code = match err {
92 ProxyError::Unauthorized => "invalid_api_key",
93 ProxyError::RateLimited => "rate_limit_exceeded",
94 ProxyError::BudgetExceeded => "budget_exceeded",
95 ProxyError::ModelNotAllowed => "model_not_allowed",
96 ProxyError::UnknownModel(_) => "model_not_found",
97 ProxyError::NoHealthyDeployment(_) => "no_healthy_deployment",
98 ProxyError::Transport(_) => "upstream_transport",
99 ProxyError::Upstream { .. } => "upstream_error",
100 ProxyError::BadRequest(_) => "bad_request",
101 ProxyError::Internal(_) => "internal_error",
102 };
103 let message = match err {
104 ProxyError::Upstream { status, .. } => {
105 format!("upstream returned status {status}")
106 }
107 _ => err.to_string(),
108 };
109 let ty = match err {
110 ProxyError::Upstream { .. } => "upstream_error",
111 ProxyError::Transport(_) => "server_error",
112 ProxyError::Internal(_) => "server_error",
113 ProxyError::BadRequest(_) | ProxyError::UnknownModel(_) => "invalid_request_error",
114 _ => "authentication_error",
115 };
116 (
117 status,
118 Json(serde_json::json!({
119 "error": { "message": message, "type": ty, "code": code, "param": null }
120 })),
121 )
122 .into_response()
123}