use axum::{http::StatusCode, response::IntoResponse, Json};
use crate::types::ErrorResponse;
use crate::AppState;
pub(crate) fn error_response(status: StatusCode, message: String) -> axum::response::Response {
(
status,
Json(ErrorResponse {
error: message,
code: None,
}),
)
.into_response()
}
pub(crate) async fn run_blocking<T, F>(work: F) -> Result<T, axum::response::Response>
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
tokio::task::spawn_blocking(work).await.map_err(|e| {
error_response(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Task panicked: {e}"),
)
})
}
pub(crate) async fn run_blocking_typed<T, F>(
work: F,
) -> Result<T, (StatusCode, Json<ErrorResponse>)>
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
tokio::task::spawn_blocking(work).await.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Task panicked: {e}"),
code: None,
}),
)
})
}
pub(crate) fn core_error_response(
status: StatusCode,
error: &velesdb_core::Error,
) -> axum::response::Response {
(
status,
Json(ErrorResponse {
error: error.to_string(),
code: Some(error.code().to_string()),
}),
)
.into_response()
}
pub(crate) fn http_status_for_error(e: &velesdb_core::Error) -> StatusCode {
use velesdb_core::Error;
match e {
Error::CollectionNotFound(_)
| Error::PointNotFound(_)
| Error::EdgeNotFound(_)
| Error::NodeNotFound(_) => StatusCode::NOT_FOUND,
Error::CollectionExists(_) | Error::EdgeExists(_) => StatusCode::CONFLICT,
Error::DimensionMismatch { .. }
| Error::InvalidVector(_)
| Error::InvalidCollectionName { .. }
| Error::InvalidDimension { .. }
| Error::Config(_)
| Error::Query(_)
| Error::InvalidEdgeLabel(_)
| Error::InvalidQuantizerConfig(_)
| Error::SchemaValidation(_)
| Error::VectorNotAllowed(_)
| Error::VectorRequired(_)
| Error::SearchNotSupported(_)
| Error::GraphNotSupported(_)
| Error::Overflow(_) => StatusCode::BAD_REQUEST,
Error::DatabaseLocked(_) | Error::GuardRail(_) => StatusCode::SERVICE_UNAVAILABLE,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
pub(crate) fn auto_core_error_response(error: &velesdb_core::Error) -> axum::response::Response {
core_error_response(http_status_for_error(error), error)
}
#[allow(clippy::result_large_err)]
pub(crate) fn get_collection_or_404(
state: &AppState,
name: &str,
) -> Result<velesdb_core::AnyCollection, axum::response::Response> {
state.db.get_any_collection(name).ok_or_else(|| {
core_error_response(
StatusCode::NOT_FOUND,
&velesdb_core::Error::CollectionNotFound(name.to_string()),
)
})
}
#[allow(clippy::result_large_err)]
pub(crate) fn get_vector_collection_or_404(
state: &AppState,
name: &str,
) -> Result<velesdb_core::collection::VectorCollection, axum::response::Response> {
state.db.get_vector_collection(name).ok_or_else(|| {
core_error_response(
StatusCode::NOT_FOUND,
&velesdb_core::Error::CollectionNotFound(name.to_string()),
)
})
}
pub(crate) fn extract_client_id(headers: &axum::http::HeaderMap) -> String {
headers
.get("x-client-id")
.and_then(|v| v.to_str().ok())
.unwrap_or("anonymous")
.to_string()
}
pub(crate) fn notify_query_timing(
state: &AppState,
collection_name: &str,
start: std::time::Instant,
) {
let duration_us = start.elapsed().as_micros();
let elapsed_ms = duration_us as f64 / 1000.0;
tracing::debug!(collection = collection_name, elapsed_ms, "query completed");
#[allow(clippy::cast_possible_truncation, deprecated)]
state.db.notify_query(
collection_name,
duration_us.min(u128::from(u64::MAX)) as u64,
);
}
#[allow(clippy::result_large_err)]
pub(crate) fn apply_pre_check(
guard_rails: &velesdb_core::guardrails::GuardRails,
client_id: &str,
) -> Result<(), axum::response::Response> {
if let Err(violation) = guard_rails.pre_check(client_id) {
let (status, msg) = match violation {
velesdb_core::guardrails::GuardRailViolation::RateLimitExceeded { .. } => (
StatusCode::TOO_MANY_REQUESTS,
format!("Rate limit exceeded for client '{client_id}'"),
),
velesdb_core::guardrails::GuardRailViolation::CircuitOpen { .. } => (
StatusCode::SERVICE_UNAVAILABLE,
"Circuit breaker is open — too many recent failures".to_string(),
),
other => (
StatusCode::SERVICE_UNAVAILABLE,
format!("Guard rail violation: {other}"),
),
};
return Err((
status,
Json(ErrorResponse {
error: msg,
code: Some("VELES-027".to_string()),
}),
)
.into_response());
}
Ok(())
}
#[cfg(test)]
#[path = "helpers_tests.rs"]
mod tests;