Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667//! Typed appview error type.//!//! Handlers return `Result<XrpcResponse<_>, AppError>`. `AppError` is a small//! typed enum (not a string bag): each variant maps to a fixed HTTP status and//! XRPC error code at the [`IntoResponse`] boundary, so the error *code* is a//! compile-time constant and only the human message is a `String`. Internal//! details are logged; the response body never carries secrets or raw errors.
use axum::http::StatusCode;use axum::response::{IntoResponse, Response};use jacquard_axum::GenericXrpcErrorResponse;
/// Appview handler error. Maps onto the XRPC error response shape.#[derive(Debug)]pub(crate) enum AppError { /// 400 `InvalidRequest`: bad parameters (bad at-uri, unknown algorithm, /// unparseable cursor, unresolvable handle). InvalidRequest(String), /// 404 `RecordNotFound`: the requested record does not exist in the index. NotFound, /// 500 `InternalServerError`: unexpected failure (database, record decode, /// upstream bsky fetch). The message is safe to expose; details are logged. Internal(String),}
pub(crate) type AppResult<T> = Result<T, AppError>;
pub(super) fn invalid_request(msg: impl Into<String>) -> AppError { AppError::InvalidRequest(msg.into())}
pub(super) fn not_found() -> AppError { AppError::NotFound}
pub(super) fn internal(msg: impl Into<String>) -> AppError { AppError::Internal(msg.into())}
/// Adapt a `sqlx::Error` into a safe 500, logging the real error server-side.pub(super) fn db<T>(result: Result<T, sqlx::Error>) -> AppResult<T> { result.map_err(|e| { tracing::error!(error = %e, "database error"); AppError::Internal("database error".to_string()) })}
/// Clamp a page limit into [1, 100].pub(super) fn clamp_limit(limit: Option<i64>) -> i64 { limit.unwrap_or(50).clamp(1, 100)}
impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, code, message) = match self { AppError::InvalidRequest(m) => (StatusCode::BAD_REQUEST, "InvalidRequest", Some(m)), AppError::NotFound => (StatusCode::NOT_FOUND, "RecordNotFound", None), AppError::Internal(m) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", Some(m), ), }; GenericXrpcErrorResponse::new(status, code, message).into_response() }}