gitverse-sdk 1.2.0

Rust SDK for GitVerse API
Documentation
// Code generated by gitverse-codegen. DO NOT EDIT.

use std::fmt;

use reqwest::StatusCode;
use serde::Deserialize;

/// Ошибка API GitVerse
#[derive(Debug)]
pub enum Error {
    /// Ошибка HTTP запроса
    Http(reqwest::Error),
    /// Ошибка API с кодом статуса и сообщением
    Api(ApiError),
    /// Ошибка превышения лимита запросов
    RateLimit(RateLimitError),
    /// Ошибка сериализации/десериализации
    Serialization(serde_json::Error),
}

/// Ошибка API
#[derive(Debug, Clone)]
pub struct ApiError {
    /// HTTP статус код
    pub status_code: StatusCode,
    /// Сообщение об ошибке
    pub message: String,
    /// URL документации
    pub documentation_url: String,
    /// Уникальный идентификатор запроса
    pub request_id: String,
}

/// Ошибка превышения лимита запросов
#[derive(Debug, Clone)]
pub struct RateLimitError {
    /// Базовая ошибка API
    pub api_error: ApiError,
    /// Максимальное количество запросов
    pub limit: i64,
    /// Оставшееся количество запросов
    pub remaining: i64,
    /// Время сброса лимита (Unix timestamp)
    pub reset: i64,
    /// Через сколько секунд можно повторить запрос
    pub retry_after: i64,
}

#[derive(Deserialize)]
struct ApiErrorResponse {
    #[serde(default)]
    message: String,
    #[serde(default)]
    documentation_url: String,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Http(e) => write!(f, "HTTP error: {e}"),
            Error::Api(e) => write!(f, "{e}"),
            Error::RateLimit(e) => write!(f, "{e}"),
            Error::Serialization(e) => write!(f, "serialization error: {e}"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::Http(e) => Some(e),
            Error::Serialization(e) => Some(e),
            _ => None,
        }
    }
}

impl fmt::Display for ApiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.message.is_empty() {
            write!(f, "GitVerse API error (status {})", self.status_code)
        } else {
            write!(
                f,
                "GitVerse API error (status {}): {}",
                self.status_code, self.message
            )
        }
    }
}

impl fmt::Display for RateLimitError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "rate limit exceeded: retry after {} seconds",
            self.retry_after
        )
    }
}

impl From<reqwest::Error> for Error {
    fn from(err: reqwest::Error) -> Self {
        Error::Http(err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Serialization(err)
    }
}

/// Проверяет, является ли ошибка 404 Not Found
pub fn is_not_found(err: &Error) -> bool {
    matches!(err, Error::Api(e) if e.status_code == StatusCode::NOT_FOUND)
}

/// Проверяет, является ли ошибка превышением лимита
pub fn is_rate_limit_error(err: &Error) -> bool {
    matches!(err, Error::RateLimit(_))
}

/// Проверяет, является ли ошибка 401 Unauthorized
pub fn is_unauthorized(err: &Error) -> bool {
    matches!(err, Error::Api(e) if e.status_code == StatusCode::UNAUTHORIZED)
}

/// Проверяет, является ли ошибка 403 Forbidden
pub fn is_forbidden(err: &Error) -> bool {
    matches!(err, Error::Api(e) if e.status_code == StatusCode::FORBIDDEN)
}

pub(crate) async fn parse_api_error(resp: reqwest::Response) -> Error {
    let status_code = resp.status();
    let headers = resp.headers().clone();

    let request_id = headers
        .get("X-Request-Id")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("")
        .to_string();

    let body = match resp.text().await {
        Ok(text) => text,
        Err(e) => format!("(failed to read response body: {e})"),
    };

    let (message, documentation_url) = match serde_json::from_str::<ApiErrorResponse>(&body) {
        Ok(err_resp) => (err_resp.message, err_resp.documentation_url),
        Err(_) => (body, String::new()),
    };

    let api_error = ApiError {
        status_code,
        message,
        documentation_url,
        request_id,
    };

    if status_code == StatusCode::TOO_MANY_REQUESTS {
        let limit = headers
            .get("GitVerse-RateLimit-Limit")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse().ok())
            .unwrap_or(0);

        let remaining = headers
            .get("GitVerse-RateLimit-User-Remaining")
            .or_else(|| headers.get("GitVerse-RateLimit-Remaining"))
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse().ok())
            .unwrap_or(0);

        let reset = headers
            .get("Gitverse-Ratelimit-Reset")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse().ok())
            .unwrap_or(0);

        let retry_after = headers
            .get("GitVerse-RateLimit-Retry-After")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse().ok())
            .unwrap_or(60);

        return Error::RateLimit(RateLimitError {
            api_error,
            limit,
            remaining,
            reset,
            retry_after,
        });
    }

    Error::Api(api_error)
}