Something went wrong. Try again.
Chris’s PDS implementation
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111//! Errors shared by the application layers.
use std::fmt::Display;use thiserror::Error;
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Classify a boundary failure while retaining its original diagnostic.pub(crate) trait ErrorContext<T> { fn context(self, kind: impl FnOnce(String) -> Error, message: impl Display) -> Result<T>;}
impl<T, E: Display> ErrorContext<T> for Result<T, E> { fn context(self, kind: impl FnOnce(String) -> Error, message: impl Display) -> Result<T> { self.map_err(|error| kind(format!("{message}: {error}"))) }}
impl From<cpds_protocol::Error> for Error { fn from(error: cpds_protocol::Error) -> Self { let cpds_protocol::Error::InvalidRecord(message) = error; Self::InvalidRequest(message) }}
#[derive(Debug, Error)]pub enum Error { #[error("authentication is required")] AuthenticationRequired, #[error("authentication failed: {message}")] Authentication { name: &'static str, message: String }, #[error("authorization failed: {0}")] Authorization(String), #[error("the requested object does not exist")] NotFound, #[error("the request conflicts with existing state: {0}")] Conflict(String), #[error("the request is invalid: {0}")] InvalidRequest(String), #[error("authenticated principal request rate exceeded")] RateLimited { retry_after: u64 }, #[error("{message}")] Xrpc { name: &'static str, message: String }, #[error("configuration error: {0}")] Configuration(String), #[error("database error: {0}")] Database(#[from] rusqlite::Error), #[error("I/O error: {0}")] Io(#[from] std::io::Error), #[error("JSON error: {0}")] Json(#[from] serde_json::Error), #[error("URL error: {0}")] Url(#[from] url::ParseError), #[error("upstream service error: {0}")] Upstream(String), #[error("internal error: {0}")] Internal(String),}
impl Error { pub(crate) fn xrpc(name: &'static str, message: impl Into<String>) -> Self { Self::Xrpc { name, message: message.into(), } }}
#[cfg(test)]mod tests { use axum::{body::to_bytes, http::StatusCode, response::IntoResponse as _};
use super::Error;
#[tokio::test] async fn xrpc_constructor_preserves_borrowed_and_owned_message_responses() { let message = "details with \"quotes\", a newline\n and café"; for (name, status) in [ ("SpaceNotFound", StatusCode::BAD_REQUEST), ("AccountSuspended", StatusCode::FORBIDDEN), ] { let original = Error::Xrpc { name, message: message.to_owned(), } .into_response(); assert_eq!(original.status(), status); let headers = original.headers().clone(); let body = to_bytes(original.into_body(), usize::MAX).await.unwrap();
for error in [ Error::xrpc(name, message), Error::xrpc(name, message.to_owned()), ] { assert!( matches!(&error, Error::Xrpc { name: actual_name, message: actual_message } if *actual_name == name && actual_message == message) ); assert_eq!(error.to_string(), message); let response = error.into_response(); assert_eq!(response.status(), status); assert_eq!(response.headers(), &headers); assert_eq!( to_bytes(response.into_body(), usize::MAX).await.unwrap(), body ); } } }}