Streamlined error handling
This commit is contained in:
parent
9c42b20fa9
commit
b9160305f1
|
@ -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;
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
|
|
@ -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<EmojiCacheError> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<GenericIdCacheError> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<RemoteInstanceCacheError> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<InstanceMetaCacheError> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<UserCacheError> for ApiError {
|
||||
fn from(err: UserCacheError) -> Self {
|
||||
Self::internal("Cache error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CachedLocalUser {
|
||||
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 {
|
||||
lifetime: TimedCache<String, ()>,
|
||||
id_to_user: HashMap<String, CachedLocalUser>,
|
||||
|
|
110
src/web/auth.rs
110
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<ExtensionRejection> 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<MagnetarService>,
|
||||
}
|
||||
|
||||
#[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<AuthError> 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,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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<T>(pub T);
|
||||
|
||||
|
@ -20,16 +21,11 @@ impl<T: Serialize> IntoResponse for XrdXmlExt<T> {
|
|||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
193
src/web/mod.rs
193
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<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)]
|
||||
#[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<Cow<'static, str>>,
|
||||
cause: impl Into<Report>) -> 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<Cow<'static, str>>,
|
||||
cause: impl Into<Report>) -> 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(<dyn Display as ToString>::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<AccessForbidden> 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<FediverseTagParseError> 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<CalckeyDbError> 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<CalckeyCacheError> 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<PackError> 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<ObjectNotFound> 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<ArgumentOutOfRange> 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<ApHttpPublicKeyParseError> 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<ApHttpPrivateKeyParseError> 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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, String>,
|
||||
}
|
||||
|
||||
#[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<PaginationBuilderError> 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<Arc<MagnetarService>> for Pagination {
|
|||
.build()?;
|
||||
|
||||
let Query(PaginationQuery {
|
||||
pagination,
|
||||
query_rest,
|
||||
}) = parts.extract::<Query<PaginationQuery>>().await?;
|
||||
pagination,
|
||||
query_rest,
|
||||
}) = parts.extract::<Query<PaginationQuery>>().await?;
|
||||
|
||||
Ok(Pagination {
|
||||
base_uri,
|
||||
|
|
Loading…
Reference in New Issue