Skip to main content

gitverse_sdk/
client.rs

1// Code generated by gitverse-codegen. DO NOT EDIT.
2
3use std::time::Duration;
4
5use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue, USER_AGENT};
6use reqwest::{Client as HttpClient, Method, Response, StatusCode};
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9
10use crate::errors::{Error, parse_api_error};
11use crate::types::QueryOptions;
12
13const DEFAULT_BASE_URL: &str = "https://api.gitverse.ru";
14const DEFAULT_API_VERSION: &str = "1";
15const DEFAULT_TIMEOUT_SECS: u64 = 30;
16const DEFAULT_USER_AGENT: &str = "gitverse-sdk";
17
18/// Информация о лимитах запросов
19#[derive(Debug, Clone)]
20pub struct RateLimitInfo {
21    /// Максимальное количество запросов
22    pub limit: i64,
23    /// Оставшееся количество запросов
24    pub remaining: i64,
25    /// Время сброса лимита (Unix timestamp)
26    pub reset: i64,
27    /// Через сколько секунд можно повторить запрос
28    pub retry_after: i64,
29}
30
31/// Информация о версии API
32#[derive(Debug, Clone)]
33pub struct ApiVersionInfo {
34    /// Текущая версия API
35    pub version: String,
36    /// Последняя доступная версия
37    pub latest_version: String,
38    /// Признак устаревшей версии
39    pub deprecated: bool,
40    /// Дата вывода из эксплуатации
41    pub decommissioning: String,
42}
43
44/// Конфигурация клиента
45#[derive(Default)]
46pub struct ClientConfig {
47    /// Базовый URL API (по умолчанию https://api.gitverse.ru)
48    pub base_url: Option<String>,
49    /// Токен авторизации
50    pub token: String,
51    /// Версия API (по умолчанию "1")
52    pub api_version: Option<String>,
53    /// Таймаут запроса в секундах
54    pub timeout_secs: Option<u64>,
55    /// User-Agent заголовок
56    pub user_agent: Option<String>,
57}
58
59/// HTTP клиент для GitVerse API
60pub struct Client {
61    base_url: String,
62    token: String,
63    api_version: String,
64    http_client: HttpClient,
65    user_agent: String,
66}
67
68impl Client {
69    /// Создаёт новый клиент GitVerse API
70    pub fn new(config: ClientConfig) -> Result<Self, Error> {
71        let base_url = config
72            .base_url
73            .unwrap_or_else(|| DEFAULT_BASE_URL.to_string())
74            .trim_end_matches('/')
75            .to_string();
76
77        let api_version = config
78            .api_version
79            .unwrap_or_else(|| DEFAULT_API_VERSION.to_string());
80
81        let user_agent = config
82            .user_agent
83            .unwrap_or_else(|| DEFAULT_USER_AGENT.to_string());
84
85        let timeout = Duration::from_secs(config.timeout_secs.unwrap_or(DEFAULT_TIMEOUT_SECS));
86
87        let http_client = HttpClient::builder().timeout(timeout).build()?;
88
89        Ok(Self {
90            base_url,
91            token: config.token,
92            api_version,
93            http_client,
94            user_agent,
95        })
96    }
97
98    /// Устанавливает токен авторизации
99    pub fn set_token(&mut self, token: impl Into<String>) {
100        self.token = token.into();
101    }
102
103    /// Выполняет GET запрос
104    pub(crate) async fn send_get(
105        &self,
106        path: &str,
107        query: Option<&QueryOptions>,
108    ) -> Result<Response, Error> {
109        self.request(Method::GET, path, query, Option::<&()>::None)
110            .await
111    }
112
113    /// Выполняет POST запрос
114    pub(crate) async fn send_post<B: Serialize>(
115        &self,
116        path: &str,
117        body: Option<&B>,
118    ) -> Result<Response, Error> {
119        self.request(Method::POST, path, None, body).await
120    }
121
122    /// Выполняет PUT запрос
123    pub(crate) async fn send_put<B: Serialize>(
124        &self,
125        path: &str,
126        body: Option<&B>,
127    ) -> Result<Response, Error> {
128        self.request(Method::PUT, path, None, body).await
129    }
130
131    /// Выполняет PATCH запрос
132    pub(crate) async fn send_patch<B: Serialize>(
133        &self,
134        path: &str,
135        body: Option<&B>,
136    ) -> Result<Response, Error> {
137        self.request(Method::PATCH, path, None, body).await
138    }
139
140    /// Выполняет DELETE запрос
141    pub(crate) async fn send_delete<B: Serialize>(
142        &self,
143        path: &str,
144        body: Option<&B>,
145    ) -> Result<Response, Error> {
146        self.request(Method::DELETE, path, None, body).await
147    }
148
149    /// Выполняет HTTP запрос
150    async fn request<B: Serialize>(
151        &self,
152        method: Method,
153        path: &str,
154        query: Option<&QueryOptions>,
155        body: Option<&B>,
156    ) -> Result<Response, Error> {
157        let clean_path = path.trim_start_matches('/');
158        let url = format!("{}/{}", self.base_url, clean_path);
159
160        let mut request = self.http_client.request(method, &url);
161
162        // Заголовки
163        let mut headers = HeaderMap::new();
164
165        let ua = HeaderValue::from_str(&self.user_agent).map_err(|_| {
166            Error::Api(crate::errors::ApiError {
167                status_code: StatusCode::INTERNAL_SERVER_ERROR,
168                message: format!("invalid User-Agent value: {:?}", self.user_agent),
169                documentation_url: String::new(),
170                request_id: String::new(),
171            })
172        })?;
173        headers.insert(USER_AGENT, ua);
174
175        let accept = HeaderValue::from_str(&format!(
176            "application/vnd.gitverse.object+json; version={}",
177            self.api_version
178        ))
179        .map_err(|_| {
180            Error::Api(crate::errors::ApiError {
181                status_code: StatusCode::INTERNAL_SERVER_ERROR,
182                message: format!("invalid API version value: {:?}", self.api_version),
183                documentation_url: String::new(),
184                request_id: String::new(),
185            })
186        })?;
187        headers.insert(ACCEPT, accept);
188
189        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
190
191        if !self.token.is_empty() {
192            let auth = HeaderValue::from_str(&format!("Bearer {}", self.token)).map_err(|_| {
193                Error::Api(crate::errors::ApiError {
194                    status_code: StatusCode::INTERNAL_SERVER_ERROR,
195                    message: "invalid token: contains non-ASCII characters".to_string(),
196                    documentation_url: String::new(),
197                    request_id: String::new(),
198                })
199            })?;
200            headers.insert(AUTHORIZATION, auth);
201        }
202
203        request = request.headers(headers);
204
205        // Query параметры
206        if let Some(opts) = query {
207            request = request.query(opts);
208        }
209
210        // Body
211        if let Some(body) = body {
212            request = request.json(body);
213        }
214
215        let resp = request.send().await?;
216        Ok(resp)
217    }
218}
219
220/// Декодирует JSON ответ
221pub(crate) async fn decode_response<T: DeserializeOwned>(resp: Response) -> Result<T, Error> {
222    let status = resp.status();
223
224    if status.is_client_error() || status.is_server_error() {
225        return Err(parse_api_error(resp).await);
226    }
227
228    // Для пустых ответов (204 No Content)
229    if status == StatusCode::NO_CONTENT {
230        // Пытаемся десериализовать пустой JSON
231        let text = resp.text().await.unwrap_or_default();
232        if text.is_empty() {
233            return serde_json::from_str("null").map_err(Error::from);
234        }
235        return serde_json::from_str(&text).map_err(Error::from);
236    }
237
238    resp.json::<T>().await.map_err(Error::from)
239}
240
241/// Обрабатывает ответ без тела (void)
242pub(crate) async fn handle_empty_response(resp: Response) -> Result<(), Error> {
243    let status = resp.status();
244
245    if status.is_client_error() || status.is_server_error() {
246        return Err(parse_api_error(resp).await);
247    }
248
249    Ok(())
250}