From b9160305f1d95760f2180a4866182a858d449f32 Mon Sep 17 00:00:00 2001 From: Natty Date: Wed, 13 Nov 2024 13:41:06 +0100 Subject: [PATCH] Streamlined error handling --- ext_federation/src/crypto.rs | 20 +-- src/model/processing/mod.rs | 13 +- src/service/emoji_cache.rs | 14 +-- src/service/generic_id_cache.rs | 14 +-- src/service/instance_cache.rs | 14 +-- src/service/instance_meta_cache.rs | 15 +-- src/service/local_user_cache.rs | 28 ++--- src/web/auth.rs | 110 ++++++---------- src/web/extractors.rs | 20 ++- src/web/mod.rs | 193 +++++++++-------------------- src/web/pagination.rs | 32 ++--- 11 files changed, 165 insertions(+), 308 deletions(-) diff --git a/ext_federation/src/crypto.rs b/ext_federation/src/crypto.rs index 647cbdd..d69324e 100644 --- a/ext_federation/src/crypto.rs +++ b/ext_federation/src/crypto.rs @@ -66,17 +66,9 @@ pub enum ApHttpPublicKey<'a> { } #[derive(Debug, Copy, Clone, Error)] +#[error("Failed to parse the public key: No available parser could decode the PEM string")] pub struct ApHttpPublicKeyParseError; -impl Display for ApHttpPublicKeyParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Failed to parse the public key: No available parser could decode the PEM string" - ) - } -} - impl FromStr for ApHttpPublicKey<'_> { type Err = ApHttpPublicKeyParseError; @@ -187,17 +179,9 @@ pub enum ApHttpPrivateKey<'a> { } #[derive(Debug, Copy, Clone, Error)] +#[error("Failed to parse the private key: No available parser could decode the PEM string")] pub struct ApHttpPrivateKeyParseError; -impl Display for ApHttpPrivateKeyParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Failed to parse the private key: No available parser could decode the PEM string" - ) - } -} - impl FromStr for ApHttpPrivateKey<'_> { type Err = ApHttpPrivateKeyParseError; diff --git a/src/model/processing/mod.rs b/src/model/processing/mod.rs index f2220fe..1c74c67 100644 --- a/src/model/processing/mod.rs +++ b/src/model/processing/mod.rs @@ -5,6 +5,7 @@ use crate::service::instance_meta_cache::InstanceMetaCacheError; use magnetar_model::sea_orm::DbErr; use magnetar_model::CalckeyDbError; use magnetar_sdk::mmm::Token; +use miette::Diagnostic; use thiserror::Error; pub mod drive; @@ -13,27 +14,37 @@ pub mod note; pub mod notification; pub mod user; -#[derive(Debug, Error, strum::IntoStaticStr)] +#[derive(Debug, Error, Diagnostic)] pub enum PackError { #[error("Database error: {0}")] + #[diagnostic(code(mag::pack_error::db_error))] DbError(#[from] DbErr), #[error("Calckey database wrapper error: {0}")] + #[diagnostic(code(mag::pack_error::db_wrapper_error))] CalckeyDbError(#[from] CalckeyDbError), #[error("Data error: {0}")] + #[diagnostic(code(mag::pack_error::data_error))] DataError(String), #[error("Emoji cache error: {0}")] + #[diagnostic(code(mag::pack_error::emoji_cache_error))] EmojiCacheError(#[from] EmojiCacheError), #[error("Instance cache error: {0}")] + #[diagnostic(code(mag::pack_error::instance_meta_cache_error))] InstanceMetaCacheError(#[from] InstanceMetaCacheError), #[error("Generic cache error: {0}")] + #[diagnostic(code(mag::pack_error::generic_id_cache_error))] GenericCacheError(#[from] GenericIdCacheError), #[error("Remote instance cache error: {0}")] + #[diagnostic(code(mag::pack_error::remote_instance_cache_error))] RemoteInstanceCacheError(#[from] RemoteInstanceCacheError), #[error("Deserializer error: {0}")] + #[diagnostic(code(mag::pack_error::deserializer_error))] DeserializerError(#[from] serde_json::Error), #[error("URL parse error: {0}")] + #[diagnostic(code(mag::pack_error::url_parse_error))] UrlParseError(#[from] url::ParseError), #[error("Parallel processing error: {0}")] + #[diagnostic(code(mag::pack_error::task_join_error))] JoinError(#[from] tokio::task::JoinError), } diff --git a/src/service/emoji_cache.rs b/src/service/emoji_cache.rs index 116b637..75ef7fd 100644 --- a/src/service/emoji_cache.rs +++ b/src/service/emoji_cache.rs @@ -2,27 +2,23 @@ use crate::web::ApiError; use lru::LruCache; use magnetar_model::emoji::{EmojiResolver, EmojiTag}; use magnetar_model::{ck, CalckeyDbError, CalckeyModel}; +use miette::Diagnostic; use std::collections::HashSet; use std::sync::Arc; -use strum::VariantNames; use thiserror::Error; use tokio::sync::Mutex; -#[derive(Debug, Error, VariantNames)] +#[derive(Debug, Error, Diagnostic)] +#[error("Emoji cache error: {}")] pub enum EmojiCacheError { #[error("Database error: {0}")] + #[diagnostic(code(mag::emoji_cache_error::db_error))] DbError(#[from] CalckeyDbError), } impl From for ApiError { fn from(err: EmojiCacheError) -> Self { - let mut api_error: ApiError = match err { - EmojiCacheError::DbError(err) => err.into(), - }; - - api_error.message = format!("Emoji cache error: {}", api_error.message); - - api_error + Self::internal("Cache error", err) } } diff --git a/src/service/generic_id_cache.rs b/src/service/generic_id_cache.rs index 01c3f00..94d2ab5 100644 --- a/src/service/generic_id_cache.rs +++ b/src/service/generic_id_cache.rs @@ -2,14 +2,16 @@ use crate::web::ApiError; use lru::LruCache; use magnetar_model::sea_orm::{EntityTrait, PrimaryKeyTrait}; use magnetar_model::{CalckeyDbError, CalckeyModel}; +use miette::Diagnostic; use std::marker::PhantomData; use std::sync::Arc; use std::time::{Duration, Instant}; -use strum::VariantNames; use thiserror::Error; use tokio::sync::Mutex; -#[derive(Debug, Error, VariantNames)] +#[derive(Debug, Error, Diagnostic)] +#[error("Generic ID cache error: {}")] +#[diagnostic(code(mag::generic_id_cache_error))] pub enum GenericIdCacheError { #[error("Database error: {0}")] DbError(#[from] CalckeyDbError), @@ -17,13 +19,7 @@ pub enum GenericIdCacheError { impl From for ApiError { fn from(err: GenericIdCacheError) -> Self { - let mut api_error: ApiError = match err { - GenericIdCacheError::DbError(err) => err.into(), - }; - - api_error.message = format!("Generic ID cache error: {}", api_error.message); - - api_error + Self::internal("Cache error", err) } } diff --git a/src/service/instance_cache.rs b/src/service/instance_cache.rs index 2d12662..5d118d3 100644 --- a/src/service/instance_cache.rs +++ b/src/service/instance_cache.rs @@ -2,13 +2,15 @@ use crate::web::ApiError; use lru::LruCache; use magnetar_common::config::MagnetarConfig; use magnetar_model::{ck, CalckeyDbError, CalckeyModel}; +use miette::Diagnostic; use std::sync::Arc; use std::time::{Duration, Instant}; -use strum::VariantNames; use thiserror::Error; use tokio::sync::Mutex; -#[derive(Debug, Error, VariantNames)] +#[derive(Debug, Error, Diagnostic)] +#[error("Remote instance cache error: {}")] +#[diagnostic(code(mag::remote_instance_cache_error))] pub enum RemoteInstanceCacheError { #[error("Database error: {0}")] DbError(#[from] CalckeyDbError), @@ -16,13 +18,7 @@ pub enum RemoteInstanceCacheError { impl From for ApiError { fn from(err: RemoteInstanceCacheError) -> Self { - let mut api_error: ApiError = match err { - RemoteInstanceCacheError::DbError(err) => err.into(), - }; - - api_error.message = format!("Remote instance cache error: {}", api_error.message); - - api_error + Self::internal("Cache error", err) } } diff --git a/src/service/instance_meta_cache.rs b/src/service/instance_meta_cache.rs index 63a0fdd..19f3d12 100644 --- a/src/service/instance_meta_cache.rs +++ b/src/service/instance_meta_cache.rs @@ -1,13 +1,15 @@ use crate::web::ApiError; use magnetar_model::{ck, CalckeyDbError, CalckeyModel}; +use miette::Diagnostic; use std::sync::Arc; use std::time::{Duration, Instant}; -use strum::VariantNames; use thiserror::Error; use tokio::sync::{mpsc, oneshot}; use tracing::error; -#[derive(Debug, Error, VariantNames)] +#[derive(Debug, Error, Diagnostic)] +#[error("Instance meta cache error: {}")] +#[diagnostic(code(mag::instance_meta_cache_error))] pub enum InstanceMetaCacheError { #[error("Database error: {0}")] DbError(#[from] CalckeyDbError), @@ -17,14 +19,7 @@ pub enum InstanceMetaCacheError { impl From for ApiError { fn from(err: InstanceMetaCacheError) -> Self { - let mut api_error: ApiError = match err { - InstanceMetaCacheError::DbError(err) => err.into(), - InstanceMetaCacheError::ChannelClosed => err.into(), - }; - - api_error.message = format!("Instance meta cache error: {}", api_error.message); - - api_error + Self::internal("Cache error", err) } } diff --git a/src/service/local_user_cache.rs b/src/service/local_user_cache.rs index b621996..296c346 100644 --- a/src/service/local_user_cache.rs +++ b/src/service/local_user_cache.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::Arc; use cached::{Cached, TimedCache}; -use strum::VariantNames; +use miette::Diagnostic; use thiserror::Error; use tokio::sync::Mutex; use tracing::error; @@ -15,7 +15,9 @@ use magnetar_model::{ InternalStreamMessage, SubMessage, }; -#[derive(Debug, Error, VariantNames)] +#[derive(Debug, Error, Diagnostic)] +#[error("Local user cache error: {}")] +#[diagnostic(code(mag::local_user_cache_error))] pub enum UserCacheError { #[error("Database error: {0}")] DbError(#[from] CalckeyDbError), @@ -27,6 +29,13 @@ pub enum UserCacheError { PublicKeyParseError(#[from] ApHttpPublicKeyParseError), } +impl From for ApiError { + fn from(err: UserCacheError) -> Self { + Self::internal("Cache error", err) + } +} + + #[derive(Debug, Clone)] pub struct CachedLocalUser { pub user: Arc, @@ -48,21 +57,6 @@ impl TryFrom<(ck::user::Model, ck::user_profile::Model, ck::user_keypair::Model) } } -impl From for ApiError { - fn from(err: UserCacheError) -> Self { - let mut api_error: ApiError = match err { - UserCacheError::DbError(err) => err.into(), - UserCacheError::RedisError(err) => err.into(), - UserCacheError::PublicKeyParseError(err) => err.into(), - UserCacheError::PrivateKeyParseError(err) => err.into() - }; - - api_error.message = format!("Local user cache error: {}", api_error.message); - - api_error - } -} - struct LocalUserCache { lifetime: TimedCache, id_to_user: HashMap, diff --git a/src/web/auth.rs b/src/web/auth.rs index 3bb092e..aae329b 100644 --- a/src/web/auth.rs +++ b/src/web/auth.rs @@ -2,23 +2,23 @@ use std::convert::Infallible; use std::sync::Arc; use axum::async_trait; -use axum::extract::{FromRequestParts, Request, State}; use axum::extract::rejection::ExtensionRejection; -use axum::http::{HeaderMap, StatusCode}; +use axum::extract::{FromRequestParts, Request, State}; use axum::http::request::Parts; +use axum::http::{HeaderMap, StatusCode}; use axum::middleware::Next; use axum::response::{IntoResponse, Response}; -use headers::{Authorization, HeaderMapExt}; use headers::authorization::Bearer; -use strum::IntoStaticStr; +use headers::{Authorization, HeaderMapExt}; +use miette::{miette, Diagnostic}; use thiserror::Error; use tracing::error; -use magnetar_model::{CalckeyDbError, ck}; +use magnetar_model::{ck, CalckeyDbError}; use crate::service::local_user_cache::{CachedLocalUser, UserCacheError}; use crate::service::MagnetarService; -use crate::web::{ApiError, IntoErrorCode}; +use crate::web::ApiError; #[derive(Clone, Debug)] pub enum AuthMode { @@ -45,15 +45,13 @@ pub struct AuthUserRejection(ApiError); impl From for AuthUserRejection { fn from(rejection: ExtensionRejection) -> Self { - AuthUserRejection(ApiError { - status: StatusCode::UNAUTHORIZED, - code: "Unauthorized".error_code(), - message: if cfg!(debug_assertions) { - format!("Missing auth extension: {}", rejection) - } else { - "Unauthorized".to_string() - }, - }) + AuthUserRejection( + ApiError::new( + StatusCode::UNAUTHORIZED, + "Unauthorized", + miette!(code = "mag::auth_user_rejection", "Missing auth extension: {}", rejection), + ) + ) } } @@ -89,76 +87,46 @@ pub struct AuthState { service: Arc, } -#[derive(Debug, Error, IntoStaticStr)] +#[derive(Debug, Error, Diagnostic)] +#[error("Auth error: {}")] enum AuthError { #[error("Unsupported authorization scheme")] + #[diagnostic(code(mag::auth_error::unsupported_scheme))] UnsupportedScheme, #[error("Cache error: {0}")] + #[diagnostic(code(mag::auth_error::cache_error))] CacheError(#[from] UserCacheError), #[error("Database error: {0}")] + #[diagnostic(code(mag::auth_error::db_error))] DbError(#[from] CalckeyDbError), #[error("Invalid token")] + #[diagnostic(code(mag::auth_error::invalid_token))] InvalidToken, - #[error("Invalid token \"{token}\" referencing user \"{user}\"")] - InvalidTokenUser { token: String, user: String }, - #[error("Invalid access token \"{access_token}\" referencing app \"{app}\"")] - InvalidAccessTokenApp { access_token: String, app: String }, + #[error("Invalid token referencing user \"{user}\"")] + #[diagnostic(code(mag::auth_error::invalid_token_user))] + InvalidTokenUser { user: String }, + #[error("Invalid access token referencing app \"{app}\"")] + #[diagnostic(code(mag::auth_error::invalid_token_app))] + InvalidAccessTokenApp { app: String }, } impl From for ApiError { fn from(err: AuthError) -> Self { - match err { - AuthError::UnsupportedScheme => ApiError { - status: StatusCode::UNAUTHORIZED, - code: err.error_code(), - message: "Unsupported authorization scheme".to_string(), - }, - AuthError::CacheError(err) => err.into(), - AuthError::DbError(err) => err.into(), - AuthError::InvalidTokenUser { - ref token, - ref user, - } => { - error!("Invalid token \"{}\" referencing user \"{}\"", token, user); + let code = match err { + AuthError::InvalidToken => StatusCode::UNAUTHORIZED, + _ => StatusCode::INTERNAL_SERVER_ERROR + }; - ApiError { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Invalid token \"{}\" referencing user \"{}\"", token, user) - } else { - "Invalid token-user link".to_string() - }, - } - } - AuthError::InvalidAccessTokenApp { - ref access_token, - ref app, - } => { - error!( - "Invalid access token \"{}\" referencing app \"{}\"", - access_token, app - ); + let message = match err { + AuthError::UnsupportedScheme => "Unsupported authorization scheme", + AuthError::InvalidTokenUser { .. } => "Invalid token and user combination", + AuthError::InvalidAccessTokenApp { .. } => "Invalid token and app combination", + AuthError::CacheError(_) => "Cache error", + AuthError::DbError(_) => "Database error", + AuthError::InvalidToken => "Invalid account token" + }; - ApiError { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!( - "Invalid access token \"{}\" referencing app \"{}\"", - access_token, app - ) - } else { - "Invalid access token-app link".to_string() - }, - } - } - AuthError::InvalidToken => ApiError { - status: StatusCode::UNAUTHORIZED, - code: err.error_code(), - message: "Invalid token".to_string(), - }, - } + ApiError::new(code, message, miette!(err)) } } @@ -203,7 +171,6 @@ impl AuthState { if user.is_none() { return Err(AuthError::InvalidTokenUser { - token: access_token.id, user: access_token.user_id, }); } @@ -220,7 +187,6 @@ impl AuthState { }), }), None => Err(AuthError::InvalidAccessTokenApp { - access_token: access_token.id, app: access_token.user_id, }), }; diff --git a/src/web/extractors.rs b/src/web/extractors.rs index 1e6b44f..e0e34fd 100644 --- a/src/web/extractors.rs +++ b/src/web/extractors.rs @@ -1,9 +1,10 @@ use axum::{http::HeaderValue, response::IntoResponse}; -use hyper::{header, StatusCode}; +use hyper::header; use magnetar_core::web_model::{content_type::ContentXrdXml, ContentType}; +use miette::miette; use serde::Serialize; -use crate::web::{ApiError, ErrorCode}; +use crate::web::ApiError; pub struct XrdXmlExt(pub T); @@ -20,16 +21,11 @@ impl IntoResponse for XrdXmlExt { buf.into_bytes(), ) .into_response(), - Err(e) => ApiError { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: ErrorCode("XmlSerializationError".into()), - message: if cfg!(debug_assertions) { - format!("Serialization error: {}", e) - } else { - "Serialization error".to_string() - }, - } - .into_response(), + Err(e) => ApiError::internal( + "XmlSerializationError", + miette!(code = "mag::xrd_xml_ext_error", "XML serialization error: {}", e), + ) + .into_response(), } } } diff --git a/src/web/mod.rs b/src/web/mod.rs index 5ea8d83..c5d94b1 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -5,74 +5,64 @@ use axum::Json; use magnetar_common::util::FediverseTagParseError; use magnetar_federation::crypto::{ApHttpPrivateKeyParseError, ApHttpPublicKeyParseError}; use magnetar_model::{CalckeyCacheError, CalckeyDbError}; -use serde::Serialize; +use miette::{diagnostic, miette, Diagnostic, Report}; use serde_json::json; -use std::fmt::{Display, Formatter}; +use std::borrow::Cow; +use std::fmt::Display; use thiserror::Error; +use tracing::warn; +use ulid::Ulid; pub mod auth; pub mod extractors; pub mod pagination; -#[derive(Debug, Clone, Serialize)] -#[repr(transparent)] -pub struct ErrorCode(pub String); - -// This janky hack allows us to use `.error_code()` on enums with strum::IntoStaticStr -pub trait IntoErrorCode { - fn error_code<'a, 'b: 'a>(&'a self) -> ErrorCode - where - &'a Self: Into<&'b str>; -} - -impl IntoErrorCode for T { - fn error_code<'a, 'b: 'a>(&'a self) -> ErrorCode - where - &'a Self: Into<&'b str>, - { - ErrorCode(<&Self as Into<&'b str>>::into(self).to_string()) - } -} - -impl ErrorCode { - pub fn join(&self, other: &str) -> Self { - Self(format!("{}:{}", other, self.0)) - } -} #[derive(Debug, Error)] +#[error("API Error")] pub struct ApiError { pub status: StatusCode, - pub code: ErrorCode, - pub message: String, + pub nonce: Ulid, + pub message: Cow<'static, str>, + pub cause: miette::Report, } -impl Display for ApiError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "ApiError[status = \"{}\", code = \"{:?}\"]: \"{}\"", - self.status, self.code, self.message - ) +impl ApiError { + pub fn new(status: StatusCode, + message: impl Into>, + cause: impl Into) -> Self { + Self { + status, + nonce: Ulid::new(), + message: message.into(), + cause: cause.into(), + } } -} -#[derive(Debug)] -pub struct AccessForbidden(pub String); - -impl From<&AccessForbidden> for &str { - fn from(_: &AccessForbidden) -> &'static str { - "AccessForbidden" + pub fn internal(message: impl Into>, + cause: impl Into) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, message, cause) } } impl IntoResponse for ApiError { fn into_response(self) -> Response { + let mut buf = [0; ulid::ULID_LEN]; + let nonce = self.nonce.array_to_str(&mut buf); + + warn!("[status={},nonce={}] {}", self.status.as_str(), nonce, self.cause); + + + let code = self.cause.code() + .as_deref() + .map(::to_string); + ( self.status, Json(json!({ "status": self.status.as_u16(), - "code": self.code, + "code": code, + "nonce": nonce, "message": self.message, })), ) @@ -80,142 +70,75 @@ impl IntoResponse for ApiError { } } +#[derive(Debug, Error, Diagnostic)] +#[error("Access forbidden: {0}")] +#[diagnostic(code(mag::access_forbidden))] +pub struct AccessForbidden(pub String); + impl From for ApiError { fn from(err: AccessForbidden) -> Self { - Self { - status: StatusCode::FORBIDDEN, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Forbidden: {}", err.0) - } else { - "Forbidden".to_string() - }, - } + Self::new(StatusCode::FORBIDDEN, "Access forbidden", err) } } impl From for ApiError { fn from(err: FediverseTagParseError) -> Self { - Self { - status: StatusCode::BAD_REQUEST, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Fediverse tag parse error: {}", err) - } else { - "Fediverse tag parse error".to_string() - }, - } + Self::new(StatusCode::BAD_REQUEST, + "Fediverse tag parse error", + miette!(code = "mag::access_forbidden", "{}", err)) } } impl From for ApiError { fn from(err: CalckeyDbError) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Database error: {}", err) - } else { - "Database error".to_string() - }, - } + Self::internal("Database error", miette!(code = "mag::db_error", "{}", err)) } } impl From for ApiError { fn from(err: CalckeyCacheError) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Cache error: {}", err) - } else { - "Cache error".to_string() - }, - } + Self::internal("Cache error", miette!(code = "mag::cache_error", "{}", err)) } } impl From for ApiError { fn from(err: PackError) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Data transformation error: {}", err) - } else { - "Data transformation error".to_string() - }, - } + Self::internal("Data transformation error", miette!(code = "mag::pack_error", "{}", err)) } } -#[derive(Debug)] +#[derive(Debug, Error, Diagnostic)] +#[error("Object not found: {0}")] +#[diagnostic(code(mag::object_not_found))] pub struct ObjectNotFound(pub String); -impl From<&ObjectNotFound> for &str { - fn from(_: &ObjectNotFound) -> Self { - "ObjectNotFound" - } -} - impl From for ApiError { fn from(err: ObjectNotFound) -> Self { - Self { - status: StatusCode::NOT_FOUND, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Object not found: {}", err.0) - } else { - "Object not found".to_string() - }, - } + Self::new(StatusCode::NOT_FOUND, "Object not found", miette!(err)) } } -#[derive(Debug)] +#[derive(Debug, Error, Diagnostic)] +#[error("Argument out of range: {0}")] +#[diagnostic(code(mag::argument_out_of_range))] pub struct ArgumentOutOfRange(pub String); -impl From<&ArgumentOutOfRange> for &str { - fn from(_: &ArgumentOutOfRange) -> Self { - "ArgumentOutOfRange" - } -} - impl From for ApiError { fn from(err: ArgumentOutOfRange) -> Self { - Self { - status: StatusCode::BAD_REQUEST, - code: err.error_code(), - message: format!("Argument out of range: {}", err.0), - } + Self::new(StatusCode::BAD_REQUEST, format!("Argument out of range: {}", err.0), err) } } impl From for ApiError { fn from(err: ApHttpPublicKeyParseError) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: "ApHttpPublicKeyParseError".error_code(), - message: if cfg!(debug_assertions) { - format!("User public key parse error: {}", err) - } else { - "User public key parse error".to_string() - }, - } + Self::internal("User public key parse error", + miette!(code = "mag::ap_http_public_key_parse_error", "{}", err)) } } impl From for ApiError { fn from(err: ApHttpPrivateKeyParseError) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: "ApHttpPrivateKeyParseError".error_code(), - message: if cfg!(debug_assertions) { - format!("User private key parse error: {}", err) - } else { - "User private key parse error".to_string() - }, - } + Self::internal("User private key parse error", + miette!(code = "mag::ap_http_private_key_parse_error", "{}", err)) } } diff --git a/src/web/pagination.rs b/src/web/pagination.rs index 372e7f7..15c9dc1 100644 --- a/src/web/pagination.rs +++ b/src/web/pagination.rs @@ -1,6 +1,6 @@ use crate::service::MagnetarService; use crate::util::serialize_as_urlenc; -use crate::web::{ApiError, IntoErrorCode}; +use crate::web::ApiError; use axum::extract::rejection::QueryRejection; use axum::extract::{FromRequestParts, OriginalUri, Query}; use axum::http::header::InvalidHeaderValue; @@ -14,10 +14,10 @@ use magnetar_core::web_model::rel::{RelNext, RelPrev}; use magnetar_model::sea_orm::prelude::async_trait::async_trait; use magnetar_sdk::types::{PaginationShape, SpanFilter}; use magnetar_sdk::util_types::U64Range; +use miette::{miette, Diagnostic}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -use strum::IntoStaticStr; use thiserror::Error; use tracing::error; @@ -39,32 +39,32 @@ struct PaginationQuery { query_rest: HashMap, } -#[derive(Debug, Error, IntoStaticStr)] +#[derive(Debug, Error, Diagnostic)] +#[error("Pagination builder error: {}")] pub enum PaginationBuilderError { #[error("Query rejection: {0}")] + #[diagnostic(code(mag::pagination_builder_error::query_rejection))] QueryRejection(#[from] QueryRejection), #[error("HTTP error: {0}")] + #[diagnostic(code(mag::pagination_builder_error::http_error))] HttpError(#[from] axum::http::Error), - #[error("Value of out of range error")] - OutOfRange, #[error("Invalid header value")] + #[diagnostic(code(mag::pagination_builder_error::invalid_header_value))] InvalidHeaderValue(#[from] InvalidHeaderValue), #[error("Query string serialization error: {0}")] + #[diagnostic(code(mag::pagination_builder_error::serialization_error_query))] SerializationErrorQuery(#[from] serde_urlencoded::ser::Error), #[error("Query string serialization error: {0}")] + #[diagnostic(code(mag::pagination_builder_error::serialization_error_json))] SerializationErrorJson(#[from] serde_json::Error), } impl From for ApiError { fn from(err: PaginationBuilderError) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - code: err.error_code(), - message: if cfg!(debug_assertions) { - format!("Pagination builder error: {}", err) - } else { - "Pagination builder error".to_string() - }, + if matches!(err, PaginationBuilderError::QueryRejection(_)) { + Self::new(StatusCode::BAD_REQUEST, "Invalid pagination query", miette!(err)) + } else { + Self::internal("Pagination error", miette!(err)) } } } @@ -92,9 +92,9 @@ impl FromRequestParts> for Pagination { .build()?; let Query(PaginationQuery { - pagination, - query_rest, - }) = parts.extract::>().await?; + pagination, + query_rest, + }) = parts.extract::>().await?; Ok(Pagination { base_uri,