diff --git a/Cargo.lock b/Cargo.lock index cc200b746..82fe73cdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3831,6 +3831,16 @@ dependencies = [ "typed-builder", ] +[[package]] +name = "kitsune-error" +version = "0.0.1-pre.6" +dependencies = [ + "axum-core 0.4.3", + "eyre", + "http 1.1.0", + "tracing", +] + [[package]] name = "kitsune-federation" version = "0.0.1-pre.6" diff --git a/Cargo.toml b/Cargo.toml index c5f30c9cf..0ba0575d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "crates/kitsune-db", "crates/kitsune-email", "crates/kitsune-embed", + "crates/kitsune-error", "crates/kitsune-federation", "crates/kitsune-federation-filter", "crates/kitsune-http-client", diff --git a/crates/kitsune-error/Cargo.toml b/crates/kitsune-error/Cargo.toml new file mode 100644 index 000000000..5d63f59ba --- /dev/null +++ b/crates/kitsune-error/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "kitsune-error" +authors.workspace = true +edition.workspace = true +version.workspace = true +license.workspace = true + +[dependencies] +axum-core = "0.4.3" +eyre = "0.6.12" +http = "1.1.0" +tracing = "0.1.40" + +[lints] +workspace = true diff --git a/crates/kitsune-error/src/axum.rs b/crates/kitsune-error/src/axum.rs new file mode 100644 index 000000000..a9fcfc85b --- /dev/null +++ b/crates/kitsune-error/src/axum.rs @@ -0,0 +1,27 @@ +use crate::{Error, ErrorType}; +use axum_core::response::{IntoResponse, Response}; +use http::StatusCode; + +#[inline] +fn to_response(status_code: StatusCode, maybe_body: Option) -> Response +where + B: IntoResponse, +{ + maybe_body.map_or_else( + || status_code.into_response(), + |body| (status_code, body).into_response(), + ) +} + +impl IntoResponse for Error { + fn into_response(self) -> Response { + debug!(error = ?self.inner); + + match self.ty { + ErrorType::BadRequest(maybe_body) => to_response(StatusCode::BAD_REQUEST, maybe_body), + ErrorType::NotFound => StatusCode::NOT_FOUND.into_response(), + ErrorType::Unauthorized => StatusCode::UNAUTHORIZED.into_response(), + ErrorType::Other => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + } + } +} diff --git a/crates/kitsune-error/src/ext.rs b/crates/kitsune-error/src/ext.rs new file mode 100644 index 000000000..8e1e939be --- /dev/null +++ b/crates/kitsune-error/src/ext.rs @@ -0,0 +1,24 @@ +use crate::{Error, ErrorType}; + +mod sealed { + pub trait Sealed {} + + impl Sealed for Result {} +} + +pub trait ResultExt: sealed::Sealed { + fn with_error_type(self, ty: ErrorType) -> Result; +} + +impl ResultExt for Result +where + E: Into, +{ + #[inline] + fn with_error_type(self, ty: ErrorType) -> Result { + self.map_err(|err| Error { + ty, + inner: err.into(), + }) + } +} diff --git a/crates/kitsune-error/src/lib.rs b/crates/kitsune-error/src/lib.rs new file mode 100644 index 000000000..e6330a53c --- /dev/null +++ b/crates/kitsune-error/src/lib.rs @@ -0,0 +1,58 @@ +#[macro_use] +extern crate tracing; + +pub use self::ext::ResultExt; + +mod axum; +mod ext; + +#[derive(Clone)] +pub enum ErrorType { + BadRequest(Option), + NotFound, + Unauthorized, + Other, +} + +pub struct Error { + ty: ErrorType, + inner: eyre::Report, +} + +impl Error { + pub fn new(ty: ErrorType, err: E) -> Self + where + E: Into, + { + Self { + ty, + inner: err.into(), + } + } + + #[must_use] + pub fn error_type(&self) -> &ErrorType { + &self.ty + } + + pub fn error(&self) -> &eyre::Report { + &self.inner + } + + #[must_use] + pub fn with_error_type(self, ty: ErrorType) -> Self { + Self { ty, ..self } + } +} + +impl From for Error +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + ty: ErrorType::Other, + inner: value.into(), + } + } +}