diff --git a/Cargo.lock b/Cargo.lock index 6b4b42de0..de85b5b0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3741,11 +3741,9 @@ version = "0.0.1-pre.6" dependencies = [ "async-trait", "const_format", - "http 1.1.0", "kitsune-db", "kitsune-error", "serde", - "thiserror", "typed-builder", "vergen", ] diff --git a/crates/kitsune-core/Cargo.toml b/crates/kitsune-core/Cargo.toml index 385ca99d2..d28250abd 100644 --- a/crates/kitsune-core/Cargo.toml +++ b/crates/kitsune-core/Cargo.toml @@ -9,11 +9,9 @@ build = "build.rs" [dependencies] async-trait = "0.1.79" const_format = "0.2.32" -http = "1.1.0" kitsune-db = { path = "../kitsune-db" } kitsune-error = { path = "../kitsune-error" } serde = { version = "1.0.197", features = ["derive"] } -thiserror = "1.0.58" typed-builder = "0.18.1" [build-dependencies] diff --git a/crates/kitsune-core/src/error.rs b/crates/kitsune-core/src/error.rs deleted file mode 100644 index 093083db4..000000000 --- a/crates/kitsune-core/src/error.rs +++ /dev/null @@ -1,48 +0,0 @@ -use http::StatusCode; -use std::borrow::Cow; -use thiserror::Error; - -macro_rules! http_error { - ($($variant_name:ident => $status_code:path),*$(,)?) => { - #[derive(Debug, Error)] - pub enum HttpError { - $( - #[doc = stringify!($variant_name)] - #[error("{}", self.as_str())] - $variant_name, - )* - } - - impl HttpError { - #[inline] - pub fn as_str(&self) -> Cow<'static, str> { - let status_code = self.status_code(); - - status_code - .canonical_reason() - .map_or_else( - || Cow::Owned(status_code.as_str().to_string()), - Cow::Borrowed, - ) - } - - #[inline] - #[must_use] - pub fn status_code(&self) -> ::http::StatusCode { - match self { - $( - Self::$variant_name => $status_code, - )* - } - } - } - } -} - -http_error! { - BadRequest => StatusCode::NOT_FOUND, - InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, - NotFound => StatusCode::NOT_FOUND, - Unauthorised => StatusCode::UNAUTHORIZED, - UnsupportedMediaType => StatusCode::UNSUPPORTED_MEDIA_TYPE, -} diff --git a/crates/kitsune-core/src/lib.rs b/crates/kitsune-core/src/lib.rs index a63d6e5b1..90b1e3c14 100644 --- a/crates/kitsune-core/src/lib.rs +++ b/crates/kitsune-core/src/lib.rs @@ -1,3 +1,2 @@ pub mod consts; -pub mod error; pub mod traits; diff --git a/crates/kitsune-error/src/ext.rs b/crates/kitsune-error/src/ext.rs index 8e1e939be..776c6a69a 100644 --- a/crates/kitsune-error/src/ext.rs +++ b/crates/kitsune-error/src/ext.rs @@ -12,13 +12,10 @@ pub trait ResultExt: sealed::Sealed { impl ResultExt for Result where - E: Into, + E: Into, { #[inline] fn with_error_type(self, ty: ErrorType) -> Result { - self.map_err(|err| Error { - ty, - inner: err.into(), - }) + self.map_err(|err| err.into().with_error_type(ty)) } } diff --git a/kitsune/src/http/extractor/signed_activity.rs b/kitsune/src/http/extractor/signed_activity.rs index 15adff0e7..2e844ce4a 100644 --- a/kitsune/src/http/extractor/signed_activity.rs +++ b/kitsune/src/http/extractor/signed_activity.rs @@ -11,7 +11,7 @@ use diesel::{ExpressionMethods, QueryDsl, SelectableHelper}; use diesel_async::RunQueryDsl; use http::StatusCode; use http_body_util::BodyExt; -use kitsune_core::{error::HttpError, traits::fetcher::AccountFetchOptions}; +use kitsune_core::traits::fetcher::AccountFetchOptions; use kitsune_db::{model::account::Account, schema::accounts, with_connection, PgPool}; use kitsune_error::{bail, Error, ErrorType, Result}; use kitsune_type::ap::Activity; @@ -123,7 +123,7 @@ async fn verify_signature( // Otherwise a random person with a key that's known to the database could start signing activities willy-nilly and the server would accept it. if let Some(expected_account) = expected_account { if expected_account.url != remote_user.url { - return Err(HttpError::Unauthorised.into()); + bail!(type = ErrorType::Unauthorized, "remote account isn't the author of the activity"); } } diff --git a/kitsune/src/http/handler/mastodon/api/v1/accounts/follow.rs b/kitsune/src/http/handler/mastodon/api/v1/accounts/follow.rs index f4f959797..59f27d4d7 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/accounts/follow.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/accounts/follow.rs @@ -4,8 +4,7 @@ use axum::{ extract::{Path, State}, Json, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{bail, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::account::{AccountService, Follow}; use kitsune_type::mastodon::relationship::Relationship; @@ -39,7 +38,7 @@ pub async fn post( follow_body: Option>, ) -> Result> { if user_data.account.id == id { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "user tried to follow themselves"); } let follow = Follow::builder() diff --git a/kitsune/src/http/handler/mastodon/api/v1/accounts/lookup.rs b/kitsune/src/http/handler/mastodon/api/v1/accounts/lookup.rs index 95cffe6b8..43d3936b1 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/accounts/lookup.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/accounts/lookup.rs @@ -4,8 +4,7 @@ use axum::{ extract::{Query, State}, Json, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{kitsune_error, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::account::{AccountService, GetUser}; use kitsune_type::mastodon::Account; @@ -50,7 +49,7 @@ pub async fn get( let account = account_service .get(get_user) .await? - .ok_or(HttpError::NotFound)?; + .ok_or_else(|| kitsune_error!(type = ErrorType::NotFound, "account not found"))?; Ok(Json(mastodon_mapper.map(account).await?)) } diff --git a/kitsune/src/http/handler/mastodon/api/v1/accounts/mod.rs b/kitsune/src/http/handler/mastodon/api/v1/accounts/mod.rs index 0eb1101c5..39299fe7f 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/accounts/mod.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/accounts/mod.rs @@ -3,8 +3,7 @@ use axum::{ extract::{Path, State}, routing, Json, Router, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{kitsune_error, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::account::AccountService; use kitsune_type::mastodon; @@ -34,7 +33,7 @@ async fn get( let account = account_service .get_by_id(id) .await? - .ok_or(HttpError::NotFound)?; + .ok_or_else(|| kitsune_error!(type = ErrorType::NotFound, "account not found"))?; Ok(Json(mastodon_mapper.map(account).await?)) } diff --git a/kitsune/src/http/handler/mastodon/api/v1/accounts/unfollow.rs b/kitsune/src/http/handler/mastodon/api/v1/accounts/unfollow.rs index 60536239b..ae75d7cd8 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/accounts/unfollow.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/accounts/unfollow.rs @@ -4,8 +4,7 @@ use axum::{ extract::{Path, State}, Json, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{bail, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::account::{AccountService, Unfollow}; use kitsune_type::mastodon::relationship::Relationship; @@ -19,7 +18,7 @@ pub async fn post( Path(id): Path, ) -> Result> { if user_data.account.id == id { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "user tried to unfollow themselves"); } let unfollow = Unfollow::builder() diff --git a/kitsune/src/http/handler/mastodon/api/v1/accounts/update_credentials.rs b/kitsune/src/http/handler/mastodon/api/v1/accounts/update_credentials.rs index 688cf62ac..8634c8884 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/accounts/update_credentials.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/accounts/update_credentials.rs @@ -6,8 +6,7 @@ use axum::{ extract::{Multipart, State}, Json, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{bail, kitsune_error, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::{ account::{AccountService, Update}, @@ -43,7 +42,7 @@ pub async fn patch( "note" => update.note(field.text().await?), "avatar" => { let Some(content_type) = field.content_type().map(ToString::to_string) else { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "invalid content-type"); }; let stream = buffer_multipart_to_tempfile(&mut field).await?; @@ -52,13 +51,13 @@ pub async fn patch( .content_type(content_type) .stream(stream) .build() - .map_err(|_| HttpError::BadRequest)?; + .unwrap(); update.avatar(upload) } "header" => { let Some(content_type) = field.content_type().map(ToString::to_string) else { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "invalid content-type"); }; let stream = buffer_multipart_to_tempfile(&mut field).await?; @@ -67,7 +66,7 @@ pub async fn patch( .content_type(content_type) .stream(stream) .build() - .map_err(|_| HttpError::BadRequest)?; + .unwrap(); update.header(upload) } @@ -76,7 +75,12 @@ pub async fn patch( }; } - let update = update.build().map_err(|_| HttpError::BadRequest)?; + let update = update.build().map_err(|err| { + kitsune_error!( + type = ErrorType::BadRequest(Some(err.to_string())), + "missing upload field" + ) + })?; let account = account_service.update(update).await?; Ok(Json(mastodon_mapper.map(account).await?)) diff --git a/kitsune/src/http/handler/mastodon/api/v1/follow_requests/accept.rs b/kitsune/src/http/handler/mastodon/api/v1/follow_requests/accept.rs index 4d7f3448f..69438d0bd 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/follow_requests/accept.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/follow_requests/accept.rs @@ -4,8 +4,7 @@ use axum::{ extract::{Path, State}, Json, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{bail, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::account::{AccountService, FollowRequest}; use kitsune_type::mastodon::relationship::Relationship; @@ -30,7 +29,7 @@ pub async fn post( Path(id): Path, ) -> Result> { if user_data.account.id == id { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "user tried to accept a follow to themselves"); } let follow_request = FollowRequest::builder() @@ -49,6 +48,6 @@ pub async fn post( .await?, )) } else { - Err(HttpError::BadRequest.into()) + bail!(type = ErrorType::BadRequest(None), "follow request wasn't found in the database"); } } diff --git a/kitsune/src/http/handler/mastodon/api/v1/follow_requests/reject.rs b/kitsune/src/http/handler/mastodon/api/v1/follow_requests/reject.rs index 7dcdc39e6..226e0b9db 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/follow_requests/reject.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/follow_requests/reject.rs @@ -4,8 +4,7 @@ use axum::{ extract::{Path, State}, Json, }; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{bail, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::account::{AccountService, FollowRequest}; use kitsune_type::mastodon::relationship::Relationship; @@ -30,7 +29,7 @@ pub async fn post( Path(id): Path, ) -> Result> { if user_data.account.id == id { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "user tried to reject a follow to themselves"); } let follow_request = FollowRequest::builder() @@ -49,6 +48,6 @@ pub async fn post( .await?, )) } else { - Err(HttpError::BadRequest.into()) + bail!(type = ErrorType::BadRequest(None), "follow request wasn't found in the database"); } } diff --git a/kitsune/src/http/handler/mastodon/api/v1/media.rs b/kitsune/src/http/handler/mastodon/api/v1/media.rs index bbf97e53b..f586b6508 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/media.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/media.rs @@ -11,8 +11,7 @@ use axum::{ routing, Json, Router, }; use futures_util::TryFutureExt; -use kitsune_core::error::HttpError; -use kitsune_error::{Error, Result}; +use kitsune_error::{kitsune_error, Error, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::attachment::{AttachmentService, Update, Upload}; use kitsune_type::mastodon::MediaAttachment; @@ -88,7 +87,13 @@ pub async fn post( } } - let upload = upload.build().map_err(|_| HttpError::BadRequest)?; + let upload = upload.build().map_err(|err| { + kitsune_error!( + type = ErrorType::BadRequest(Some(err.to_string())), + "not all fields were filled" + ) + })?; + let media_attachment = attachment_service.upload(upload).await?; Ok(Json(mastodon_mapper.map(media_attachment).await?)) } diff --git a/kitsune/src/http/handler/mastodon/api/v1/notifications/mod.rs b/kitsune/src/http/handler/mastodon/api/v1/notifications/mod.rs index 8ba36e3b5..e059c5df7 100644 --- a/kitsune/src/http/handler/mastodon/api/v1/notifications/mod.rs +++ b/kitsune/src/http/handler/mastodon/api/v1/notifications/mod.rs @@ -13,8 +13,7 @@ use axum::{ }; use axum_extra::extract::Query; use futures_util::{TryFutureExt, TryStreamExt}; -use kitsune_core::error::HttpError; -use kitsune_error::{Error, Result}; +use kitsune_error::{kitsune_error, Error, ErrorType, Result}; use kitsune_mastodon::MastodonMapper; use kitsune_service::notification::{GetNotifications, NotificationService}; use kitsune_type::mastodon::{notification::NotificationType, Notification}; @@ -121,7 +120,7 @@ pub async fn get_by_id( let notification = notification_service .get_notification_by_id(id, user_data.account.id) .await? - .ok_or(HttpError::NotFound)?; + .ok_or_else(|| kitsune_error!(type = ErrorType::NotFound, "notification not found"))?; Ok(Json(mastodon_mapper.map(notification).await?)) } diff --git a/kitsune/src/http/handler/oidc/callback.rs b/kitsune/src/http/handler/oidc/callback.rs index 0bac86d3f..504421f03 100644 --- a/kitsune/src/http/handler/oidc/callback.rs +++ b/kitsune/src/http/handler/oidc/callback.rs @@ -5,12 +5,11 @@ use axum::{ }; use diesel::{ExpressionMethods, OptionalExtension, QueryDsl}; use diesel_async::RunQueryDsl; -use kitsune_core::error::HttpError; use kitsune_db::{ schema::{oauth2_applications, users}, with_connection, PgPool, }; -use kitsune_error::Result; +use kitsune_error::{bail, ErrorType, Result}; use kitsune_oidc::OidcService; use kitsune_service::user::{Register, UserService}; use serde::Deserialize; @@ -29,7 +28,7 @@ pub async fn get( Query(query): Query, ) -> Result { let Some(oidc_service) = oidc_service else { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "oidc not configured"); }; let user_info = oidc_service.get_user_info(query.state, query.code).await?; diff --git a/kitsune/src/http/handler/users/inbox.rs b/kitsune/src/http/handler/users/inbox.rs index 39de53694..8dda08759 100644 --- a/kitsune/src/http/handler/users/inbox.rs +++ b/kitsune/src/http/handler/users/inbox.rs @@ -4,7 +4,6 @@ use diesel::{BoolExpressionMethods, ExpressionMethods, QueryDsl, SelectableHelpe use diesel_async::RunQueryDsl; use iso8601_timestamp::Timestamp; use kitsune_activitypub::{process_new_object, update_object, ProcessNewObject}; -use kitsune_core::error::HttpError; use kitsune_db::{ model::{ account::Account, @@ -18,7 +17,7 @@ use kitsune_db::{ schema::{accounts_follows, accounts_preferences, notifications, posts, posts_favourites}, with_connection, }; -use kitsune_error::{Error, Result}; +use kitsune_error::{bail, Error, ErrorType, Result}; use kitsune_federation_filter::FederationFilter; use kitsune_jobs::deliver::accept::DeliverAccept; use kitsune_service::job::Enqueue; @@ -40,7 +39,7 @@ async fn accept_activity(state: &Zustand, activity: Activity) -> Result<()> { async fn announce_activity(state: &Zustand, author: Account, activity: Activity) -> Result<()> { let Some(reposted_post) = state.fetcher.fetch_post(activity.object().into()).await? else { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "announced post couldn't be fetched"); }; with_connection!(state.db_pool, |db_conn| { @@ -105,7 +104,7 @@ async fn follow_activity(state: &Zustand, author: Account, activity: Activity) - .fetch_account(activity.object().into()) .await? else { - return Err(HttpError::BadRequest.into()); + bail!(type = ErrorType::BadRequest(None), "followed account couldn't be fetched"); }; let approved_at = followed_user.locked.not().then(Timestamp::now_utc); diff --git a/kitsune/src/http/handler/users/mod.rs b/kitsune/src/http/handler/users/mod.rs index f7cc1768e..e70af5d1d 100644 --- a/kitsune/src/http/handler/users/mod.rs +++ b/kitsune/src/http/handler/users/mod.rs @@ -4,8 +4,7 @@ use axum::{ routing, Router, }; use kitsune_activitypub::mapping::IntoObject; -use kitsune_core::error::HttpError; -use kitsune_error::Result; +use kitsune_error::{kitsune_error, ErrorType, Result}; use kitsune_service::account::AccountService; use kitsune_type::ap::actor::Actor; use speedy_uuid::Uuid; @@ -24,7 +23,7 @@ async fn get( let account = account_service .get_by_id(account_id) .await? - .ok_or(HttpError::NotFound)?; + .ok_or_else(|| kitsune_error!(type = ErrorType::NotFound, "account not found"))?; Ok(ActivityPubJson( account.into_object(state.ap_state()).await?, diff --git a/kitsune/src/http/util.rs b/kitsune/src/http/util.rs index fb35b8136..4bcbb3cb2 100644 --- a/kitsune/src/http/util.rs +++ b/kitsune/src/http/util.rs @@ -1,7 +1,7 @@ use axum::extract::multipart; use bytes::Bytes; +use color_eyre::eyre::Context; use futures_util::{Stream, TryStreamExt}; -use kitsune_core::error::HttpError; use kitsune_error::Result; use std::io::SeekFrom; use tempfile::tempfile; @@ -19,13 +19,13 @@ pub async fn buffer_multipart_to_tempfile( let mut tempfile = File::from_std(tempfile); while let Some(chunk) = field.chunk().await? { - if let Err(error) = tempfile.write_all(&chunk).await { - error!(?error, "Failed to write chunk to tempfile"); - return Err(HttpError::InternalServerError.into()); - } + tempfile + .write_all(&chunk) + .await + .wrap_err("failed to write chunk to tempfile")?; } - tempfile.seek(SeekFrom::Start(0)).await.unwrap(); + tempfile.seek(SeekFrom::Start(0)).await?; Ok(ReaderStream::new(tempfile).map_err(Into::into)) }