Skip to main content

bitrouter_api/
error.rs

1#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
2use std::fmt;
3
4#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
5use bitrouter_core::errors::BitrouterError;
6#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
7use warp::reject::Reject;
8
9/// Wraps a [`BitrouterError`] so it can be used as a warp rejection.
10#[derive(Debug)]
11#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
12pub(crate) struct BitrouterRejection(pub BitrouterError);
13
14#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
15impl fmt::Display for BitrouterRejection {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        write!(f, "{}", self.0)
18    }
19}
20
21#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
22impl Reject for BitrouterRejection {}
23
24/// Wraps a generic message as a warp rejection.
25#[derive(Debug)]
26#[cfg(feature = "openai")]
27pub(crate) struct BadRequest(pub String);
28
29#[cfg(feature = "openai")]
30impl fmt::Display for BadRequest {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "{}", self.0)
33    }
34}
35
36#[cfg(feature = "openai")]
37impl Reject for BadRequest {}
38
39/// Converts a [`BitrouterRejection`] or [`BadRequest`] warp rejection into a
40/// structured JSON error response.
41///
42/// Returns `Some(response)` if the rejection matches, `None` otherwise —
43/// allowing callers to fall through to other rejection handling.
44#[cfg(any(feature = "openai", feature = "anthropic", feature = "google"))]
45pub fn handle_bitrouter_rejection(err: &warp::Rejection) -> Option<warp::http::Response<String>> {
46    use warp::http::StatusCode;
47
48    if let Some(e) = err.find::<BitrouterRejection>() {
49        let (status, error_type) = match &e.0 {
50            BitrouterError::InvalidRequest { .. } | BitrouterError::UnsupportedFeature { .. } => {
51                (StatusCode::BAD_REQUEST, "invalid_request_error")
52            }
53            BitrouterError::AccessDenied { .. } => (StatusCode::FORBIDDEN, "access_denied"),
54            BitrouterError::Cancelled { .. } => (StatusCode::BAD_REQUEST, "cancelled"),
55            BitrouterError::Provider { context, .. } => {
56                let status = context
57                    .status_code
58                    .and_then(|code| StatusCode::from_u16(code).ok())
59                    .unwrap_or(StatusCode::BAD_GATEWAY);
60                (status, "provider_error")
61            }
62            BitrouterError::Transport { .. }
63            | BitrouterError::ResponseDecode { .. }
64            | BitrouterError::InvalidResponse { .. }
65            | BitrouterError::StreamProtocol { .. } => (StatusCode::BAD_GATEWAY, "upstream_error"),
66        };
67
68        let body = serde_json::json!({
69            "error": {
70                "message": e.0.to_string(),
71                "type": error_type,
72            }
73        })
74        .to_string();
75
76        let response = warp::http::Response::builder()
77            .status(status)
78            .header("content-type", "application/json")
79            .body(body)
80            .ok()?;
81
82        return Some(response);
83    }
84
85    #[cfg(feature = "openai")]
86    if let Some(e) = err.find::<BadRequest>() {
87        let body = serde_json::json!({
88            "error": {
89                "message": e.to_string(),
90                "type": "invalid_request_error",
91            }
92        })
93        .to_string();
94
95        let response = warp::http::Response::builder()
96            .status(StatusCode::BAD_REQUEST)
97            .header("content-type", "application/json")
98            .body(body)
99            .ok()?;
100
101        return Some(response);
102    }
103
104    None
105}