/
alexefan136
/
flowstack
Обзор
Документация
Войти
/
alexefan136
/
flowstack
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
core/rag/src/error.rs
336 строк
11 KB
Alexander Efanov
Обновление репозитория
15 июл 2026, 12:19
15 июл 2026, 12:19
76704c6
Код
Авторство
О чём код?
// src/error.rs use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("Document not found: {id}")] DocumentNotFound { id: String }, #[error("Chunk not found: {id}")] ChunkNotFound { id: String }, #[error("Collection not found: {name}")] CollectionNotFound { name: String }, #[error("Invalid input: {message}")] InvalidInput { message: String, field: Option<String>, }, #[error("Invalid configuration: {message}")] InvalidConfig { message: String }, #[error("Dimension mismatch: expected {expected}, got {actual}")] DimensionMismatch { expected: usize, actual: usize }, #[error("Embedding failed: {message}")] EmbeddingFailed { message: String, model: Option<String>, }, #[error("Reranking failed: {message}")] RerankingFailed { message: String, model: Option<String>, }, #[error("OCR failed: {message}")] OcrFailed { message: String }, #[error("LLM provider error: {status} {message}")] LlmProvider { status: u16, message: String, provider: Option<String>, }, #[error("LLM rate limit exceeded: {message}")] LlmRateLimit { message: String, retry_after: Option<u64>, }, #[error("Storage error: {message}")] Storage { message: String, source: Option<Box<dyn std::error::Error + Send + Sync>>, }, #[error("Vector store unavailable: {message}")] VectorStoreUnavailable { message: String }, #[error("Database error: {message}")] Database { message: String, source: Option<Box<dyn std::error::Error + Send + Sync>>, }, #[error("Parse error ({format}): {message}")] ParseError { format: String, message: String }, #[error("Unsupported format: {format}")] UnsupportedFormat { format: String }, #[error("File too large: {size_bytes} bytes (max: {max_bytes})")] FileTooLarge { size_bytes: u64, max_bytes: u64 }, #[error("HTTP error: {0}")] Http(#[from] reqwest::Error), #[error("Connection timeout: {operation}")] Timeout { operation: String }, #[error("Service unavailable: {service}")] ServiceUnavailable { service: String }, #[error("Unauthorized: {message}")] Unauthorized { message: String }, #[error("Forbidden: {message}")] Forbidden { message: String }, #[error("Workspace not found: {workspace_id}")] WorkspaceNotFound { workspace_id: String }, #[error("Internal error: {message}")] Internal { message: String, source: Option<Box<dyn std::error::Error + Send + Sync>>, }, } pub type Result<T> = std::result::Result<T, Error>; #[derive(Debug, Serialize, Deserialize)] pub struct ErrorResponse { pub error: String, pub message: String, pub status: u16, pub request_id: Option<String>, #[serde(skip_serializing_if = "HashMap::is_empty")] pub details: HashMap<String, serde_json::Value>, } impl Error { pub fn error_code(&self) -> &'static str { match self { Self::DocumentNotFound { .. } => "document_not_found", Self::ChunkNotFound { .. } => "chunk_not_found", Self::CollectionNotFound { .. } => "collection_not_found", Self::InvalidInput { .. } => "invalid_input", Self::InvalidConfig { .. } => "invalid_config", Self::DimensionMismatch { .. } => "dimension_mismatch", Self::EmbeddingFailed { .. } => "embedding_failed", Self::RerankingFailed { .. } => "reranking_failed", Self::OcrFailed { .. } => "ocr_failed", Self::LlmProvider { .. } => "llm_provider_error", Self::LlmRateLimit { .. } => "llm_rate_limit", Self::Storage { .. } => "storage_error", Self::VectorStoreUnavailable { .. } => "vector_store_unavailable", Self::Database { .. } => "database_error", Self::ParseError { .. } => "parse_error", Self::UnsupportedFormat { .. } => "unsupported_format", Self::FileTooLarge { .. } => "file_too_large", Self::Http(_) => "http_error", Self::Timeout { .. } => "timeout", Self::ServiceUnavailable { .. } => "service_unavailable", Self::Unauthorized { .. } => "unauthorized", Self::Forbidden { .. } => "forbidden", Self::WorkspaceNotFound { .. } => "workspace_not_found", Self::Internal { .. } => "internal_error", } } pub fn status_code(&self) -> StatusCode { match self { Self::DocumentNotFound { .. } | Self::ChunkNotFound { .. } | Self::CollectionNotFound { .. } | Self::WorkspaceNotFound { .. } => StatusCode::NOT_FOUND, Self::InvalidInput { .. } | Self::InvalidConfig { .. } | Self::DimensionMismatch { .. } | Self::ParseError { .. } | Self::UnsupportedFormat { .. } | Self::FileTooLarge { .. } => StatusCode::BAD_REQUEST, Self::Unauthorized { .. } => StatusCode::UNAUTHORIZED, Self::Forbidden { .. } => StatusCode::FORBIDDEN, Self::LlmRateLimit { .. } => StatusCode::TOO_MANY_REQUESTS, Self::LlmProvider { status, .. } => { StatusCode::from_u16(*status).unwrap_or(StatusCode::BAD_GATEWAY) } Self::ServiceUnavailable { .. } | Self::VectorStoreUnavailable { .. } | Self::Timeout { .. } => StatusCode::SERVICE_UNAVAILABLE, Self::EmbeddingFailed { .. } | Self::RerankingFailed { .. } | Self::OcrFailed { .. } | Self::Storage { .. } | Self::Database { .. } | Self::Http(_) | Self::Internal { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } pub fn details(&self) -> HashMap<String, serde_json::Value> { let mut details = HashMap::new(); match self { Self::DocumentNotFound { id } => { details.insert("document_id".to_string(), serde_json::json!(id)); } Self::ChunkNotFound { id } => { details.insert("chunk_id".to_string(), serde_json::json!(id)); } Self::InvalidInput { field, .. } => { if let Some(f) = field { details.insert("field".to_string(), serde_json::json!(f)); } } Self::DimensionMismatch { expected, actual } => { details.insert("expected".to_string(), serde_json::json!(expected)); details.insert("actual".to_string(), serde_json::json!(actual)); } Self::LlmProvider { provider, status, .. } => { if let Some(p) = provider { details.insert("provider".to_string(), serde_json::json!(p)); } details.insert("status".to_string(), serde_json::json!(status)); } Self::LlmRateLimit { retry_after, .. } => { if let Some(s) = retry_after { details.insert("retry_after".to_string(), serde_json::json!(s)); } } Self::ParseError { format, .. } => { details.insert("format".to_string(), serde_json::json!(format)); } Self::FileTooLarge { size_bytes, max_bytes } => { details.insert("size_bytes".to_string(), serde_json::json!(size_bytes)); details.insert("max_bytes".to_string(), serde_json::json!(max_bytes)); } Self::WorkspaceNotFound { workspace_id } => { details.insert("workspace_id".to_string(), serde_json::json!(workspace_id)); } _ => {} } details } pub fn internal<E: std::error::Error + Send + Sync + 'static>( message: impl Into<String>, source: E, ) -> Self { Self::Internal { message: message.into(), source: Some(Box::new(source)), } } pub fn storage<E: std::error::Error + Send + Sync + 'static>( message: impl Into<String>, source: E, ) -> Self { Self::Storage { message: message.into(), source: Some(Box::new(source)), } } } impl IntoResponse for Error { fn into_response(self) -> Response { let status = self.status_code(); let message = self.to_string(); let error_code = self.error_code().to_string(); let details = self.details(); match status.as_u16() { 500..=599 => tracing::error!(error_code = %error_code, error = %message, "Internal error"), 400..=499 => tracing::warn!(error_code = %error_code, error = %message, "Client error"), _ => tracing::info!(error_code = %error_code, error = %message, "Error"), } let body = ErrorResponse { error: error_code, message, status: status.as_u16(), request_id: None, details, }; (status, Json(body)).into_response() } } impl From<sqlx::Error> for Error { fn from(err: sqlx::Error) -> Self { Self::Database { message: err.to_string(), source: Some(Box::new(err)), } } } impl From<std::io::Error> for Error { fn from(err: std::io::Error) -> Self { Self::Internal { message: format!("IO error: {}", err), source: Some(Box::new(err)), } } } impl From<serde_json::Error> for Error { fn from(err: serde_json::Error) -> Self { Self::ParseError { format: "json".to_string(), message: err.to_string(), } } } impl From<uuid::Error> for Error { fn from(err: uuid::Error) -> Self { Self::InvalidInput { message: format!("Invalid UUID: {}", err), field: None, } } } pub trait ResultExt<T, E> { fn context(self, message: impl Into<String>) -> Result<T>; fn with_context<F>(self, f: F) -> Result<T> where F: FnOnce() -> String; } impl<T, E: std::error::Error + Send + Sync + 'static> ResultExt<T, E> for std::result::Result<T, E> { fn context(self, message: impl Into<String>) -> Result<T> { self.map_err(|e| Error::internal(message.into(), e)) } fn with_context<F>(self, f: F) -> Result<T> where F: FnOnce() -> String, { self.map_err(|e| Error::internal(f(), e)) } }