Streamlined error handling

This commit is contained in:
Natty 2024-11-13 13:41:06 +01:00
parent 9c42b20fa9
commit b9160305f1
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
11 changed files with 165 additions and 308 deletions

View File

@ -66,17 +66,9 @@ pub enum ApHttpPublicKey<'a> {
} }
#[derive(Debug, Copy, Clone, Error)] #[derive(Debug, Copy, Clone, Error)]
#[error("Failed to parse the public key: No available parser could decode the PEM string")]
pub struct ApHttpPublicKeyParseError; 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<'_> { impl FromStr for ApHttpPublicKey<'_> {
type Err = ApHttpPublicKeyParseError; type Err = ApHttpPublicKeyParseError;
@ -187,17 +179,9 @@ pub enum ApHttpPrivateKey<'a> {
} }
#[derive(Debug, Copy, Clone, Error)] #[derive(Debug, Copy, Clone, Error)]
#[error("Failed to parse the private key: No available parser could decode the PEM string")]
pub struct ApHttpPrivateKeyParseError; 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<'_> { impl FromStr for ApHttpPrivateKey<'_> {
type Err = ApHttpPrivateKeyParseError; type Err = ApHttpPrivateKeyParseError;

View File

@ -5,6 +5,7 @@ use crate::service::instance_meta_cache::InstanceMetaCacheError;
use magnetar_model::sea_orm::DbErr; use magnetar_model::sea_orm::DbErr;
use magnetar_model::CalckeyDbError; use magnetar_model::CalckeyDbError;
use magnetar_sdk::mmm::Token; use magnetar_sdk::mmm::Token;
use miette::Diagnostic;
use thiserror::Error; use thiserror::Error;
pub mod drive; pub mod drive;
@ -13,27 +14,37 @@ pub mod note;
pub mod notification; pub mod notification;
pub mod user; pub mod user;
#[derive(Debug, Error, strum::IntoStaticStr)] #[derive(Debug, Error, Diagnostic)]
pub enum PackError { pub enum PackError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
#[diagnostic(code(mag::pack_error::db_error))]
DbError(#[from] DbErr), DbError(#[from] DbErr),
#[error("Calckey database wrapper error: {0}")] #[error("Calckey database wrapper error: {0}")]
#[diagnostic(code(mag::pack_error::db_wrapper_error))]
CalckeyDbError(#[from] CalckeyDbError), CalckeyDbError(#[from] CalckeyDbError),
#[error("Data error: {0}")] #[error("Data error: {0}")]
#[diagnostic(code(mag::pack_error::data_error))]
DataError(String), DataError(String),
#[error("Emoji cache error: {0}")] #[error("Emoji cache error: {0}")]
#[diagnostic(code(mag::pack_error::emoji_cache_error))]
EmojiCacheError(#[from] EmojiCacheError), EmojiCacheError(#[from] EmojiCacheError),
#[error("Instance cache error: {0}")] #[error("Instance cache error: {0}")]
#[diagnostic(code(mag::pack_error::instance_meta_cache_error))]
InstanceMetaCacheError(#[from] InstanceMetaCacheError), InstanceMetaCacheError(#[from] InstanceMetaCacheError),
#[error("Generic cache error: {0}")] #[error("Generic cache error: {0}")]
#[diagnostic(code(mag::pack_error::generic_id_cache_error))]
GenericCacheError(#[from] GenericIdCacheError), GenericCacheError(#[from] GenericIdCacheError),
#[error("Remote instance cache error: {0}")] #[error("Remote instance cache error: {0}")]
#[diagnostic(code(mag::pack_error::remote_instance_cache_error))]
RemoteInstanceCacheError(#[from] RemoteInstanceCacheError), RemoteInstanceCacheError(#[from] RemoteInstanceCacheError),
#[error("Deserializer error: {0}")] #[error("Deserializer error: {0}")]
#[diagnostic(code(mag::pack_error::deserializer_error))]
DeserializerError(#[from] serde_json::Error), DeserializerError(#[from] serde_json::Error),
#[error("URL parse error: {0}")] #[error("URL parse error: {0}")]
#[diagnostic(code(mag::pack_error::url_parse_error))]
UrlParseError(#[from] url::ParseError), UrlParseError(#[from] url::ParseError),
#[error("Parallel processing error: {0}")] #[error("Parallel processing error: {0}")]
#[diagnostic(code(mag::pack_error::task_join_error))]
JoinError(#[from] tokio::task::JoinError), JoinError(#[from] tokio::task::JoinError),
} }

View File

@ -2,27 +2,23 @@ use crate::web::ApiError;
use lru::LruCache; use lru::LruCache;
use magnetar_model::emoji::{EmojiResolver, EmojiTag}; use magnetar_model::emoji::{EmojiResolver, EmojiTag};
use magnetar_model::{ck, CalckeyDbError, CalckeyModel}; use magnetar_model::{ck, CalckeyDbError, CalckeyModel};
use miette::Diagnostic;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use strum::VariantNames;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[derive(Debug, Error, VariantNames)] #[derive(Debug, Error, Diagnostic)]
#[error("Emoji cache error: {}")]
pub enum EmojiCacheError { pub enum EmojiCacheError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
#[diagnostic(code(mag::emoji_cache_error::db_error))]
DbError(#[from] CalckeyDbError), DbError(#[from] CalckeyDbError),
} }
impl From<EmojiCacheError> for ApiError { impl From<EmojiCacheError> for ApiError {
fn from(err: EmojiCacheError) -> Self { fn from(err: EmojiCacheError) -> Self {
let mut api_error: ApiError = match err { Self::internal("Cache error", err)
EmojiCacheError::DbError(err) => err.into(),
};
api_error.message = format!("Emoji cache error: {}", api_error.message);
api_error
} }
} }

View File

@ -2,14 +2,16 @@ use crate::web::ApiError;
use lru::LruCache; use lru::LruCache;
use magnetar_model::sea_orm::{EntityTrait, PrimaryKeyTrait}; use magnetar_model::sea_orm::{EntityTrait, PrimaryKeyTrait};
use magnetar_model::{CalckeyDbError, CalckeyModel}; use magnetar_model::{CalckeyDbError, CalckeyModel};
use miette::Diagnostic;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use strum::VariantNames;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex; 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 { pub enum GenericIdCacheError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
DbError(#[from] CalckeyDbError), DbError(#[from] CalckeyDbError),
@ -17,13 +19,7 @@ pub enum GenericIdCacheError {
impl From<GenericIdCacheError> for ApiError { impl From<GenericIdCacheError> for ApiError {
fn from(err: GenericIdCacheError) -> Self { fn from(err: GenericIdCacheError) -> Self {
let mut api_error: ApiError = match err { Self::internal("Cache error", err)
GenericIdCacheError::DbError(err) => err.into(),
};
api_error.message = format!("Generic ID cache error: {}", api_error.message);
api_error
} }
} }

View File

@ -2,13 +2,15 @@ use crate::web::ApiError;
use lru::LruCache; use lru::LruCache;
use magnetar_common::config::MagnetarConfig; use magnetar_common::config::MagnetarConfig;
use magnetar_model::{ck, CalckeyDbError, CalckeyModel}; use magnetar_model::{ck, CalckeyDbError, CalckeyModel};
use miette::Diagnostic;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use strum::VariantNames;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex; 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 { pub enum RemoteInstanceCacheError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
DbError(#[from] CalckeyDbError), DbError(#[from] CalckeyDbError),
@ -16,13 +18,7 @@ pub enum RemoteInstanceCacheError {
impl From<RemoteInstanceCacheError> for ApiError { impl From<RemoteInstanceCacheError> for ApiError {
fn from(err: RemoteInstanceCacheError) -> Self { fn from(err: RemoteInstanceCacheError) -> Self {
let mut api_error: ApiError = match err { Self::internal("Cache error", err)
RemoteInstanceCacheError::DbError(err) => err.into(),
};
api_error.message = format!("Remote instance cache error: {}", api_error.message);
api_error
} }
} }

View File

@ -1,13 +1,15 @@
use crate::web::ApiError; use crate::web::ApiError;
use magnetar_model::{ck, CalckeyDbError, CalckeyModel}; use magnetar_model::{ck, CalckeyDbError, CalckeyModel};
use miette::Diagnostic;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use strum::VariantNames;
use thiserror::Error; use thiserror::Error;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use tracing::error; 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 { pub enum InstanceMetaCacheError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
DbError(#[from] CalckeyDbError), DbError(#[from] CalckeyDbError),
@ -17,14 +19,7 @@ pub enum InstanceMetaCacheError {
impl From<InstanceMetaCacheError> for ApiError { impl From<InstanceMetaCacheError> for ApiError {
fn from(err: InstanceMetaCacheError) -> Self { fn from(err: InstanceMetaCacheError) -> Self {
let mut api_error: ApiError = match err { Self::internal("Cache error", err)
InstanceMetaCacheError::DbError(err) => err.into(),
InstanceMetaCacheError::ChannelClosed => err.into(),
};
api_error.message = format!("Instance meta cache error: {}", api_error.message);
api_error
} }
} }

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use cached::{Cached, TimedCache}; use cached::{Cached, TimedCache};
use strum::VariantNames; use miette::Diagnostic;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::error; use tracing::error;
@ -15,7 +15,9 @@ use magnetar_model::{
InternalStreamMessage, SubMessage, 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 { pub enum UserCacheError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
DbError(#[from] CalckeyDbError), DbError(#[from] CalckeyDbError),
@ -27,6 +29,13 @@ pub enum UserCacheError {
PublicKeyParseError(#[from] ApHttpPublicKeyParseError), PublicKeyParseError(#[from] ApHttpPublicKeyParseError),
} }
impl From<UserCacheError> for ApiError {
fn from(err: UserCacheError) -> Self {
Self::internal("Cache error", err)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CachedLocalUser { pub struct CachedLocalUser {
pub user: Arc<ck::user::Model>, pub user: Arc<ck::user::Model>,
@ -48,21 +57,6 @@ impl TryFrom<(ck::user::Model, ck::user_profile::Model, ck::user_keypair::Model)
} }
} }
impl From<UserCacheError> 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 { struct LocalUserCache {
lifetime: TimedCache<String, ()>, lifetime: TimedCache<String, ()>,
id_to_user: HashMap<String, CachedLocalUser>, id_to_user: HashMap<String, CachedLocalUser>,

View File

@ -2,23 +2,23 @@ use std::convert::Infallible;
use std::sync::Arc; use std::sync::Arc;
use axum::async_trait; use axum::async_trait;
use axum::extract::{FromRequestParts, Request, State};
use axum::extract::rejection::ExtensionRejection; use axum::extract::rejection::ExtensionRejection;
use axum::http::{HeaderMap, StatusCode}; use axum::extract::{FromRequestParts, Request, State};
use axum::http::request::Parts; use axum::http::request::Parts;
use axum::http::{HeaderMap, StatusCode};
use axum::middleware::Next; use axum::middleware::Next;
use axum::response::{IntoResponse, Response}; use axum::response::{IntoResponse, Response};
use headers::{Authorization, HeaderMapExt};
use headers::authorization::Bearer; use headers::authorization::Bearer;
use strum::IntoStaticStr; use headers::{Authorization, HeaderMapExt};
use miette::{miette, Diagnostic};
use thiserror::Error; use thiserror::Error;
use tracing::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::local_user_cache::{CachedLocalUser, UserCacheError};
use crate::service::MagnetarService; use crate::service::MagnetarService;
use crate::web::{ApiError, IntoErrorCode}; use crate::web::ApiError;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum AuthMode { pub enum AuthMode {
@ -45,15 +45,13 @@ pub struct AuthUserRejection(ApiError);
impl From<ExtensionRejection> for AuthUserRejection { impl From<ExtensionRejection> for AuthUserRejection {
fn from(rejection: ExtensionRejection) -> Self { fn from(rejection: ExtensionRejection) -> Self {
AuthUserRejection(ApiError { AuthUserRejection(
status: StatusCode::UNAUTHORIZED, ApiError::new(
code: "Unauthorized".error_code(), StatusCode::UNAUTHORIZED,
message: if cfg!(debug_assertions) { "Unauthorized",
format!("Missing auth extension: {}", rejection) miette!(code = "mag::auth_user_rejection", "Missing auth extension: {}", rejection),
} else { )
"Unauthorized".to_string() )
},
})
} }
} }
@ -89,76 +87,46 @@ pub struct AuthState {
service: Arc<MagnetarService>, service: Arc<MagnetarService>,
} }
#[derive(Debug, Error, IntoStaticStr)] #[derive(Debug, Error, Diagnostic)]
#[error("Auth error: {}")]
enum AuthError { enum AuthError {
#[error("Unsupported authorization scheme")] #[error("Unsupported authorization scheme")]
#[diagnostic(code(mag::auth_error::unsupported_scheme))]
UnsupportedScheme, UnsupportedScheme,
#[error("Cache error: {0}")] #[error("Cache error: {0}")]
#[diagnostic(code(mag::auth_error::cache_error))]
CacheError(#[from] UserCacheError), CacheError(#[from] UserCacheError),
#[error("Database error: {0}")] #[error("Database error: {0}")]
#[diagnostic(code(mag::auth_error::db_error))]
DbError(#[from] CalckeyDbError), DbError(#[from] CalckeyDbError),
#[error("Invalid token")] #[error("Invalid token")]
#[diagnostic(code(mag::auth_error::invalid_token))]
InvalidToken, InvalidToken,
#[error("Invalid token \"{token}\" referencing user \"{user}\"")] #[error("Invalid token referencing user \"{user}\"")]
InvalidTokenUser { token: String, user: String }, #[diagnostic(code(mag::auth_error::invalid_token_user))]
#[error("Invalid access token \"{access_token}\" referencing app \"{app}\"")] InvalidTokenUser { user: String },
InvalidAccessTokenApp { access_token: String, app: String }, #[error("Invalid access token referencing app \"{app}\"")]
#[diagnostic(code(mag::auth_error::invalid_token_app))]
InvalidAccessTokenApp { app: String },
} }
impl From<AuthError> for ApiError { impl From<AuthError> for ApiError {
fn from(err: AuthError) -> Self { fn from(err: AuthError) -> Self {
match err { let code = match err {
AuthError::UnsupportedScheme => ApiError { AuthError::InvalidToken => StatusCode::UNAUTHORIZED,
status: StatusCode::UNAUTHORIZED, _ => StatusCode::INTERNAL_SERVER_ERROR
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);
ApiError { let message = match err {
status: StatusCode::INTERNAL_SERVER_ERROR, AuthError::UnsupportedScheme => "Unsupported authorization scheme",
code: err.error_code(), AuthError::InvalidTokenUser { .. } => "Invalid token and user combination",
message: if cfg!(debug_assertions) { AuthError::InvalidAccessTokenApp { .. } => "Invalid token and app combination",
format!("Invalid token \"{}\" referencing user \"{}\"", token, user) AuthError::CacheError(_) => "Cache error",
} else { AuthError::DbError(_) => "Database error",
"Invalid token-user link".to_string() AuthError::InvalidToken => "Invalid account token"
}, };
}
}
AuthError::InvalidAccessTokenApp {
ref access_token,
ref app,
} => {
error!(
"Invalid access token \"{}\" referencing app \"{}\"",
access_token, app
);
ApiError { ApiError::new(code, message, miette!(err))
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(),
},
}
} }
} }
@ -203,7 +171,6 @@ impl AuthState {
if user.is_none() { if user.is_none() {
return Err(AuthError::InvalidTokenUser { return Err(AuthError::InvalidTokenUser {
token: access_token.id,
user: access_token.user_id, user: access_token.user_id,
}); });
} }
@ -220,7 +187,6 @@ impl AuthState {
}), }),
}), }),
None => Err(AuthError::InvalidAccessTokenApp { None => Err(AuthError::InvalidAccessTokenApp {
access_token: access_token.id,
app: access_token.user_id, app: access_token.user_id,
}), }),
}; };

View File

@ -1,9 +1,10 @@
use axum::{http::HeaderValue, response::IntoResponse}; use axum::{http::HeaderValue, response::IntoResponse};
use hyper::{header, StatusCode}; use hyper::header;
use magnetar_core::web_model::{content_type::ContentXrdXml, ContentType}; use magnetar_core::web_model::{content_type::ContentXrdXml, ContentType};
use miette::miette;
use serde::Serialize; use serde::Serialize;
use crate::web::{ApiError, ErrorCode}; use crate::web::ApiError;
pub struct XrdXmlExt<T>(pub T); pub struct XrdXmlExt<T>(pub T);
@ -20,15 +21,10 @@ impl<T: Serialize> IntoResponse for XrdXmlExt<T> {
buf.into_bytes(), buf.into_bytes(),
) )
.into_response(), .into_response(),
Err(e) => ApiError { Err(e) => ApiError::internal(
status: StatusCode::INTERNAL_SERVER_ERROR, "XmlSerializationError",
code: ErrorCode("XmlSerializationError".into()), miette!(code = "mag::xrd_xml_ext_error", "XML serialization error: {}", e),
message: if cfg!(debug_assertions) { )
format!("Serialization error: {}", e)
} else {
"Serialization error".to_string()
},
}
.into_response(), .into_response(),
} }
} }

View File

@ -5,74 +5,64 @@ use axum::Json;
use magnetar_common::util::FediverseTagParseError; use magnetar_common::util::FediverseTagParseError;
use magnetar_federation::crypto::{ApHttpPrivateKeyParseError, ApHttpPublicKeyParseError}; use magnetar_federation::crypto::{ApHttpPrivateKeyParseError, ApHttpPublicKeyParseError};
use magnetar_model::{CalckeyCacheError, CalckeyDbError}; use magnetar_model::{CalckeyCacheError, CalckeyDbError};
use serde::Serialize; use miette::{diagnostic, miette, Diagnostic, Report};
use serde_json::json; use serde_json::json;
use std::fmt::{Display, Formatter}; use std::borrow::Cow;
use std::fmt::Display;
use thiserror::Error; use thiserror::Error;
use tracing::warn;
use ulid::Ulid;
pub mod auth; pub mod auth;
pub mod extractors; pub mod extractors;
pub mod pagination; 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<T: ?Sized> 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)] #[derive(Debug, Error)]
#[error("API Error")]
pub struct ApiError { pub struct ApiError {
pub status: StatusCode, pub status: StatusCode,
pub code: ErrorCode, pub nonce: Ulid,
pub message: String, pub message: Cow<'static, str>,
pub cause: miette::Report,
} }
impl Display for ApiError { impl ApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { pub fn new(status: StatusCode,
write!( message: impl Into<Cow<'static, str>>,
f, cause: impl Into<Report>) -> Self {
"ApiError[status = \"{}\", code = \"{:?}\"]: \"{}\"", Self {
self.status, self.code, self.message status,
) nonce: Ulid::new(),
message: message.into(),
cause: cause.into(),
} }
} }
#[derive(Debug)] pub fn internal(message: impl Into<Cow<'static, str>>,
pub struct AccessForbidden(pub String); cause: impl Into<Report>) -> Self {
Self::new(StatusCode::INTERNAL_SERVER_ERROR, message, cause)
impl From<&AccessForbidden> for &str {
fn from(_: &AccessForbidden) -> &'static str {
"AccessForbidden"
} }
} }
impl IntoResponse for ApiError { impl IntoResponse for ApiError {
fn into_response(self) -> Response { 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(<dyn Display as ToString>::to_string);
( (
self.status, self.status,
Json(json!({ Json(json!({
"status": self.status.as_u16(), "status": self.status.as_u16(),
"code": self.code, "code": code,
"nonce": nonce,
"message": self.message, "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<AccessForbidden> for ApiError { impl From<AccessForbidden> for ApiError {
fn from(err: AccessForbidden) -> Self { fn from(err: AccessForbidden) -> Self {
Self { Self::new(StatusCode::FORBIDDEN, "Access forbidden", err)
status: StatusCode::FORBIDDEN,
code: err.error_code(),
message: if cfg!(debug_assertions) {
format!("Forbidden: {}", err.0)
} else {
"Forbidden".to_string()
},
}
} }
} }
impl From<FediverseTagParseError> for ApiError { impl From<FediverseTagParseError> for ApiError {
fn from(err: FediverseTagParseError) -> Self { fn from(err: FediverseTagParseError) -> Self {
Self { Self::new(StatusCode::BAD_REQUEST,
status: StatusCode::BAD_REQUEST, "Fediverse tag parse error",
code: err.error_code(), miette!(code = "mag::access_forbidden", "{}", err))
message: if cfg!(debug_assertions) {
format!("Fediverse tag parse error: {}", err)
} else {
"Fediverse tag parse error".to_string()
},
}
} }
} }
impl From<CalckeyDbError> for ApiError { impl From<CalckeyDbError> for ApiError {
fn from(err: CalckeyDbError) -> Self { fn from(err: CalckeyDbError) -> Self {
Self { Self::internal("Database error", miette!(code = "mag::db_error", "{}", err))
status: StatusCode::INTERNAL_SERVER_ERROR,
code: err.error_code(),
message: if cfg!(debug_assertions) {
format!("Database error: {}", err)
} else {
"Database error".to_string()
},
}
} }
} }
impl From<CalckeyCacheError> for ApiError { impl From<CalckeyCacheError> for ApiError {
fn from(err: CalckeyCacheError) -> Self { fn from(err: CalckeyCacheError) -> Self {
Self { Self::internal("Cache error", miette!(code = "mag::cache_error", "{}", err))
status: StatusCode::INTERNAL_SERVER_ERROR,
code: err.error_code(),
message: if cfg!(debug_assertions) {
format!("Cache error: {}", err)
} else {
"Cache error".to_string()
},
}
} }
} }
impl From<PackError> for ApiError { impl From<PackError> for ApiError {
fn from(err: PackError) -> Self { fn from(err: PackError) -> Self {
Self { Self::internal("Data transformation error", miette!(code = "mag::pack_error", "{}", err))
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()
},
}
} }
} }
#[derive(Debug)] #[derive(Debug, Error, Diagnostic)]
#[error("Object not found: {0}")]
#[diagnostic(code(mag::object_not_found))]
pub struct ObjectNotFound(pub String); pub struct ObjectNotFound(pub String);
impl From<&ObjectNotFound> for &str {
fn from(_: &ObjectNotFound) -> Self {
"ObjectNotFound"
}
}
impl From<ObjectNotFound> for ApiError { impl From<ObjectNotFound> for ApiError {
fn from(err: ObjectNotFound) -> Self { fn from(err: ObjectNotFound) -> Self {
Self { Self::new(StatusCode::NOT_FOUND, "Object not found", miette!(err))
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()
},
}
} }
} }
#[derive(Debug)] #[derive(Debug, Error, Diagnostic)]
#[error("Argument out of range: {0}")]
#[diagnostic(code(mag::argument_out_of_range))]
pub struct ArgumentOutOfRange(pub String); pub struct ArgumentOutOfRange(pub String);
impl From<&ArgumentOutOfRange> for &str {
fn from(_: &ArgumentOutOfRange) -> Self {
"ArgumentOutOfRange"
}
}
impl From<ArgumentOutOfRange> for ApiError { impl From<ArgumentOutOfRange> for ApiError {
fn from(err: ArgumentOutOfRange) -> Self { fn from(err: ArgumentOutOfRange) -> Self {
Self { Self::new(StatusCode::BAD_REQUEST, format!("Argument out of range: {}", err.0), err)
status: StatusCode::BAD_REQUEST,
code: err.error_code(),
message: format!("Argument out of range: {}", err.0),
}
} }
} }
impl From<ApHttpPublicKeyParseError> for ApiError { impl From<ApHttpPublicKeyParseError> for ApiError {
fn from(err: ApHttpPublicKeyParseError) -> Self { fn from(err: ApHttpPublicKeyParseError) -> Self {
Self { Self::internal("User public key parse error",
status: StatusCode::INTERNAL_SERVER_ERROR, miette!(code = "mag::ap_http_public_key_parse_error", "{}", err))
code: "ApHttpPublicKeyParseError".error_code(),
message: if cfg!(debug_assertions) {
format!("User public key parse error: {}", err)
} else {
"User public key parse error".to_string()
},
}
} }
} }
impl From<ApHttpPrivateKeyParseError> for ApiError { impl From<ApHttpPrivateKeyParseError> for ApiError {
fn from(err: ApHttpPrivateKeyParseError) -> Self { fn from(err: ApHttpPrivateKeyParseError) -> Self {
Self { Self::internal("User private key parse error",
status: StatusCode::INTERNAL_SERVER_ERROR, miette!(code = "mag::ap_http_private_key_parse_error", "{}", err))
code: "ApHttpPrivateKeyParseError".error_code(),
message: if cfg!(debug_assertions) {
format!("User private key parse error: {}", err)
} else {
"User private key parse error".to_string()
},
}
} }
} }

View File

@ -1,6 +1,6 @@
use crate::service::MagnetarService; use crate::service::MagnetarService;
use crate::util::serialize_as_urlenc; use crate::util::serialize_as_urlenc;
use crate::web::{ApiError, IntoErrorCode}; use crate::web::ApiError;
use axum::extract::rejection::QueryRejection; use axum::extract::rejection::QueryRejection;
use axum::extract::{FromRequestParts, OriginalUri, Query}; use axum::extract::{FromRequestParts, OriginalUri, Query};
use axum::http::header::InvalidHeaderValue; 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_model::sea_orm::prelude::async_trait::async_trait;
use magnetar_sdk::types::{PaginationShape, SpanFilter}; use magnetar_sdk::types::{PaginationShape, SpanFilter};
use magnetar_sdk::util_types::U64Range; use magnetar_sdk::util_types::U64Range;
use miette::{miette, Diagnostic};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use strum::IntoStaticStr;
use thiserror::Error; use thiserror::Error;
use tracing::error; use tracing::error;
@ -39,32 +39,32 @@ struct PaginationQuery {
query_rest: HashMap<String, String>, query_rest: HashMap<String, String>,
} }
#[derive(Debug, Error, IntoStaticStr)] #[derive(Debug, Error, Diagnostic)]
#[error("Pagination builder error: {}")]
pub enum PaginationBuilderError { pub enum PaginationBuilderError {
#[error("Query rejection: {0}")] #[error("Query rejection: {0}")]
#[diagnostic(code(mag::pagination_builder_error::query_rejection))]
QueryRejection(#[from] QueryRejection), QueryRejection(#[from] QueryRejection),
#[error("HTTP error: {0}")] #[error("HTTP error: {0}")]
#[diagnostic(code(mag::pagination_builder_error::http_error))]
HttpError(#[from] axum::http::Error), HttpError(#[from] axum::http::Error),
#[error("Value of out of range error")]
OutOfRange,
#[error("Invalid header value")] #[error("Invalid header value")]
#[diagnostic(code(mag::pagination_builder_error::invalid_header_value))]
InvalidHeaderValue(#[from] InvalidHeaderValue), InvalidHeaderValue(#[from] InvalidHeaderValue),
#[error("Query string serialization error: {0}")] #[error("Query string serialization error: {0}")]
#[diagnostic(code(mag::pagination_builder_error::serialization_error_query))]
SerializationErrorQuery(#[from] serde_urlencoded::ser::Error), SerializationErrorQuery(#[from] serde_urlencoded::ser::Error),
#[error("Query string serialization error: {0}")] #[error("Query string serialization error: {0}")]
#[diagnostic(code(mag::pagination_builder_error::serialization_error_json))]
SerializationErrorJson(#[from] serde_json::Error), SerializationErrorJson(#[from] serde_json::Error),
} }
impl From<PaginationBuilderError> for ApiError { impl From<PaginationBuilderError> for ApiError {
fn from(err: PaginationBuilderError) -> Self { fn from(err: PaginationBuilderError) -> Self {
Self { if matches!(err, PaginationBuilderError::QueryRejection(_)) {
status: StatusCode::INTERNAL_SERVER_ERROR, Self::new(StatusCode::BAD_REQUEST, "Invalid pagination query", miette!(err))
code: err.error_code(),
message: if cfg!(debug_assertions) {
format!("Pagination builder error: {}", err)
} else { } else {
"Pagination builder error".to_string() Self::internal("Pagination error", miette!(err))
},
} }
} }
} }