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)]
#[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;

View File

@ -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),
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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>,

View File

@ -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,
}),
};

View File

@ -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(),
}
}
}

View File

@ -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))
}
}

View File

@ -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,