Skip to main content

gitverse_sdk/
errors.rs

1// Code generated by gitverse-codegen. DO NOT EDIT.
2
3use std::fmt;
4
5use reqwest::StatusCode;
6use serde::Deserialize;
7
8/// Ошибка API GitVerse
9#[derive(Debug)]
10pub enum Error {
11    /// Ошибка HTTP запроса
12    Http(reqwest::Error),
13    /// Ошибка API с кодом статуса и сообщением
14    Api(ApiError),
15    /// Ошибка превышения лимита запросов
16    RateLimit(RateLimitError),
17    /// Ошибка сериализации/десериализации
18    Serialization(serde_json::Error),
19}
20
21/// Ошибка API
22#[derive(Debug, Clone)]
23pub struct ApiError {
24    /// HTTP статус код
25    pub status_code: StatusCode,
26    /// Сообщение об ошибке
27    pub message: String,
28    /// URL документации
29    pub documentation_url: String,
30    /// Уникальный идентификатор запроса
31    pub request_id: String,
32}
33
34/// Ошибка превышения лимита запросов
35#[derive(Debug, Clone)]
36pub struct RateLimitError {
37    /// Базовая ошибка API
38    pub api_error: ApiError,
39    /// Максимальное количество запросов
40    pub limit: i64,
41    /// Оставшееся количество запросов
42    pub remaining: i64,
43    /// Время сброса лимита (Unix timestamp)
44    pub reset: i64,
45    /// Через сколько секунд можно повторить запрос
46    pub retry_after: i64,
47}
48
49#[derive(Deserialize)]
50struct ApiErrorResponse {
51    #[serde(default)]
52    message: String,
53    #[serde(default)]
54    documentation_url: String,
55}
56
57impl fmt::Display for Error {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Error::Http(e) => write!(f, "HTTP error: {e}"),
61            Error::Api(e) => write!(f, "{e}"),
62            Error::RateLimit(e) => write!(f, "{e}"),
63            Error::Serialization(e) => write!(f, "serialization error: {e}"),
64        }
65    }
66}
67
68impl std::error::Error for Error {
69    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70        match self {
71            Error::Http(e) => Some(e),
72            Error::Serialization(e) => Some(e),
73            _ => None,
74        }
75    }
76}
77
78impl fmt::Display for ApiError {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        if self.message.is_empty() {
81            write!(f, "GitVerse API error (status {})", self.status_code)
82        } else {
83            write!(
84                f,
85                "GitVerse API error (status {}): {}",
86                self.status_code, self.message
87            )
88        }
89    }
90}
91
92impl fmt::Display for RateLimitError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(
95            f,
96            "rate limit exceeded: retry after {} seconds",
97            self.retry_after
98        )
99    }
100}
101
102impl From<reqwest::Error> for Error {
103    fn from(err: reqwest::Error) -> Self {
104        Error::Http(err)
105    }
106}
107
108impl From<serde_json::Error> for Error {
109    fn from(err: serde_json::Error) -> Self {
110        Error::Serialization(err)
111    }
112}
113
114/// Проверяет, является ли ошибка 404 Not Found
115pub fn is_not_found(err: &Error) -> bool {
116    matches!(err, Error::Api(e) if e.status_code == StatusCode::NOT_FOUND)
117}
118
119/// Проверяет, является ли ошибка превышением лимита
120pub fn is_rate_limit_error(err: &Error) -> bool {
121    matches!(err, Error::RateLimit(_))
122}
123
124/// Проверяет, является ли ошибка 401 Unauthorized
125pub fn is_unauthorized(err: &Error) -> bool {
126    matches!(err, Error::Api(e) if e.status_code == StatusCode::UNAUTHORIZED)
127}
128
129/// Проверяет, является ли ошибка 403 Forbidden
130pub fn is_forbidden(err: &Error) -> bool {
131    matches!(err, Error::Api(e) if e.status_code == StatusCode::FORBIDDEN)
132}
133
134pub(crate) async fn parse_api_error(resp: reqwest::Response) -> Error {
135    let status_code = resp.status();
136    let headers = resp.headers().clone();
137
138    let request_id = headers
139        .get("X-Request-Id")
140        .and_then(|v| v.to_str().ok())
141        .unwrap_or("")
142        .to_string();
143
144    let body = match resp.text().await {
145        Ok(text) => text,
146        Err(e) => format!("(failed to read response body: {e})"),
147    };
148
149    let (message, documentation_url) = match serde_json::from_str::<ApiErrorResponse>(&body) {
150        Ok(err_resp) => (err_resp.message, err_resp.documentation_url),
151        Err(_) => (body, String::new()),
152    };
153
154    let api_error = ApiError {
155        status_code,
156        message,
157        documentation_url,
158        request_id,
159    };
160
161    if status_code == StatusCode::TOO_MANY_REQUESTS {
162        let limit = headers
163            .get("GitVerse-RateLimit-Limit")
164            .and_then(|v| v.to_str().ok())
165            .and_then(|v| v.parse().ok())
166            .unwrap_or(0);
167
168        let remaining = headers
169            .get("GitVerse-RateLimit-User-Remaining")
170            .or_else(|| headers.get("GitVerse-RateLimit-Remaining"))
171            .and_then(|v| v.to_str().ok())
172            .and_then(|v| v.parse().ok())
173            .unwrap_or(0);
174
175        let reset = headers
176            .get("Gitverse-Ratelimit-Reset")
177            .and_then(|v| v.to_str().ok())
178            .and_then(|v| v.parse().ok())
179            .unwrap_or(0);
180
181        let retry_after = headers
182            .get("GitVerse-RateLimit-Retry-After")
183            .and_then(|v| v.to_str().ok())
184            .and_then(|v| v.parse().ok())
185            .unwrap_or(60);
186
187        return Error::RateLimit(RateLimitError {
188            api_error,
189            limit,
190            remaining,
191            reset,
192            retry_after,
193        });
194    }
195
196    Error::Api(api_error)
197}