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)]
|
#[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;
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
110
src/web/auth.rs
110
src/web/auth.rs
|
@ -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,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
191
src/web/mod.rs
191
src/web/mod.rs
|
@ -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()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue