diff --git a/Cargo.lock b/Cargo.lock index e6ee29d22..b2d3a7bf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2911,6 +2911,7 @@ dependencies = [ "totp-rs", "tracing", "tracing-error", + "ts-rs", "typed-builder", "url", "uuid", diff --git a/crates/api/src/comment/distinguish.rs b/crates/api/src/comment/distinguish.rs index 17f45bfd4..1478ee220 100644 --- a/crates/api/src/comment/distinguish.rs +++ b/crates/api/src/comment/distinguish.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::CommentView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for DistinguishComment { @@ -46,7 +46,7 @@ impl Perform for DistinguishComment { .build(); Comment::update(context.pool(), comment_id, &form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; let comment_id = data.comment_id; let person_id = local_user_view.person.id; diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index 6c4bdebc7..f44e03dcf 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -16,7 +16,7 @@ use lemmy_db_schema::{ traits::Likeable, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for CreateCommentLike { @@ -69,7 +69,7 @@ impl Perform for CreateCommentLike { if do_add { CommentLike::like(context.pool(), &like_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_like_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; } build_comment_response( diff --git a/crates/api/src/comment/save.rs b/crates/api/src/comment/save.rs index 70c9745f8..42c91bf05 100644 --- a/crates/api/src/comment/save.rs +++ b/crates/api/src/comment/save.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Saveable, }; use lemmy_db_views::structs::CommentView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for SaveComment { @@ -29,11 +29,11 @@ impl Perform for SaveComment { if data.save { CommentSaved::save(context.pool(), &comment_saved_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_save_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntSaveComment)?; } else { CommentSaved::unsave(context.pool(), &comment_saved_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_save_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntSaveComment)?; } let comment_id = data.comment_id; diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index 8ef975786..5c3157833 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ traits::Reportable, }; use lemmy_db_views::structs::{CommentReportView, CommentView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; /// Creates a comment report and notifies the moderators of the community #[async_trait::async_trait(?Send)] @@ -47,7 +47,7 @@ impl Perform for CreateCommentReport { let report = CommentReport::report(context.pool(), &report_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; let comment_report_view = CommentReportView::read(context.pool(), report.id, person_id).await?; diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs index 5f1ae97dd..88fd70071 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/comment_report/resolve.rs @@ -7,7 +7,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable}; use lemmy_db_views::structs::CommentReportView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; /// Resolves or unresolves a comment report and notifies the moderators of the community #[async_trait::async_trait(?Send)] @@ -32,11 +32,11 @@ impl Perform for ResolveCommentReport { if data.resolved { CommentReport::resolve(context.pool(), report_id, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } else { CommentReport::unresolve(context.pool(), report_id, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } let report_id = data.report_id; diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index a7afd952d..2d703577e 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ traits::{Crud, Joinable}, }; use lemmy_db_views_actor::structs::CommunityModeratorView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for AddModToCommunity { @@ -33,7 +33,7 @@ impl Perform for AddModToCommunity { is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?; let community = Community::read(context.pool(), community_id).await?; if local_user_view.person.admin && !community.local { - return Err(LemmyError::from_message("not_a_moderator")); + return Err(LemmyErrorType::NotAModerator)?; } // Update in local database @@ -44,11 +44,11 @@ impl Perform for AddModToCommunity { if data.added { CommunityModerator::join(context.pool(), &community_moderator_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?; } else { CommunityModerator::leave(context.pool(), &community_moderator_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?; } // Mod tables diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 330c2c56d..1d3865e13 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{time::naive_from_unix, validation::is_valid_body_field}, }; @@ -53,7 +53,7 @@ impl Perform for BanFromCommunity { if data.ban { CommunityPersonBan::ban(context.pool(), &community_user_ban_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_user_already_banned"))?; + .with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?; // Also unsubscribe them from the community, if they are subscribed let community_follower_form = CommunityFollowerForm { @@ -68,7 +68,7 @@ impl Perform for BanFromCommunity { } else { CommunityPersonBan::unban(context.pool(), &community_user_ban_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_user_already_banned"))?; + .with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?; } // Remove/Restore their data if that's desired diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index fe3fb6e07..20c601eb3 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ traits::{Blockable, Followable}, }; use lemmy_db_views_actor::structs::CommunityView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for BlockCommunity { @@ -37,7 +37,7 @@ impl Perform for BlockCommunity { if data.block { CommunityBlock::block(context.pool(), &community_block_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?; // Also, unfollow the community, and send a federated unfollow let community_follower_form = CommunityFollowerForm { @@ -52,7 +52,7 @@ impl Perform for BlockCommunity { } else { CommunityBlock::unblock(context.pool(), &community_block_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?; } let community_view = diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index 39feeff25..6d3d82854 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ traits::{Crud, Followable}, }; use lemmy_db_views_actor::structs::CommunityView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for FollowCommunity { @@ -39,19 +39,19 @@ impl Perform for FollowCommunity { CommunityFollower::follow(context.pool(), &community_follower_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; } else { // Mark as pending, the actual federation activity is sent via `SendActivity` handler community_follower_form.pending = true; CommunityFollower::follow(context.pool(), &community_follower_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; } } if !data.follow { CommunityFollower::unfollow(context.pool(), &community_follower_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; } let community_id = data.community_id; diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs index f8d4b1fd7..ecc9d390d 100644 --- a/crates/api/src/community/hide.rs +++ b/crates/api/src/community/hide.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for HideCommunity { @@ -41,7 +41,7 @@ impl Perform for HideCommunity { let community_id = data.community_id; Community::update(context.pool(), community_id, &community_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community_hidden_status"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?; ModHideCommunity::create(context.pool(), &mod_hide_community_form).await?; diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs index 10ba1c4fa..192db9daf 100644 --- a/crates/api/src/community/transfer.rs +++ b/crates/api/src/community/transfer.rs @@ -14,7 +14,10 @@ use lemmy_db_schema::{ traits::{Crud, Joinable}, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::{error::LemmyError, location_info}; +use lemmy_utils::{ + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + location_info, +}; // TODO: we dont do anything for federation here, it should be updated the next time the community // gets fetched. i hope we can get rid of the community creator role soon. @@ -39,7 +42,7 @@ impl Perform for TransferCommunity { if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok()) { - return Err(LemmyError::from_message("not_an_admin")); + return Err(LemmyErrorType::NotAnAdmin)?; } // You have to re-do the community_moderator table, reordering it. @@ -66,7 +69,7 @@ impl Perform for TransferCommunity { CommunityModerator::join(context.pool(), &community_moderator_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?; } // Mod tables @@ -82,12 +85,12 @@ impl Perform for TransferCommunity { let person_id = local_user_view.person.id; let community_view = CommunityView::read(context.pool(), community_id, Some(person_id), None) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; let community_id = data.community_id; let moderators = CommunityModeratorView::for_community(context.pool(), community_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; // Return the jwt Ok(GetCommunityResponse { diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 44e7a76b5..f47d1bd56 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -3,7 +3,10 @@ use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine}; use captcha::Captcha; use lemmy_api_common::{context::LemmyContext, utils::local_site_to_slur_regex}; use lemmy_db_schema::source::local_site::LocalSite; -use lemmy_utils::{error::LemmyError, utils::slurs::check_slurs}; +use lemmy_utils::{ + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + utils::slurs::check_slurs, +}; use std::io::Cursor; mod comment; @@ -37,7 +40,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result Result header, - None => return Err(LemmyError::from_message("couldnt_create_audio_captcha")), + None => return Err(LemmyErrorType::CouldntCreateAudioCaptcha)?, }; - let wav_write_result = wav::write( + wav::write( header, &wav::BitDepth::Sixteen(concat_samples), &mut output_buffer, - ); - if let Err(e) = wav_write_result { - return Err(LemmyError::from_error_message( - e, - "couldnt_create_audio_captcha", - )); - } + ) + .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?; Ok(base64.encode(output_buffer.into_inner())) } @@ -68,10 +66,10 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul check_slurs(reason, slur_regex)?; if reason.is_empty() { - return Err(LemmyError::from_message("report_reason_required")); + return Err(LemmyErrorType::ReportReasonRequired)?; } if reason.chars().count() > 1000 { - return Err(LemmyError::from_message("report_too_long")); + return Err(LemmyErrorType::ReportTooLong)?; } Ok(()) } diff --git a/crates/api/src/local_user/add_admin.rs b/crates/api/src/local_user/add_admin.rs index 0d2db3771..a58174fb4 100644 --- a/crates/api/src/local_user/add_admin.rs +++ b/crates/api/src/local_user/add_admin.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views_actor::structs::PersonView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for AddAdmin { @@ -35,7 +35,7 @@ impl Perform for AddAdmin { &PersonUpdateForm::builder().admin(Some(added)).build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?; // Mod tables let form = ModAddForm { diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index 2c2d363e3..15bc11cc6 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -14,7 +14,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{time::naive_from_unix, validation::is_valid_body_field}, }; @@ -45,7 +45,7 @@ impl Perform for BanPerson { .build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?; // Remove their data if that's desired let remove_data = data.remove_data.unwrap_or(false); diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs index c9eaa2725..67b1f521d 100644 --- a/crates/api/src/local_user/block.rs +++ b/crates/api/src/local_user/block.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Blockable, }; use lemmy_db_views_actor::structs::PersonView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for BlockPerson { @@ -26,7 +26,7 @@ impl Perform for BlockPerson { // Don't let a person block themselves if target_id == person_id { - return Err(LemmyError::from_message("cant_block_yourself")); + return Err(LemmyErrorType::CantBlockYourself)?; } let person_block_form = PersonBlockForm { @@ -37,17 +37,17 @@ impl Perform for BlockPerson { let target_person_view = PersonView::read(context.pool(), target_id).await?; if target_person_view.person.admin { - return Err(LemmyError::from_message("cant_block_admin")); + return Err(LemmyErrorType::CantBlockAdmin)?; } if data.block { PersonBlock::block(context.pool(), &person_block_form) .await - .map_err(|e| LemmyError::from_error_message(e, "person_block_already_exists"))?; + .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } else { PersonBlock::unblock(context.pool(), &person_block_form) .await - .map_err(|e| LemmyError::from_error_message(e, "person_block_already_exists"))?; + .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } Ok(BlockPersonResponse { diff --git a/crates/api/src/local_user/change_password.rs b/crates/api/src/local_user/change_password.rs index 60e0b960d..ffe452c92 100644 --- a/crates/api/src/local_user/change_password.rs +++ b/crates/api/src/local_user/change_password.rs @@ -7,7 +7,10 @@ use lemmy_api_common::{ utils::{local_user_view_from_jwt, password_length_check}, }; use lemmy_db_schema::source::local_user::LocalUser; -use lemmy_utils::{claims::Claims, error::LemmyError}; +use lemmy_utils::{ + claims::Claims, + error::{LemmyError, LemmyErrorType}, +}; #[async_trait::async_trait(?Send)] impl Perform for ChangePassword { @@ -22,7 +25,7 @@ impl Perform for ChangePassword { // Make sure passwords match if data.new_password != data.new_password_verify { - return Err(LemmyError::from_message("passwords_dont_match")); + return Err(LemmyErrorType::PasswordsDoNotMatch)?; } // Check the old password @@ -32,7 +35,7 @@ impl Perform for ChangePassword { ) .unwrap_or(false); if !valid { - return Err(LemmyError::from_message("password_incorrect")); + return Err(LemmyErrorType::IncorrectLogin)?; } let local_user_id = local_user_view.local_user.id; diff --git a/crates/api/src/local_user/change_password_after_reset.rs b/crates/api/src/local_user/change_password_after_reset.rs index de05f8e59..b6da6c324 100644 --- a/crates/api/src/local_user/change_password_after_reset.rs +++ b/crates/api/src/local_user/change_password_after_reset.rs @@ -10,7 +10,10 @@ use lemmy_db_schema::{ RegistrationMode, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::{claims::Claims, error::LemmyError}; +use lemmy_utils::{ + claims::Claims, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, +}; #[async_trait::async_trait(?Send)] impl Perform for PasswordChangeAfterReset { @@ -30,14 +33,14 @@ impl Perform for PasswordChangeAfterReset { // Make sure passwords match if data.password != data.password_verify { - return Err(LemmyError::from_message("passwords_dont_match")); + return Err(LemmyErrorType::PasswordsDoNotMatch)?; } // Update the user with the new password let password = data.password.clone(); let updated_local_user = LocalUser::update_password(context.pool(), local_user_id, &password) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?; // Return the jwt if login is allowed let site_view = SiteView::read_local(context.pool()).await?; diff --git a/crates/api/src/local_user/login.rs b/crates/api/src/local_user/login.rs index 3bf177666..f93ef335e 100644 --- a/crates/api/src/local_user/login.rs +++ b/crates/api/src/local_user/login.rs @@ -7,7 +7,11 @@ use lemmy_api_common::{ utils::{check_registration_application, check_user_valid}, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; -use lemmy_utils::{claims::Claims, error::LemmyError, utils::validation::check_totp_2fa_valid}; +use lemmy_utils::{ + claims::Claims, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + utils::validation::check_totp_2fa_valid, +}; #[async_trait::async_trait(?Send)] impl Perform for Login { @@ -23,7 +27,7 @@ impl Perform for Login { let username_or_email = data.username_or_email.clone(); let local_user_view = LocalUserView::find_by_email_or_name(context.pool(), &username_or_email) .await - .map_err(|e| LemmyError::from_error_message(e, "incorrect_login"))?; + .with_lemmy_type(LemmyErrorType::IncorrectLogin)?; // Verify the password let valid: bool = verify( @@ -32,7 +36,7 @@ impl Perform for Login { ) .unwrap_or(false); if !valid { - return Err(LemmyError::from_message("incorrect_login")); + return Err(LemmyErrorType::IncorrectLogin)?; } check_user_valid( local_user_view.person.banned, @@ -46,7 +50,7 @@ impl Perform for Login { && site_view.local_site.require_email_verification && !local_user_view.local_user.email_verified { - return Err(LemmyError::from_message("email_not_verified")); + return Err(LemmyErrorType::EmailNotVerified)?; } check_registration_application(&local_user_view, &site_view.local_site, context.pool()).await?; diff --git a/crates/api/src/local_user/notifications/mark_all_read.rs b/crates/api/src/local_user/notifications/mark_all_read.rs index cc2999698..fada65118 100644 --- a/crates/api/src/local_user/notifications/mark_all_read.rs +++ b/crates/api/src/local_user/notifications/mark_all_read.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::source::{ person_mention::PersonMention, private_message::PrivateMessage, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for MarkAllAsRead { @@ -25,17 +25,17 @@ impl Perform for MarkAllAsRead { // Mark all comment_replies as read CommentReply::mark_all_as_read(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; // Mark all user mentions as read PersonMention::mark_all_as_read(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; // Mark all private_messages as read PrivateMessage::mark_all_as_read(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; Ok(GetRepliesResponse { replies: vec![] }) } diff --git a/crates/api/src/local_user/notifications/mark_mention_read.rs b/crates/api/src/local_user/notifications/mark_mention_read.rs index dd3c413f2..668286db9 100644 --- a/crates/api/src/local_user/notifications/mark_mention_read.rs +++ b/crates/api/src/local_user/notifications/mark_mention_read.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views_actor::structs::PersonMentionView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for MarkPersonMentionAsRead { @@ -28,7 +28,7 @@ impl Perform for MarkPersonMentionAsRead { let read_person_mention = PersonMention::read(context.pool(), person_mention_id).await?; if local_user_view.person.id != read_person_mention.recipient_id { - return Err(LemmyError::from_message("couldnt_update_comment")); + return Err(LemmyErrorType::CouldntUpdateComment)?; } let person_mention_id = read_person_mention.id; @@ -39,7 +39,7 @@ impl Perform for MarkPersonMentionAsRead { &PersonMentionUpdateForm { read }, ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; let person_mention_id = read_person_mention.id; let person_id = local_user_view.person.id; diff --git a/crates/api/src/local_user/notifications/mark_reply_read.rs b/crates/api/src/local_user/notifications/mark_reply_read.rs index a8291cd50..2e338367e 100644 --- a/crates/api/src/local_user/notifications/mark_reply_read.rs +++ b/crates/api/src/local_user/notifications/mark_reply_read.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views_actor::structs::CommentReplyView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for MarkCommentReplyAsRead { @@ -28,7 +28,7 @@ impl Perform for MarkCommentReplyAsRead { let read_comment_reply = CommentReply::read(context.pool(), comment_reply_id).await?; if local_user_view.person.id != read_comment_reply.recipient_id { - return Err(LemmyError::from_message("couldnt_update_comment")); + return Err(LemmyErrorType::CouldntUpdateComment)?; } let comment_reply_id = read_comment_reply.id; @@ -40,7 +40,7 @@ impl Perform for MarkCommentReplyAsRead { &CommentReplyUpdateForm { read }, ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; let comment_reply_id = read_comment_reply.id; let person_id = local_user_view.person.id; diff --git a/crates/api/src/local_user/reset_password.rs b/crates/api/src/local_user/reset_password.rs index b5325608d..0f896f477 100644 --- a/crates/api/src/local_user/reset_password.rs +++ b/crates/api/src/local_user/reset_password.rs @@ -7,7 +7,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::password_reset_request::PasswordResetRequest; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for PasswordReset { @@ -24,7 +24,7 @@ impl Perform for PasswordReset { let email = data.email.to_lowercase(); let local_user_view = LocalUserView::find_by_email(context.pool(), &email) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_that_username_or_email"))?; + .with_lemmy_type(LemmyErrorType::IncorrectLogin)?; // Check for too many attempts (to limit potential abuse) let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count( @@ -33,7 +33,7 @@ impl Perform for PasswordReset { ) .await?; if recent_resets_count >= 3 { - return Err(LemmyError::from_message("password_reset_limit_reached")); + return Err(LemmyErrorType::PasswordResetLimitReached)?; } // Email the pure token to the user. diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 8f8d3194e..578d17732 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::SiteView; use lemmy_utils::{ claims::Claims, - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::validation::{ build_totp_2fa, generate_totp_2fa_secret, @@ -57,7 +57,7 @@ impl Perform for SaveUserSettings { // When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value if let Some(email) = &email { if email.is_none() && site_view.local_site.require_email_verification { - return Err(LemmyError::from_message("email_required")); + return Err(LemmyErrorType::EmailRequired)?; } } @@ -92,7 +92,7 @@ impl Perform for SaveUserSettings { Person::update(context.pool(), person_id, &person_form) .await - .map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?; + .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; if let Some(discussion_languages) = data.discussion_languages.clone() { LocalUserLanguage::update(context.pool(), discussion_languages, local_user_id).await?; @@ -137,12 +137,12 @@ impl Perform for SaveUserSettings { let err_type = if e.to_string() == "duplicate key value violates unique constraint \"local_user_email_key\"" { - "email_already_exists" + LemmyErrorType::EmailAlreadyExists } else { - "user_already_exists" + LemmyErrorType::UserAlreadyExists }; - return Err(LemmyError::from_error_message(e, err_type)); + return Err(e).with_lemmy_type(err_type); } }; diff --git a/crates/api/src/local_user/verify_email.rs b/crates/api/src/local_user/verify_email.rs index 0807eebe0..2a0b2f3c5 100644 --- a/crates/api/src/local_user/verify_email.rs +++ b/crates/api/src/local_user/verify_email.rs @@ -11,7 +11,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for VerifyEmail { @@ -21,7 +21,7 @@ impl Perform for VerifyEmail { let token = self.token.clone(); let verification = EmailVerification::read_for_token(context.pool(), &token) .await - .map_err(|e| LemmyError::from_error_message(e, "token_not_found"))?; + .with_lemmy_type(LemmyErrorType::TokenNotFound)?; let form = LocalUserUpdateForm::builder() // necessary in case this is a new signup diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index 5811107c2..896ab5ba2 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Likeable}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for CreatePostLike { @@ -57,7 +57,7 @@ impl Perform for CreatePostLike { if do_add { PostLike::like(context.pool(), &like_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_like_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; } // Mark the post as read diff --git a/crates/api/src/post/save.rs b/crates/api/src/post/save.rs index 3213d2be6..bc43610af 100644 --- a/crates/api/src/post/save.rs +++ b/crates/api/src/post/save.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Saveable, }; use lemmy_db_views::structs::PostView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for SavePost { @@ -29,11 +29,11 @@ impl Perform for SavePost { if data.save { PostSaved::save(context.pool(), &post_saved_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_save_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntSavePost)?; } else { PostSaved::unsave(context.pool(), &post_saved_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_save_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntSavePost)?; } let post_id = data.post_id; diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index 52bc3ce75..0915a0a8e 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ traits::Reportable, }; use lemmy_db_views::structs::{PostReportView, PostView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; /// Creates a post report and notifies the moderators of the community #[async_trait::async_trait(?Send)] @@ -46,7 +46,7 @@ impl Perform for CreatePostReport { let report = PostReport::report(context.pool(), &report_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; let post_report_view = PostReportView::read(context.pool(), report.id, person_id).await?; diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs index cc6d0510f..91243c098 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/post_report/resolve.rs @@ -7,7 +7,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable}; use lemmy_db_views::structs::PostReportView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; /// Resolves or unresolves a post report and notifies the moderators of the community #[async_trait::async_trait(?Send)] @@ -29,11 +29,11 @@ impl Perform for ResolvePostReport { if data.resolved { PostReport::resolve(context.pool(), report_id, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } else { PostReport::unresolve(context.pool(), report_id, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } let post_report_view = PostReportView::read(context.pool(), report_id, person_id).await?; diff --git a/crates/api/src/private_message/mark_read.rs b/crates/api/src/private_message/mark_read.rs index ca3431742..bb81a62c0 100644 --- a/crates/api/src/private_message/mark_read.rs +++ b/crates/api/src/private_message/mark_read.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::PrivateMessageView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for MarkPrivateMessageAsRead { @@ -28,7 +28,7 @@ impl Perform for MarkPrivateMessageAsRead { let private_message_id = data.private_message_id; let orig_private_message = PrivateMessage::read(context.pool(), private_message_id).await?; if local_user_view.person.id != orig_private_message.recipient_id { - return Err(LemmyError::from_message("couldnt_update_private_message")); + return Err(LemmyErrorType::CouldntUpdatePrivateMessage)?; } // Doing the update @@ -40,7 +40,7 @@ impl Perform for MarkPrivateMessageAsRead { &PrivateMessageUpdateForm::builder().read(Some(read)).build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; let view = PrivateMessageView::read(context.pool(), private_message_id).await?; Ok(PrivateMessageResponse { diff --git a/crates/api/src/private_message_report/create.rs b/crates/api/src/private_message_report/create.rs index 2a2feef34..d732b41af 100644 --- a/crates/api/src/private_message_report/create.rs +++ b/crates/api/src/private_message_report/create.rs @@ -14,7 +14,7 @@ use lemmy_db_schema::{ traits::{Crud, Reportable}, }; use lemmy_db_views::structs::PrivateMessageReportView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for CreatePrivateMessageReport { @@ -41,7 +41,7 @@ impl Perform for CreatePrivateMessageReport { let report = PrivateMessageReport::report(context.pool(), &report_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; let private_message_report_view = PrivateMessageReportView::read(context.pool(), report.id).await?; diff --git a/crates/api/src/private_message_report/resolve.rs b/crates/api/src/private_message_report/resolve.rs index e4fcfc856..964610536 100644 --- a/crates/api/src/private_message_report/resolve.rs +++ b/crates/api/src/private_message_report/resolve.rs @@ -7,7 +7,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable}; use lemmy_db_views::structs::PrivateMessageReportView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl Perform for ResolvePrivateMessageReport { @@ -24,11 +24,11 @@ impl Perform for ResolvePrivateMessageReport { if self.resolved { PrivateMessageReport::resolve(context.pool(), report_id, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } else { PrivateMessageReport::unresolve(context.pool(), report_id, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?; + .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } let private_message_report_view = diff --git a/crates/api/src/site/leave_admin.rs b/crates/api/src/site/leave_admin.rs index d7c0cf3aa..d23683800 100644 --- a/crates/api/src/site/leave_admin.rs +++ b/crates/api/src/site/leave_admin.rs @@ -17,7 +17,10 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{CustomEmojiView, SiteView}; use lemmy_db_views_actor::structs::PersonView; -use lemmy_utils::{error::LemmyError, version}; +use lemmy_utils::{ + error::{LemmyError, LemmyErrorType}, + version, +}; #[async_trait::async_trait(?Send)] impl Perform for LeaveAdmin { @@ -33,7 +36,7 @@ impl Perform for LeaveAdmin { // Make sure there isn't just one admin (so if one leaves, there will still be one left) let admins = PersonView::admins(context.pool()).await?; if admins.len() == 1 { - return Err(LemmyError::from_message("cannot_leave_admin")); + return Err(LemmyErrorType::CannotLeaveAdmin)?; } let person_id = local_user_view.person.id; diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index 64563bc0d..dc09ecaa7 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -2,7 +2,7 @@ use crate::post::SiteMetadata; use encoding::{all::encodings, DecoderTrap}; use lemmy_db_schema::newtypes::DbUrl; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorType}, settings::structs::Settings, version::VERSION, REQWEST_TIMEOUT, @@ -40,13 +40,11 @@ fn html_to_site_metadata(html_bytes: &[u8], url: &Url) -> Result") { - return Err(LemmyError::from_message( - "Site metadata page fetch is not DOCTYPE html", - )); + return Err(LemmyErrorType::SiteMetadataPageIsNotDoctypeHtml)?; } let mut page = HTML::from_string(html.to_string(), None)?; @@ -142,7 +140,7 @@ pub(crate) async fn fetch_pictrs( if response.msg == "ok" { Ok(response) } else { - Err(LemmyError::from_message(&response.msg)) + Err(LemmyErrorType::PictrsResponseError(response.msg))? } } @@ -161,15 +159,15 @@ pub async fn purge_image_from_pictrs( let alias = image_url .path_segments() - .ok_or_else(|| LemmyError::from_message("Image URL missing path segments"))? + .ok_or(LemmyErrorType::ImageUrlMissingPathSegments)? .next_back() - .ok_or_else(|| LemmyError::from_message("Image URL missing last path segment"))?; + .ok_or(LemmyErrorType::ImageUrlMissingLastPathSegment)?; let purge_url = format!("{}/internal/purge?alias={}", pictrs_config.url, alias); let pictrs_api_key = pictrs_config .api_key - .ok_or_else(|| LemmyError::from_message("pictrs_api_key_not_provided"))?; + .ok_or(LemmyErrorType::PictrsApiKeyNotProvided)?; let response = client .post(&purge_url) .timeout(REQWEST_TIMEOUT) @@ -182,7 +180,7 @@ pub async fn purge_image_from_pictrs( if response.msg == "ok" { Ok(()) } else { - Err(LemmyError::from_message(&response.msg)) + Err(LemmyErrorType::PictrsPurgeResponseError(response.msg))? } } @@ -252,13 +250,13 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Resu if response .headers() .get("Content-Type") - .ok_or_else(|| LemmyError::from_message("No Content-Type header"))? + .ok_or(LemmyErrorType::NoContentTypeHeader)? .to_str()? .starts_with("image/") { Ok(()) } else { - Err(LemmyError::from_message("Not an image type.")) + Err(LemmyErrorType::NotAnImageType)? } } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 2ef06f528..793cc70c3 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -36,7 +36,7 @@ use lemmy_db_views_actor::structs::{ use lemmy_utils::{ claims::Claims, email::{send_email, translations::Lang}, - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType}, location_info, rate_limit::RateLimitConfig, settings::structs::Settings, @@ -56,7 +56,7 @@ pub async fn is_mod_or_admin( ) -> Result<(), LemmyError> { let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person_id, community_id).await?; if !is_mod_or_admin { - return Err(LemmyError::from_message("not_a_mod_or_admin")); + return Err(LemmyErrorType::NotAModOrAdmin)?; } Ok(()) } @@ -74,13 +74,13 @@ pub async fn is_mod_or_admin_opt( is_admin(local_user_view) } } else { - Err(LemmyError::from_message("not_a_mod_or_admin")) + Err(LemmyErrorType::NotAModOrAdmin)? } } pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> { if !local_user_view.person.admin { - return Err(LemmyError::from_message("not_an_admin")); + return Err(LemmyErrorType::NotAnAdmin)?; } Ok(()) } @@ -95,7 +95,7 @@ pub fn is_top_mod( .map(|cm| cm.moderator.id) .unwrap_or(PersonId(0)) { - return Err(LemmyError::from_message("not_top_mod")); + return Err(LemmyErrorType::NotTopMod)?; } Ok(()) } @@ -104,7 +104,7 @@ pub fn is_top_mod( pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result { Post::read(pool, post_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post")) + .with_lemmy_type(LemmyErrorType::CouldntFindPost) } #[tracing::instrument(skip_all)] @@ -117,7 +117,7 @@ pub async fn mark_post_as_read( PostRead::mark_as_read(pool, &post_read_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_mark_post_as_read")) + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) } #[tracing::instrument(skip_all)] @@ -130,7 +130,7 @@ pub async fn mark_post_as_unread( PostRead::mark_as_unread(pool, &post_read_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_mark_post_as_read")) + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) } #[tracing::instrument(skip_all)] @@ -139,7 +139,7 @@ pub async fn local_user_view_from_jwt( context: &LemmyContext, ) -> Result { let claims = Claims::decode(jwt, &context.secret().jwt_secret) - .map_err(|e| e.with_message("not_logged_in"))? + .with_lemmy_type(LemmyErrorType::NotLoggedIn)? .claims; let local_user_id = LocalUserId(claims.sub); let local_user_view = LocalUserView::read(context.pool(), local_user_id).await?; @@ -169,7 +169,7 @@ pub fn check_validator_time( ) -> Result<(), LemmyError> { let user_validation_time = validator_time.timestamp(); if user_validation_time > claims.iat { - Err(LemmyError::from_message("not_logged_in")) + Err(LemmyErrorType::NotLoggedIn)? } else { Ok(()) } @@ -182,12 +182,12 @@ pub fn check_user_valid( ) -> Result<(), LemmyError> { // Check for a site ban if is_banned(banned, ban_expires) { - return Err(LemmyError::from_message("site_ban")); + return Err(LemmyErrorType::SiteBan)?; } // check for account deletion if deleted { - return Err(LemmyError::from_message("deleted")); + return Err(LemmyErrorType::Deleted)?; } Ok(()) @@ -203,7 +203,7 @@ pub async fn check_community_ban( .await .is_ok(); if is_banned { - Err(LemmyError::from_message("community_ban")) + Err(LemmyErrorType::BannedFromCommunity)? } else { Ok(()) } @@ -216,9 +216,9 @@ pub async fn check_community_deleted_or_removed( ) -> Result<(), LemmyError> { let community = Community::read(pool, community_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; if community.deleted || community.removed { - Err(LemmyError::from_message("deleted")) + Err(LemmyErrorType::Deleted)? } else { Ok(()) } @@ -226,7 +226,7 @@ pub async fn check_community_deleted_or_removed( pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> { if post.deleted || post.removed { - Err(LemmyError::from_message("deleted")) + Err(LemmyErrorType::Deleted)? } else { Ok(()) } @@ -242,7 +242,7 @@ pub async fn check_person_block( .await .is_ok(); if is_blocked { - Err(LemmyError::from_message("person_block")) + Err(LemmyErrorType::PersonIsBlocked)? } else { Ok(()) } @@ -251,7 +251,7 @@ pub async fn check_person_block( #[tracing::instrument(skip_all)] pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> { if score == -1 && !local_site.enable_downvotes { - return Err(LemmyError::from_message("downvotes_disabled")); + return Err(LemmyErrorType::DownvotesAreDisabled)?; } Ok(()) } @@ -262,7 +262,7 @@ pub fn check_private_instance( local_site: &LocalSite, ) -> Result<(), LemmyError> { if local_user_view.is_none() && local_site.private_instance { - return Err(LemmyError::from_message("instance_is_private")); + return Err(LemmyErrorType::InstanceIsPrivate)?; } Ok(()) } @@ -293,7 +293,7 @@ pub async fn build_federated_instances( /// Checks the password length pub fn password_length_check(pass: &str) -> Result<(), LemmyError> { if !(10..=60).contains(&pass.chars().count()) { - Err(LemmyError::from_message("invalid_password")) + Err(LemmyErrorType::InvalidPassword)? } else { Ok(()) } @@ -302,7 +302,7 @@ pub fn password_length_check(pass: &str) -> Result<(), LemmyError> { /// Checks for a honeypot. If this field is filled, fail the rest of the function pub fn honeypot_check(honeypot: &Option) -> Result<(), LemmyError> { if honeypot.is_some() && honeypot != &Some(String::new()) { - Err(LemmyError::from_message("honeypot_fail")) + Err(LemmyErrorType::HoneypotFailed)? } else { Ok(()) } @@ -509,10 +509,12 @@ pub async fn check_registration_application( let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?; if let Some(deny_reason) = registration.deny_reason { let lang = get_interface_language(local_user_view); - let registration_denied_message = format!("{}: {}", lang.registration_denied(), &deny_reason); - return Err(LemmyError::from_message(®istration_denied_message)); + let registration_denied_message = format!("{}: {}", lang.registration_denied(), deny_reason); + return Err(LemmyErrorType::RegistrationDenied( + registration_denied_message, + ))?; } else { - return Err(LemmyError::from_message("registration_application_pending")); + return Err(LemmyErrorType::RegistrationApplicationIsPending)?; } } Ok(()) @@ -522,9 +524,7 @@ pub fn check_private_instance_and_federation_enabled( local_site: &LocalSite, ) -> Result<(), LemmyError> { if local_site.private_instance && local_site.federation_enabled { - return Err(LemmyError::from_message( - "Cannot have both private instance and federation enabled.", - )); + return Err(LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether)?; } Ok(()) } @@ -712,12 +712,12 @@ pub async fn delete_user_account( // Comments Comment::permadelete_for_creator(pool, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; // Posts Post::permadelete_for_creator(pool, person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; // Purge image posts purge_image_posts_for_person(person_id, pool, settings, client).await?; diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 1bcc78483..c227e84da 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -26,13 +26,14 @@ use lemmy_db_schema::{ traits::{Crud, Likeable}, }; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{ mention::scrape_text_for_mentions, slurs::remove_slurs, validation::is_valid_body_field, }, }; + const MAX_COMMENT_DEPTH_LIMIT: usize = 100; #[async_trait::async_trait(?Send)] @@ -62,7 +63,7 @@ impl PerformCrud for CreateComment { // Check if post is locked, no new comments if post.locked { - return Err(LemmyError::from_message("locked")); + return Err(LemmyErrorType::Locked)?; } // Fetch the parent, if it exists @@ -76,7 +77,7 @@ impl PerformCrud for CreateComment { // Strange issue where sometimes the post ID of the parent comment is incorrect if let Some(parent) = parent_opt.as_ref() { if parent.post_id != post_id { - return Err(LemmyError::from_message("couldnt_create_comment")); + return Err(LemmyErrorType::CouldntCreateComment)?; } check_comment_depth(parent)?; } @@ -106,7 +107,7 @@ impl PerformCrud for CreateComment { let parent_path = parent_opt.clone().map(|t| t.path); let inserted_comment = Comment::create(context.pool(), &comment_form, parent_path.as_ref()) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreateComment)?; // Necessary to update the ap_id let inserted_comment_id = inserted_comment.id; @@ -123,7 +124,7 @@ impl PerformCrud for CreateComment { &CommentUpdateForm::builder().ap_id(Some(apub_id)).build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreateComment)?; // Scan the comment for user mentions, add those rows let mentions = scrape_text_for_mentions(&content_slurs_removed); @@ -147,7 +148,7 @@ impl PerformCrud for CreateComment { CommentLike::like(context.pool(), &like_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_like_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; // If its a reply, mark the parent as read if let Some(parent) = parent_opt { @@ -160,7 +161,7 @@ impl PerformCrud for CreateComment { &CommentReplyUpdateForm { read: Some(true) }, ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_replies"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateReplies)?; } // If the parent has PersonMentions mark them as read too @@ -174,7 +175,7 @@ impl PerformCrud for CreateComment { &PersonMentionUpdateForm { read: Some(true) }, ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_person_mentions"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePersonMentions)?; } } @@ -193,7 +194,7 @@ pub fn check_comment_depth(comment: &Comment) -> Result<(), LemmyError> { let path = &comment.path.0; let length = path.split('.').count(); if length > MAX_COMMENT_DEPTH_LIMIT { - Err(LemmyError::from_message("max_comment_depth_reached")) + Err(LemmyErrorType::MaxCommentDepthReached)? } else { Ok(()) } diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 864daded8..da2403bcf 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -14,7 +14,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::CommentView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for DeleteComment { @@ -30,7 +30,7 @@ impl PerformCrud for DeleteComment { // Dont delete it if its already been deleted. if orig_comment.comment.deleted == data.deleted { - return Err(LemmyError::from_message("couldnt_update_comment")); + return Err(LemmyErrorType::CouldntUpdateComment)?; } check_community_ban( @@ -42,7 +42,7 @@ impl PerformCrud for DeleteComment { // Verify that only the creator can delete if local_user_view.person.id != orig_comment.creator.id { - return Err(LemmyError::from_message("no_comment_edit_allowed")); + return Err(LemmyErrorType::NoCommentEditAllowed)?; } // Do the delete @@ -53,7 +53,7 @@ impl PerformCrud for DeleteComment { &CommentUpdateForm::builder().deleted(Some(deleted)).build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; let post_id = updated_comment.post_id; let post = Post::read(context.pool(), post_id).await?; diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index 6e4e55281..21cb3c8a6 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::CommentView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for RemoveComment { @@ -52,7 +52,7 @@ impl PerformCrud for RemoveComment { &CommentUpdateForm::builder().removed(Some(removed)).build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; // Mod tables let form = ModRemoveCommentForm { diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 3504e784d..7b37d9004 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::CommentView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{ mention::scrape_text_for_mentions, slurs::remove_slurs, @@ -47,7 +47,7 @@ impl PerformCrud for EditComment { // Verify that only the creator can edit if local_user_view.person.id != orig_comment.creator.id { - return Err(LemmyError::from_message("no_comment_edit_allowed")); + return Err(LemmyErrorType::NoCommentEditAllowed)?; } let language_id = self.language_id; @@ -74,7 +74,7 @@ impl PerformCrud for EditComment { .build(); let updated_comment = Comment::update(context.pool(), comment_id, &form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; // Do the mentions / recipients let updated_comment_content = updated_comment.content.clone(); diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 0e55beac9..5532e3150 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -33,7 +33,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::SiteView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{ slurs::{check_slurs, check_slurs_opt}, validation::{is_valid_actor_name, is_valid_body_field}, @@ -52,9 +52,7 @@ impl PerformCrud for CreateCommunity { let local_site = site_view.local_site; if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() { - return Err(LemmyError::from_message( - "only_admins_can_create_communities", - )); + return Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?; } // Check to make sure the icon and banners are urls @@ -77,7 +75,7 @@ impl PerformCrud for CreateCommunity { )?; let community_dupe = Community::read_from_apub_id(context.pool(), &community_actor_id).await?; if community_dupe.is_some() { - return Err(LemmyError::from_message("community_already_exists")); + return Err(LemmyErrorType::CommunityAlreadyExists)?; } // When you create a community, make sure the user becomes a moderator and a follower @@ -102,7 +100,7 @@ impl PerformCrud for CreateCommunity { let inserted_community = Community::create(context.pool(), &community_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityAlreadyExists)?; // The community creator becomes a moderator let community_moderator_form = CommunityModeratorForm { @@ -112,7 +110,7 @@ impl PerformCrud for CreateCommunity { CommunityModerator::join(context.pool(), &community_moderator_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?; // Follow your own community let community_follower_form = CommunityFollowerForm { @@ -123,7 +121,7 @@ impl PerformCrud for CreateCommunity { CommunityFollower::follow(context.pool(), &community_follower_form) .await - .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; // Update the discussion_languages if that's provided let community_id = inserted_community.id; @@ -133,7 +131,7 @@ impl PerformCrud for CreateCommunity { // https://stackoverflow.com/a/64227550 let is_subset = languages.iter().all(|item| site_languages.contains(item)); if !is_subset { - return Err(LemmyError::from_message("language_not_allowed")); + return Err(LemmyErrorType::LanguageNotAllowed)?; } CommunityLanguage::update(context.pool(), languages, community_id).await?; } diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs index 019e9f1da..97641f57e 100644 --- a/crates/api_crud/src/community/delete.rs +++ b/crates/api_crud/src/community/delete.rs @@ -11,7 +11,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views_actor::structs::CommunityModeratorView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for DeleteCommunity { @@ -41,7 +41,7 @@ impl PerformCrud for DeleteCommunity { .build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; build_community_response(context, local_user_view, community_id).await } diff --git a/crates/api_crud/src/community/remove.rs b/crates/api_crud/src/community/remove.rs index 52a503c45..fd011c6c2 100644 --- a/crates/api_crud/src/community/remove.rs +++ b/crates/api_crud/src/community/remove.rs @@ -13,7 +13,10 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::{error::LemmyError, utils::time::naive_from_unix}; +use lemmy_utils::{ + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + utils::time::naive_from_unix, +}; #[async_trait::async_trait(?Send)] impl PerformCrud for RemoveCommunity { @@ -38,7 +41,7 @@ impl PerformCrud for RemoveCommunity { .build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; // Mod tables let expires = data.expires.map(naive_from_unix); diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index dec62865f..9bef9388b 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -18,7 +18,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{slurs::check_slurs_opt, validation::is_valid_body_field}, }; @@ -47,7 +47,7 @@ impl PerformCrud for EditCommunity { .await .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?; if !mods.contains(&local_user_view.person.id) { - return Err(LemmyError::from_message("not_a_moderator")); + return Err(LemmyErrorType::NotAModerator)?; } let community_id = data.community_id; @@ -57,7 +57,7 @@ impl PerformCrud for EditCommunity { // https://stackoverflow.com/a/64227550 let is_subset = languages.iter().all(|item| site_languages.contains(item)); if !is_subset { - return Err(LemmyError::from_message("language_not_allowed")); + return Err(LemmyErrorType::LanguageNotAllowed)?; } CommunityLanguage::update(context.pool(), languages, community_id).await?; } @@ -75,7 +75,7 @@ impl PerformCrud for EditCommunity { let community_id = data.community_id; Community::update(context.pool(), community_id, &community_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; build_community_response(context, local_user_view, community_id).await } diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 4264c26d4..20f6cfd3b 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -28,7 +28,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views_actor::structs::CommunityView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, spawn_try_task, utils::{ slurs::{check_slurs, check_slurs_opt}, @@ -76,7 +76,7 @@ impl PerformCrud for CreatePost { ) .await?; if !is_mod { - return Err(LemmyError::from_message("only_mods_can_post_in_community")); + return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?; } } @@ -112,7 +112,7 @@ impl PerformCrud for CreatePost { let inserted_post = Post::create(context.pool(), &post_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; let inserted_post_id = inserted_post.id; let protocol_and_hostname = context.settings().get_protocol_and_hostname(); @@ -127,7 +127,7 @@ impl PerformCrud for CreatePost { &PostUpdateForm::builder().ap_id(Some(apub_id)).build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; // They like their own post by default let person_id = local_user_view.person.id; @@ -140,7 +140,7 @@ impl PerformCrud for CreatePost { PostLike::like(context.pool(), &like_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_like_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; // Mark the post as read mark_post_as_read(person_id, post_id, context.pool()).await?; @@ -157,10 +157,7 @@ impl PerformCrud for CreatePost { { Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()), Ok(_) => Ok(()), - Err(e) => Err(LemmyError::from_error_message( - e, - "Couldn't send webmention", - )), + Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention), } }; if *SYNCHRONOUS_FEDERATION { diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index 6e6a4c076..d11245949 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ source::post::{Post, PostUpdateForm}, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for DeletePost { @@ -26,7 +26,7 @@ impl PerformCrud for DeletePost { // Dont delete it if its already been deleted. if orig_post.deleted == data.deleted { - return Err(LemmyError::from_message("couldnt_update_post")); + return Err(LemmyErrorType::CouldntUpdatePost)?; } check_community_ban( @@ -39,7 +39,7 @@ impl PerformCrud for DeletePost { // Verify that only the creator can delete if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { - return Err(LemmyError::from_message("no_post_edit_allowed")); + return Err(LemmyErrorType::NoPostEditAllowed)?; } // Update the post diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index d1851f33e..519b748cc 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::{post_view::PostQuery, structs::PostView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for GetPost { @@ -39,10 +39,10 @@ impl PerformCrud for GetPost { } else if let Some(comment_id) = data.comment_id { Comment::read(context.pool(), comment_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))? + .with_lemmy_type(LemmyErrorType::CouldntFindPost)? .post_id } else { - Err(LemmyError::from_message("couldnt_find_post"))? + Err(LemmyErrorType::CouldntFindPost)? }; // Check to see if the person is a mod or admin, to show deleted / removed @@ -54,7 +54,7 @@ impl PerformCrud for GetPost { let post_view = PostView::read(context.pool(), post_id, person_id, Some(is_mod_or_admin)) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindPost)?; // Mark the post as read let post_id = post_view.post.id; @@ -70,7 +70,7 @@ impl PerformCrud for GetPost { Some(is_mod_or_admin), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; // Insert into PersonPostAggregates // to update the read_comments count @@ -84,7 +84,7 @@ impl PerformCrud for GetPost { }; PersonPostAggregates::upsert(context.pool(), &person_post_agg_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindPost)?; } let moderators = CommunityModeratorView::for_community(context.pool(), community_id).await?; diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index a8c5715bb..936610966 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ utils::{diesel_option_overwrite, naive_now}, }; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{ slurs::check_slurs_opt, validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, @@ -64,7 +64,7 @@ impl PerformCrud for EditPost { // Verify that only the creator can edit if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { - return Err(LemmyError::from_message("no_post_edit_allowed")); + return Err(LemmyErrorType::NoPostEditAllowed)?; } // Fetch post links and Pictrs cached image @@ -99,7 +99,7 @@ impl PerformCrud for EditPost { let post_id = data.post_id; Post::update(context.pool(), post_id, &post_form) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_post"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; build_post_response( context, diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 187b3f90a..d399ffb76 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -22,7 +22,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{slurs::remove_slurs, validation::is_valid_body_field}, }; @@ -53,16 +53,9 @@ impl PerformCrud for CreatePrivateMessage { .recipient_id(data.recipient_id) .build(); - let inserted_private_message = - match PrivateMessage::create(context.pool(), &private_message_form).await { - Ok(private_message) => private_message, - Err(e) => { - return Err(LemmyError::from_error_message( - e, - "couldnt_create_private_message", - )); - } - }; + let inserted_private_message = PrivateMessage::create(context.pool(), &private_message_form) + .await + .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; let inserted_private_message_id = inserted_private_message.id; let protocol_and_hostname = context.settings().get_protocol_and_hostname(); @@ -79,7 +72,7 @@ impl PerformCrud for CreatePrivateMessage { .build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_private_message"))?; + .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; let view = PrivateMessageView::read(context.pool(), inserted_private_message.id).await?; diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs index b8e3c3b11..be9ed695f 100644 --- a/crates/api_crud/src/private_message/delete.rs +++ b/crates/api_crud/src/private_message/delete.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::PrivateMessageView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for DeletePrivateMessage { @@ -28,7 +28,7 @@ impl PerformCrud for DeletePrivateMessage { let private_message_id = data.private_message_id; let orig_private_message = PrivateMessage::read(context.pool(), private_message_id).await?; if local_user_view.person.id != orig_private_message.creator_id { - return Err(LemmyError::from_message("no_private_message_edit_allowed")); + return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?; } // Doing the update @@ -42,7 +42,7 @@ impl PerformCrud for DeletePrivateMessage { .build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; let view = PrivateMessageView::read(context.pool(), private_message_id).await?; Ok(PrivateMessageResponse { diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index b2d8e48f9..e20a7115e 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::PrivateMessageView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{slurs::remove_slurs, validation::is_valid_body_field}, }; @@ -36,7 +36,7 @@ impl PerformCrud for EditPrivateMessage { let private_message_id = data.private_message_id; let orig_private_message = PrivateMessage::read(context.pool(), private_message_id).await?; if local_user_view.person.id != orig_private_message.creator_id { - return Err(LemmyError::from_message("no_private_message_edit_allowed")); + return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?; } // Doing the update @@ -53,7 +53,7 @@ impl PerformCrud for EditPrivateMessage { .build(), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?; + .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; let view = PrivateMessageView::read(context.pool(), private_message_id).await?; diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 838d5bc40..be8411e24 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::SiteView; use lemmy_utils::{ - error::{LemmyError, LemmyResult}, + error::{LemmyError, LemmyErrorType, LemmyResult}, utils::{ slurs::{check_slurs, check_slurs_opt}, validation::{ @@ -140,7 +140,7 @@ impl PerformCrud for CreateSite { fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> LemmyResult<()> { // Make sure the site hasn't already been set up... if local_site.site_setup { - return Err(LemmyError::from_message("site_already_exists")); + return Err(LemmyErrorType::SiteAlreadyExists)?; }; // Check that the slur regex compiles, and returns the regex if valid... @@ -186,13 +186,14 @@ mod tests { use crate::site::create::validate_create_payload; use lemmy_api_common::site::CreateSite; use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode}; + use lemmy_utils::error::LemmyErrorType; #[test] fn test_validate_invalid_create_payload() { let invalid_payloads = [ ( "CreateSite attempted on set up LocalSite", - "site_already_exists", + LemmyErrorType::SiteAlreadyExists, &generate_local_site( true, None::, @@ -215,7 +216,7 @@ mod tests { ), ( "CreateSite name matches LocalSite slur filter", - "slurs", + LemmyErrorType::Slurs, &generate_local_site( false, Some(String::from("(foo|bar)")), @@ -238,7 +239,7 @@ mod tests { ), ( "CreateSite name matches new slur filter", - "slurs", + LemmyErrorType::Slurs, &generate_local_site( false, Some(String::from("(foo|bar)")), @@ -261,7 +262,7 @@ mod tests { ), ( "CreateSite listing type is Subscribed, which is invalid", - "invalid_default_post_listing_type", + LemmyErrorType::InvalidDefaultPostListingType, &generate_local_site( false, None::, @@ -284,7 +285,7 @@ mod tests { ), ( "CreateSite is both private and federated", - "cant_enable_private_instance_and_federation_together", + LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, &generate_local_site( false, None::, @@ -307,7 +308,7 @@ mod tests { ), ( "LocalSite is private, but CreateSite also makes it federated", - "cant_enable_private_instance_and_federation_together", + LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, &generate_local_site( false, None::, @@ -330,7 +331,7 @@ mod tests { ), ( "CreateSite requires application, but neither it nor LocalSite has an application question", - "application_question_required", + LemmyErrorType::ApplicationQuestionRequired, &generate_local_site( false, None::, @@ -356,7 +357,7 @@ mod tests { invalid_payloads.iter().enumerate().for_each( |( idx, - &(reason, expected_err, local_site, create_site), + &(reason, ref expected_err, local_site, create_site), )| { match validate_create_payload( local_site, @@ -370,9 +371,9 @@ mod tests { } Err(error) => { assert!( - error.message.eq(&Some(String::from(expected_err))), + error.error_type.eq(&Some(expected_err.clone())), "Got Err {:?}, but should have failed with message: {} for reason: {}. invalid_payloads.nth({})", - error.message, + error.error_type, expected_err, reason, idx diff --git a/crates/api_crud/src/site/mod.rs b/crates/api_crud/src/site/mod.rs index a98f2057c..d7ae94aca 100644 --- a/crates/api_crud/src/site/mod.rs +++ b/crates/api_crud/src/site/mod.rs @@ -1,5 +1,5 @@ use lemmy_db_schema::{ListingType, RegistrationMode}; -use lemmy_utils::error::{LemmyError, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; mod create; mod read; @@ -12,9 +12,7 @@ pub fn site_default_post_listing_type_check( if let Some(listing_type) = default_post_listing_type { // Only allow all or local as default listing types... if listing_type != &ListingType::All && listing_type != &ListingType::Local { - Err(LemmyError::from_message( - "invalid_default_post_listing_type", - )) + Err(LemmyErrorType::InvalidDefaultPostListingType)? } else { Ok(()) } @@ -36,7 +34,7 @@ pub fn application_question_check( if registration_mode == RegistrationMode::RequireApplication && (has_no_question || is_nullifying_question) { - Err(LemmyError::from_message("application_question_required")) + Err(LemmyErrorType::ApplicationQuestionRequired)? } else { Ok(()) } diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index f6c663ac0..4368da99d 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -22,7 +22,11 @@ use lemmy_db_views_actor::structs::{ PersonBlockView, PersonView, }; -use lemmy_utils::{claims::Claims, error::LemmyError, version}; +use lemmy_utils::{ + claims::Claims, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + version, +}; #[async_trait::async_trait(?Send)] impl PerformCrud for GetSite { @@ -45,25 +49,25 @@ impl PerformCrud for GetSite { let follows = CommunityFollowerView::for_person(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?; + .with_lemmy_type(LemmyErrorType::SystemErrLogin)?; let person_id = local_user_view.person.id; let community_blocks = CommunityBlockView::for_person(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?; + .with_lemmy_type(LemmyErrorType::SystemErrLogin)?; let person_id = local_user_view.person.id; let person_blocks = PersonBlockView::for_person(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?; + .with_lemmy_type(LemmyErrorType::SystemErrLogin)?; let moderates = CommunityModeratorView::for_person(context.pool(), person_id) .await - .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?; + .with_lemmy_type(LemmyErrorType::SystemErrLogin)?; let discussion_languages = LocalUserLanguage::read(context.pool(), local_user_id) .await - .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?; + .with_lemmy_type(LemmyErrorType::SystemErrLogin)?; Some(MyUserInfo { local_user_view, diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index c9f97e8df..1e354d9cd 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::SiteView; use lemmy_utils::{ - error::{LemmyError, LemmyResult}, + error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ slurs::check_slurs_opt, validation::{ @@ -139,7 +139,7 @@ impl PerformCrud for EditSite { if !old_require_application && new_require_application { LocalUser::set_all_users_registration_applications_accepted(context.pool()) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_set_all_registrations_accepted"))?; + .with_lemmy_type(LemmyErrorType::CouldntSetAllRegistrationsAccepted)?; } let new_require_email_verification = update_local_site @@ -149,7 +149,7 @@ impl PerformCrud for EditSite { if !local_site.require_email_verification && new_require_email_verification { LocalUser::set_all_users_email_verified(context.pool()) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_set_all_email_verified"))?; + .with_lemmy_type(LemmyErrorType::CouldntSetAllEmailVerified)?; } let new_taglines = data.taglines.clone(); @@ -220,13 +220,14 @@ mod tests { use crate::site::update::validate_update_payload; use lemmy_api_common::site::EditSite; use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode}; + use lemmy_utils::error::LemmyErrorType; #[test] fn test_validate_invalid_update_payload() { let invalid_payloads = [ ( "EditSite name matches LocalSite slur filter", - "slurs", + LemmyErrorType::Slurs, &generate_local_site( Some(String::from("(foo|bar)")), true, @@ -248,7 +249,7 @@ mod tests { ), ( "EditSite name matches new slur filter", - "slurs", + LemmyErrorType::Slurs, &generate_local_site( Some(String::from("(foo|bar)")), true, @@ -270,7 +271,7 @@ mod tests { ), ( "EditSite listing type is Subscribed, which is invalid", - "invalid_default_post_listing_type", + LemmyErrorType::InvalidDefaultPostListingType, &generate_local_site( None::, true, @@ -292,7 +293,7 @@ mod tests { ), ( "EditSite is both private and federated", - "cant_enable_private_instance_and_federation_together", + LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, &generate_local_site( None::, true, @@ -314,7 +315,7 @@ mod tests { ), ( "LocalSite is private, but EditSite also makes it federated", - "cant_enable_private_instance_and_federation_together", + LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, &generate_local_site( None::, true, @@ -336,7 +337,7 @@ mod tests { ), ( "EditSite requires application, but neither it nor LocalSite has an application question", - "application_question_required", + LemmyErrorType::ApplicationQuestionRequired, &generate_local_site( None::, true, @@ -361,7 +362,7 @@ mod tests { invalid_payloads.iter().enumerate().for_each( |( idx, - &(reason, expected_err, local_site, edit_site), + &(reason, ref expected_err, local_site, edit_site), )| { match validate_update_payload(local_site, edit_site) { Ok(_) => { @@ -372,9 +373,9 @@ mod tests { } Err(error) => { assert!( - error.message.eq(&Some(String::from(expected_err))), + error.error_type.eq(&Some(expected_err.clone())), "Got Err {:?}, but should have failed with message: {} for reason: {}. invalid_payloads.nth({})", - error.message, + error.error_type, expected_err, reason, idx diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 302e2f98e..2bfd48ef0 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -30,7 +30,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ claims::Claims, - error::LemmyError, + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{ slurs::{check_slurs, check_slurs_opt}, validation::is_valid_actor_name, @@ -51,25 +51,23 @@ impl PerformCrud for Register { local_site.registration_mode == RegistrationMode::RequireApplication; if local_site.registration_mode == RegistrationMode::Closed { - return Err(LemmyError::from_message("registration_closed")); + return Err(LemmyErrorType::RegistrationClosed)?; } password_length_check(&data.password)?; honeypot_check(&data.honeypot)?; if local_site.require_email_verification && data.email.is_none() { - return Err(LemmyError::from_message("email_required")); + return Err(LemmyErrorType::EmailRequired)?; } if local_site.site_setup && require_registration_application && data.answer.is_none() { - return Err(LemmyError::from_message( - "registration_application_answer_required", - )); + return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?; } // Make sure passwords match if data.password != data.password_verify { - return Err(LemmyError::from_message("passwords_dont_match")); + return Err(LemmyErrorType::PasswordsDoNotMatch)?; } if local_site.site_setup && local_site.captcha_enabled { @@ -84,10 +82,10 @@ impl PerformCrud for Register { ) .await?; if !check { - return Err(LemmyError::from_message("captcha_incorrect")); + return Err(LemmyErrorType::CaptchaIncorrect)?; } } else { - return Err(LemmyError::from_message("captcha_incorrect")); + return Err(LemmyErrorType::CaptchaIncorrect)?; } } @@ -105,7 +103,7 @@ impl PerformCrud for Register { if let Some(email) = &data.email { if LocalUser::is_email_taken(context.pool(), email).await? { - return Err(LemmyError::from_message("email_already_exists")); + return Err(LemmyErrorType::EmailAlreadyExists)?; } } @@ -127,7 +125,7 @@ impl PerformCrud for Register { // insert the person let inserted_person = Person::create(context.pool(), &person_form) .await - .map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?; + .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; // Automatically set their application as accepted, if they created this with open registration. // Also fixes a bug which allows users to log in when registrations are changed to closed. diff --git a/crates/api_crud/src/user/delete.rs b/crates/api_crud/src/user/delete.rs index 7ce0312cf..5a8b4d036 100644 --- a/crates/api_crud/src/user/delete.rs +++ b/crates/api_crud/src/user/delete.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ person::{DeleteAccount, DeleteAccountResponse}, utils::local_user_view_from_jwt, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; #[async_trait::async_trait(?Send)] impl PerformCrud for DeleteAccount { @@ -24,7 +24,7 @@ impl PerformCrud for DeleteAccount { ) .unwrap_or(false); if !valid { - return Err(LemmyError::from_message("password_incorrect")); + return Err(LemmyErrorType::IncorrectLogin)?; } Ok(DeleteAccountResponse {}) diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 116b02726..e33e9fbf4 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -21,7 +21,7 @@ use activitypub_federation::{ traits::{ActivityHandler, Actor}, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use serde_json::Value; use url::Url; @@ -48,7 +48,7 @@ impl ActivityHandler for RawAnnouncableActivities { let activity: AnnouncableActivities = self.clone().try_into()?; // This is only for sending, not receiving so we reject it. if let AnnouncableActivities::Page(_) = activity { - return Err(LemmyError::from_message("Cant receive page")); + return Err(LemmyErrorType::CannotReceivePage)?; } let community = activity.community(data).await?; let actor_id = activity.actor().clone().into(); @@ -144,7 +144,7 @@ impl ActivityHandler for AnnounceActivity { let object: AnnouncableActivities = self.object.object(context).await?.try_into()?; // This is only for sending, not receiving so we reject it. if let AnnouncableActivities::Page(_) = object { - return Err(LemmyError::from_message("Cant receive page")); + return Err(LemmyErrorType::CannotReceivePage)?; } // verify here in order to avoid fetching the object twice over http diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index 916111fc5..7d5ce3365 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -36,7 +36,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Likeable}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use url::Url; #[async_trait::async_trait] @@ -159,7 +159,7 @@ impl ActivityHandler for CreateOrUpdatePage { // because then we will definitely receive all create and update activities separately. let is_locked = self.object.comments_enabled == Some(false); if community.local && is_locked { - return Err(LemmyError::from_message("New post cannot be locked")); + return Err(LemmyErrorType::NewPostCannotBeLocked)?; } } CreateOrUpdateType::Update => { diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index dfa709c36..d08330928 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use url::Url; #[async_trait::async_trait] @@ -108,9 +108,7 @@ pub(in crate::activities) async fn receive_remove_action( match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { - return Err(LemmyError::from_message( - "Only local admin can remove community", - )); + return Err(LemmyErrorType::OnlyLocalAdminCanRemoveCommunity)?; } let form = ModRemoveCommunityForm { mod_person_id: actor.id, diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 00768580d..e5c514811 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use url::Url; #[async_trait::async_trait] @@ -100,9 +100,7 @@ impl UndoDelete { match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { - return Err(LemmyError::from_message( - "Only local admin can restore community", - )); + return Err(LemmyErrorType::OnlyLocalAdminCanRestoreCommunity)?; } let form = ModRemoveCommunityForm { mod_person_id: actor.id, diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 4b50c5c83..29d15701d 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -15,7 +15,7 @@ use anyhow::anyhow; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{newtypes::CommunityId, source::community::Community}; use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use serde::Serialize; use std::ops::Deref; use tracing::info; @@ -39,8 +39,8 @@ async fn verify_person( ) -> Result<(), LemmyError> { let person = person_id.dereference(context).await?; if person.banned { - let err = anyhow!("Person {} is banned", person_id); - return Err(LemmyError::from_error_message(err, "banned")); + return Err(anyhow!("Person {} is banned", person_id)) + .with_lemmy_type(LemmyErrorType::CouldntUpdateComment); } Ok(()) } @@ -55,7 +55,7 @@ pub(crate) async fn verify_person_in_community( ) -> Result<(), LemmyError> { let person = person_id.dereference(context).await?; if person.banned { - return Err(LemmyError::from_message("Person is banned from site")); + return Err(LemmyErrorType::PersonIsBannedFromSite)?; } let person_id = person.id; let community_id = community.id; @@ -63,7 +63,7 @@ pub(crate) async fn verify_person_in_community( .await .is_ok(); if is_banned { - return Err(LemmyError::from_message("Person is banned from community")); + return Err(LemmyErrorType::PersonIsBannedFromCommunity)?; } Ok(()) @@ -96,12 +96,12 @@ pub(crate) async fn verify_mod_action( return Ok(()); } - Err(LemmyError::from_message("Not a mod")) + Err(LemmyErrorType::NotAModerator)? } pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> { if ![to, cc].iter().any(|set| set.contains(&public())) { - return Err(LemmyError::from_message("Object is not public")); + return Err(LemmyErrorType::ObjectIsNotPublic)?; } Ok(()) } @@ -115,16 +115,14 @@ where { let b: ObjectId = b.into(); if a != &b { - return Err(LemmyError::from_message("Invalid community")); + return Err(LemmyErrorType::InvalidCommunity)?; } Ok(()) } pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Result<(), LemmyError> { if community.deleted || community.removed { - Err(LemmyError::from_message( - "New post or comment cannot be created in deleted or removed community", - )) + Err(LemmyErrorType::CannotCreatePostOrCommentInDeletedOrRemovedCommunity)? } else { Ok(()) } diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index edb70dbaa..531a6eddb 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::comment_view::CommentQuery; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn list_comments( @@ -66,7 +66,7 @@ pub async fn list_comments( .build() .list() .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?; + .with_lemmy_type(LemmyErrorType::CouldntGetComments)?; Ok(Json(GetCommentsResponse { comments })) } diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index ff7ac1089..929cb95c3 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -12,7 +12,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::{community::Community, local_site::LocalSite}; use lemmy_db_views::post_view::PostQuery; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn list_posts( @@ -55,7 +55,7 @@ pub async fn list_posts( .build() .list() .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?; + .with_lemmy_type(LemmyErrorType::CouldntGetPosts)?; Ok(Json(GetPostsResponse { posts })) } diff --git a/crates/apub/src/api/read_community.rs b/crates/apub/src/api/read_community.rs index e524694d3..5c8e8cac3 100644 --- a/crates/apub/src/api/read_community.rs +++ b/crates/apub/src/api/read_community.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::source::{ site::Site, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn read_community( @@ -24,7 +24,7 @@ pub async fn read_community( let local_site = LocalSite::read(context.pool()).await?; if data.name.is_none() && data.id.is_none() { - return Err(LemmyError::from_message("no_id_given")); + return Err(LemmyErrorType::NoIdGiven)?; } check_private_instance(&local_user_view, &local_site)?; @@ -37,7 +37,7 @@ pub async fn read_community( let name = data.name.clone().unwrap_or_else(|| "main".to_string()); resolve_actor_identifier::(&name, &context, &local_user_view, true) .await - .map_err(|e| e.with_message("couldnt_find_community"))? + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)? .id } }; @@ -54,11 +54,11 @@ pub async fn read_community( Some(is_mod_or_admin), ) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; let moderators = CommunityModeratorView::for_community(context.pool(), community_id) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; let site_id = Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into()); let mut site = Site::read_from_apub_id(context.pool(), &site_id.into()).await?; diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index fb4755e38..35fd59cfe 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -12,7 +12,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery}; use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn read_person( @@ -21,7 +21,7 @@ pub async fn read_person( ) -> Result, LemmyError> { // Check to make sure a person name or an id is given if data.username.is_none() && data.person_id.is_none() { - return Err(LemmyError::from_message("no_id_given")); + return Err(LemmyErrorType::NoIdGiven)?; } let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; @@ -36,12 +36,10 @@ pub async fn read_person( if let Some(username) = &data.username { resolve_actor_identifier::(username, &context, &local_user_view, true) .await - .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))? + .with_lemmy_type(LemmyErrorType::CouldntFindPerson)? .id } else { - return Err(LemmyError::from_message( - "couldnt_find_that_username_or_email", - )); + return Err(LemmyErrorType::CouldntFindPerson)?; } } }; diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index 09689def1..30f381d76 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -10,7 +10,7 @@ use lemmy_api_common::{ use lemmy_db_schema::{newtypes::PersonId, source::local_site::LocalSite, utils::DbPool}; use lemmy_db_views::structs::{CommentView, PostView}; use lemmy_db_views_actor::structs::{CommunityView, PersonView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn resolve_object( @@ -24,10 +24,10 @@ pub async fn resolve_object( let res = search_query_to_object_id(&data.q, &context) .await - .map_err(|e| e.with_message("couldnt_find_object"))?; + .with_lemmy_type(LemmyErrorType::CouldntFindObject)?; convert_response(res, person_id, context.pool()) .await - .map_err(|e| e.with_message("couldnt_find_object")) + .with_lemmy_type(LemmyErrorType::CouldntFindObject) } async fn convert_response( diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 41bcce375..39ecbc1be 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -9,7 +9,7 @@ use activitypub_federation::{ }; use chrono::NaiveDateTime; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use serde::Deserialize; use url::Url; @@ -38,7 +38,7 @@ pub(crate) async fn search_query_to_object_id( Some('!') => SearchableObjects::Community( webfinger_resolve_actor::(identifier, context).await?, ), - _ => return Err(LemmyError::from_message("invalid query")), + _ => return Err(LemmyErrorType::InvalidQuery)?, } } }) diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 04ac8e3fd..19793674c 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -18,7 +18,7 @@ use activitypub_federation::{ use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::ApubActor}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use serde::Deserialize; #[derive(Deserialize)] @@ -80,7 +80,7 @@ pub(crate) async fn get_apub_community_outbox( .await? .into(); if community.deleted || community.removed { - return Err(LemmyError::from_message("deleted")); + return Err(LemmyErrorType::Deleted)?; } let outbox = ApubCommunityOutbox::read_local(&community, &context).await?; create_apub_response(&outbox) @@ -96,7 +96,7 @@ pub(crate) async fn get_apub_community_moderators( .await? .into(); if community.deleted || community.removed { - return Err(LemmyError::from_message("deleted")); + return Err(LemmyErrorType::Deleted)?; } let moderators = ApubCommunityModerators::read_local(&community, &context).await?; create_apub_response(&moderators) @@ -112,7 +112,7 @@ pub(crate) async fn get_apub_community_featured( .await? .into(); if community.deleted || community.removed { - return Err(LemmyError::from_message("deleted")); + return Err(LemmyErrorType::Deleted)?; } let featured = ApubCommunityFeatured::read_local(&community, &context).await?; create_apub_response(&featured) diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 66d5a0f9f..fec287e19 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -14,7 +14,7 @@ use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; use http::StatusCode; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::activity::Activity; -use lemmy_utils::error::{LemmyError, LemmyResult}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use std::ops::Deref; use url::Url; @@ -65,7 +65,7 @@ fn create_apub_tombstone_response>(id: T) -> LemmyResult LemmyError { - LemmyError::from_message("Object not local, fetch it from original instance") + LemmyErrorType::ObjectNotLocal.into() } #[derive(Deserialize)] diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 1c36f9852..f7ef22eec 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -12,7 +12,7 @@ use lemmy_db_schema::{ traits::Crud, utils::DbPool, }; -use lemmy_utils::error::{LemmyError, LemmyResult}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use moka::future::Cache; use once_cell::sync::Lazy; use serde::Serialize; @@ -145,7 +145,12 @@ pub(crate) async fn check_apub_id_valid_with_strictness( } let local_site_data = local_site_data_cached(context.pool()).await?; - check_apub_id_valid(apub_id, &local_site_data).map_err(LemmyError::from_message)?; + check_apub_id_valid(apub_id, &local_site_data).map_err(|err| match err { + "Federation disabled" => LemmyErrorType::FederationDisabled, + "Domain is blocked" => LemmyErrorType::DomainBlocked, + "Domain is not in allowlist" => LemmyErrorType::DomainNotInAllowList, + _ => panic!("Could not handle apub error!"), + })?; // Only check allowlist if this is a community, and there are instances in the allowlist if is_strict && !local_site_data.allowed_instances.is_empty() { @@ -164,9 +169,7 @@ pub(crate) async fn check_apub_id_valid_with_strictness( let domain = apub_id.domain().expect("apud id has domain").to_string(); if !allowed_and_local.contains(&domain) { - return Err(LemmyError::from_message( - "Federation forbidden by strict allowlist", - )); + return Err(LemmyErrorType::FederationDisabledByStrictAllowList)?; } } Ok(()) diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 46898effe..da844ecc5 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -28,7 +28,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorType}, utils::{markdown::markdown_to_html, slurs::remove_slurs, time::convert_datetime}, }; use std::ops::Deref; @@ -137,7 +137,7 @@ impl Object for ApubComment { verify_person_in_community(¬e.attributed_to, &community, context).await?; let (post, _) = note.get_parents(context).await?; if post.locked { - return Err(LemmyError::from_message("Post is locked")); + return Err(LemmyErrorType::PostIsLocked)?; } Ok(()) } diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 75c9e460b..e2571bd93 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -21,7 +21,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorType}, utils::{markdown::markdown_to_html, time::convert_datetime}, }; use std::ops::Deref; @@ -104,7 +104,7 @@ impl Object for ApubPrivateMessage { check_apub_id_valid_with_strictness(note.id.inner(), false, context).await?; let person = note.attributed_to.dereference(context).await?; if person.banned { - return Err(LemmyError::from_message("Person is banned from site")); + return Err(LemmyErrorType::PersonIsBannedFromSite)?; } Ok(()) } diff --git a/crates/apub/src/protocol/activities/voting/vote.rs b/crates/apub/src/protocol/activities/voting/vote.rs index 4450fed8b..0c199c729 100644 --- a/crates/apub/src/protocol/activities/voting/vote.rs +++ b/crates/apub/src/protocol/activities/voting/vote.rs @@ -6,7 +6,7 @@ use crate::{ }; use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use strum_macros::Display; @@ -36,7 +36,7 @@ impl TryFrom for VoteType { match value { 1 => Ok(VoteType::Like), -1 => Ok(VoteType::Dislike), - _ => Err(LemmyError::from_message("invalid vote value")), + _ => Err(LemmyErrorType::InvalidVoteValue.into()), } } } diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index ad89cf35b..c48cabfa7 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -21,7 +21,7 @@ use chrono::{DateTime, FixedOffset}; use itertools::Itertools; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::newtypes::DbUrl; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -161,7 +161,7 @@ impl Page { .iter() .find(|a| a.kind == PersonOrGroupType::Person) .map(|a| ObjectId::::from(a.id.clone().into_inner())) - .ok_or_else(|| LemmyError::from_message("page does not specify creator person")), + .ok_or_else(|| LemmyErrorType::PageDoesNotSpecifyCreator.into()), } } } @@ -208,7 +208,7 @@ impl InCommunity for Page { break c; } } else { - return Err(LemmyError::from_message("No community found in cc")); + return Err(LemmyErrorType::NoCommunityFoundInCc)?; } } } @@ -216,7 +216,7 @@ impl InCommunity for Page { p.iter() .find(|a| a.kind == PersonOrGroupType::Group) .map(|a| ObjectId::::from(a.id.clone().into_inner())) - .ok_or_else(|| LemmyError::from_message("page does not specify group"))? + .ok_or(LemmyErrorType::PageDoesNotSpecifyGroup)? .dereference(context) .await? } diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index dbb9c70fe..403ef7170 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -30,7 +30,7 @@ use diesel_async::{ AsyncPgConnection, RunQueryDsl, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use tokio::sync::OnceCell; pub const UNDETERMINED_ID: LanguageId = LanguageId(0); @@ -217,7 +217,7 @@ impl CommunityLanguage { if is_allowed { Ok(()) } else { - Err(LemmyError::from_message("language_not_allowed")) + Err(LemmyErrorType::LanguageNotAllowed)? } } else { Ok(()) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 1dc2f9afa..267540554 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -26,7 +26,10 @@ use diesel_async::{ }; use diesel_migrations::EmbeddedMigrations; use futures_util::{future::BoxFuture, FutureExt}; -use lemmy_utils::{error::LemmyError, settings::structs::Settings}; +use lemmy_utils::{ + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + settings::structs::Settings, +}; use once_cell::sync::Lazy; use regex::Regex; use rustls::{ @@ -118,13 +121,12 @@ pub fn diesel_option_overwrite(opt: &Option) -> Option> { pub fn diesel_option_overwrite_to_url( opt: &Option, ) -> Result>, LemmyError> { - match opt.as_ref().map(std::string::String::as_str) { + match opt.as_ref().map(String::as_str) { // An empty string is an erase Some("") => Ok(Some(None)), - Some(str_url) => match Url::parse(str_url) { - Ok(url) => Ok(Some(Some(url.into()))), - Err(e) => Err(LemmyError::from_error_message(e, "invalid_url")), - }, + Some(str_url) => Url::parse(str_url) + .map(|u| Some(Some(u.into()))) + .with_lemmy_type(LemmyErrorType::InvalidUrl), None => Ok(None), } } @@ -132,13 +134,12 @@ pub fn diesel_option_overwrite_to_url( pub fn diesel_option_overwrite_to_url_create( opt: &Option, ) -> Result, LemmyError> { - match opt.as_ref().map(std::string::String::as_str) { + match opt.as_ref().map(String::as_str) { // An empty string is nothing Some("") => Ok(None), - Some(str_url) => match Url::parse(str_url) { - Ok(url) => Ok(Some(url.into())), - Err(e) => Err(LemmyError::from_error_message(e, "invalid_url")), - }, + Some(str_url) => Url::parse(str_url) + .map(|u| Some(u.into())) + .with_lemmy_type(LemmyErrorType::InvalidUrl), None => Ok(None), } } diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 14709e724..b97ce8bdc 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -13,6 +13,9 @@ name = "lemmy_utils" path = "src/lib.rs" doctest = false +[features] +full = ["ts-rs"] + [dependencies] regex = { workspace = true } chrono = { workspace = true } @@ -45,6 +48,7 @@ jsonwebtoken = "8.3.0" lettre = { version = "0.10.4", features = ["tokio1", "tokio1-native-tls"] } markdown-it = "0.5.1" totp-rs = { version = "5.0.2", features = ["gen_secret", "otpauth"] } +ts-rs = { workspace = true, optional = true } enum-map = "2.6" [dev-dependencies] diff --git a/crates/utils/src/email.rs b/crates/utils/src/email.rs index fba624666..3c8d7a1a1 100644 --- a/crates/utils/src/email.rs +++ b/crates/utils/src/email.rs @@ -1,4 +1,7 @@ -use crate::{error::LemmyError, settings::structs::Settings}; +use crate::{ + error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + settings::structs::Settings, +}; use html2text; use lettre::{ message::{Mailbox, MultiPart}, @@ -23,22 +26,17 @@ pub async fn send_email( html: &str, settings: &Settings, ) -> Result<(), LemmyError> { - let email_config = settings - .email - .clone() - .ok_or_else(|| LemmyError::from_message("no_email_setup"))?; + let email_config = settings.email.clone().ok_or(LemmyErrorType::NoEmailSetup)?; let domain = settings.hostname.clone(); let (smtp_server, smtp_port) = { let email_and_port = email_config.smtp_server.split(':').collect::>(); let email = *email_and_port .first() - .ok_or_else(|| LemmyError::from_message("missing an email"))?; + .ok_or(LemmyErrorType::MissingAnEmail)?; let port = email_and_port .get(1) - .ok_or_else(|| { - LemmyError::from_message("email.smtp_server needs a port, IE smtp.xxx.com:465") - })? + .ok_or(LemmyErrorType::EmailSmtpServerNeedsAPort)? .parse::()?; (email, port) @@ -89,10 +87,10 @@ pub async fn send_email( let mailer = builder.hello_name(ClientId::Domain(domain)).build(); - let result = mailer.send(email).await; + mailer + .send(email) + .await + .with_lemmy_type(LemmyErrorType::EmailSendFailed)?; - match result { - Ok(_) => Ok(()), - Err(e) => Err(LemmyError::from_error_message(e, "email_send_failed")), - } + Ok(()) } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index c89e84dbb..cdb484722 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -1,74 +1,27 @@ +use serde::{Deserialize, Serialize}; use std::{ fmt, fmt::{Debug, Display}, }; use tracing_error::SpanTrace; - -#[derive(serde::Serialize)] -struct ApiError { - error: String, -} +#[cfg(feature = "full")] +use ts_rs::TS; pub type LemmyResult = Result; pub struct LemmyError { - pub message: Option, + pub error_type: Option, pub inner: anyhow::Error, pub context: SpanTrace, } -impl LemmyError { - /// Create LemmyError from a message, including stack trace - pub fn from_message(message: &str) -> Self { - let inner = anyhow::anyhow!("{}", message); - LemmyError { - message: Some(message.into()), - inner, - context: SpanTrace::capture(), - } - } - - /// Create a LemmyError from error and message, including stack trace - pub fn from_error_message(error: E, message: &str) -> Self - where - E: Into, - { - LemmyError { - message: Some(message.into()), - inner: error.into(), - context: SpanTrace::capture(), - } - } - - /// Add message to existing LemmyError (or overwrite existing error) - pub fn with_message(self, message: &str) -> Self { - LemmyError { - message: Some(message.into()), - ..self - } - } - - pub fn to_json(&self) -> Result { - let api_error = match &self.message { - Some(error) => ApiError { - error: error.into(), - }, - None => ApiError { - error: "Unknown".into(), - }, - }; - - Ok(serde_json::to_string(&api_error)?) - } -} - impl From for LemmyError where T: Into, { fn from(t: T) -> Self { LemmyError { - message: None, + error_type: None, inner: t.into(), context: SpanTrace::capture(), } @@ -78,7 +31,7 @@ where impl Debug for LemmyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("LemmyError") - .field("message", &self.message) + .field("message", &self.error_type) .field("inner", &self.inner) .field("context", &self.context) .finish() @@ -87,7 +40,7 @@ impl Debug for LemmyError { impl Display for LemmyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(message) = &self.message { + if let Some(message) = &self.error_type { write!(f, "{message}: ")?; } // print anyhow including trace @@ -108,10 +61,8 @@ impl actix_web::error::ResponseError for LemmyError { } fn error_response(&self) -> actix_web::HttpResponse { - if let Some(message) = &self.message { - actix_web::HttpResponse::build(self.status_code()).json(ApiError { - error: message.into(), - }) + if let Some(message) = &self.error_type { + actix_web::HttpResponse::build(self.status_code()).json(message) } else { actix_web::HttpResponse::build(self.status_code()) .content_type("text/plain") @@ -119,3 +70,230 @@ impl actix_web::error::ResponseError for LemmyError { } } } + +#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, EnumIter)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +#[serde(tag = "error", content = "message", rename_all = "snake_case")] +// TODO: order these based on the crate they belong to (utils, federation, db, api) +pub enum LemmyErrorType { + ReportReasonRequired, + ReportTooLong, + NotAModerator, + NotAnAdmin, + CantBlockYourself, + CantBlockAdmin, + CouldntUpdateUser, + PasswordsDoNotMatch, + EmailNotVerified, + EmailRequired, + CouldntUpdateComment, + CouldntUpdatePrivateMessage, + CannotLeaveAdmin, + NoLinesInHtml, + SiteMetadataPageIsNotDoctypeHtml, + PictrsResponseError(String), + PictrsPurgeResponseError(String), + ImageUrlMissingPathSegments, + ImageUrlMissingLastPathSegment, + PictrsApiKeyNotProvided, + NoContentTypeHeader, + NotAnImageType, + NotAModOrAdmin, + NoAdmins, + NotTopAdmin, + NotTopMod, + NotLoggedIn, + SiteBan, + Deleted, + BannedFromCommunity, + CouldntFindCommunity, + CouldntFindPerson, + PersonIsBlocked, + DownvotesAreDisabled, + InstanceIsPrivate, + InvalidPassword, + SiteDescriptionLengthOverflow, + HoneypotFailed, + RegistrationApplicationIsPending, + CantEnablePrivateInstanceAndFederationTogether, + Locked, + CouldntCreateComment, + MaxCommentDepthReached, + NoCommentEditAllowed, + OnlyAdminsCanCreateCommunities, + CommunityAlreadyExists, + LanguageNotAllowed, + OnlyModsCanPostInCommunity, + CouldntUpdatePost, + NoPostEditAllowed, + CouldntFindPost, + EditPrivateMessageNotAllowed, + SiteAlreadyExists, + ApplicationQuestionRequired, + InvalidDefaultPostListingType, + RegistrationClosed, + RegistrationApplicationAnswerRequired, + EmailAlreadyExists, + FederationForbiddenByStrictAllowList, + PersonIsBannedFromCommunity, + ObjectIsNotPublic, + InvalidCommunity, + CannotCreatePostOrCommentInDeletedOrRemovedCommunity, + CannotReceivePage, + NewPostCannotBeLocked, + OnlyLocalAdminCanRemoveCommunity, + OnlyLocalAdminCanRestoreCommunity, + NoIdGiven, + IncorrectLogin, + InvalidQuery, + ObjectNotLocal, + PostIsLocked, + PersonIsBannedFromSite, + InvalidVoteValue, + PageDoesNotSpecifyCreator, + PageDoesNotSpecifyGroup, + NoCommunityFoundInCc, + NoEmailSetup, + EmailSmtpServerNeedsAPort, + MissingAnEmail, + RateLimitError, + InvalidName, + InvalidDisplayName, + InvalidMatrixId, + InvalidPostTitle, + InvalidBodyField, + BioLengthOverflow, + MissingTotpToken, + IncorrectTotpToken, + CouldntParseTotpSecret, + CouldntLikeComment, + CouldntSaveComment, + CouldntCreateReport, + CouldntResolveReport, + CommunityModeratorAlreadyExists, + CommunityUserAlreadyBanned, + CommunityBlockAlreadyExists, + CommunityFollowerAlreadyExists, + CouldntUpdateCommunityHiddenStatus, + PersonBlockAlreadyExists, + UserAlreadyExists, + TokenNotFound, + CouldntLikePost, + CouldntSavePost, + CouldntMarkPostAsRead, + CouldntUpdateCommunity, + CouldntUpdateReplies, + CouldntUpdatePersonMentions, + PostTitleTooLong, + CouldntCreatePost, + CouldntCreatePrivateMessage, + CouldntUpdatePrivate, + SystemErrLogin, + CouldntSetAllRegistrationsAccepted, + CouldntSetAllEmailVerified, + Banned, + CouldntGetComments, + CouldntGetPosts, + InvalidUrl, + EmailSendFailed, + Slurs, + CouldntGenerateTotp, + CouldntFindObject, + RegistrationDenied(String), + FederationDisabled, + DomainBlocked, + DomainNotInAllowList, + FederationDisabledByStrictAllowList, + SiteNameRequired, + SiteNameLengthOverflow, + PermissiveRegex, + InvalidRegex, + CaptchaIncorrect, + PasswordResetLimitReached, + CouldntCreateAudioCaptcha, + InvalidUrlScheme, + CouldntSendWebmention, + Unknown, +} + +impl From for LemmyError { + fn from(error_type: LemmyErrorType) -> Self { + let inner = anyhow::anyhow!("{}", error_type); + LemmyError { + error_type: Some(error_type), + inner, + context: SpanTrace::capture(), + } + } +} + +pub trait LemmyErrorExt> { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; +} + +impl> LemmyErrorExt for Result { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { + self.map_err(|error| LemmyError { + error_type: Some(error_type), + inner: error.into(), + context: SpanTrace::capture(), + }) + } +} +pub trait LemmyErrorExt2 { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; +} + +impl LemmyErrorExt2 for Result { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { + self.map_err(|mut e| { + e.error_type = Some(error_type); + e + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{body::MessageBody, ResponseError}; + use std::fs::read_to_string; + use strum::IntoEnumIterator; + + #[test] + fn deserializes_no_message() { + let err = LemmyError::from(LemmyErrorType::Banned).error_response(); + let json = String::from_utf8(err.into_body().try_into_bytes().unwrap().to_vec()).unwrap(); + assert_eq!(&json, "{\"error\":\"banned\"}") + } + + #[test] + fn deserializes_with_message() { + let reg_denied = LemmyErrorType::RegistrationDenied(String::from("reason")); + let err = LemmyError::from(reg_denied).error_response(); + let json = String::from_utf8(err.into_body().try_into_bytes().unwrap().to_vec()).unwrap(); + assert_eq!( + &json, + "{\"error\":\"registration_denied\",\"message\":\"reason\"}" + ) + } + + /// Check if errors match translations. Disabled because many are not translated at all. + #[test] + #[ignore] + fn test_translations_match() { + #[derive(Deserialize)] + struct Err { + error: String, + } + + let translations = read_to_string("translations/translations/en.json").unwrap(); + LemmyErrorType::iter().for_each(|e| { + let msg = serde_json::to_string(&e).unwrap(); + let msg: Err = serde_json::from_str(&msg).unwrap(); + let msg = msg.error; + assert!(translations.contains(&format!("\"{msg}\"")), "{msg}"); + }); + } +} diff --git a/crates/utils/src/rate_limit/mod.rs b/crates/utils/src/rate_limit/mod.rs index d1c51265d..7a5c1ec68 100644 --- a/crates/utils/src/rate_limit/mod.rs +++ b/crates/utils/src/rate_limit/mod.rs @@ -1,4 +1,4 @@ -use crate::error::LemmyError; +use crate::error::{LemmyError, LemmyErrorType}; use actix_web::dev::{ConnectionInfo, Service, ServiceRequest, ServiceResponse, Transform}; use enum_map::enum_map; use futures::future::{ok, Ready}; @@ -246,7 +246,7 @@ where } else { let (http_req, _) = req.into_parts(); Ok(ServiceResponse::from_err( - LemmyError::from_message("rate_limit_error"), + LemmyError::from(LemmyErrorType::RateLimitError), http_req, )) } diff --git a/crates/utils/src/utils/slurs.rs b/crates/utils/src/utils/slurs.rs index b92650ea3..b041eb460 100644 --- a/crates/utils/src/utils/slurs.rs +++ b/crates/utils/src/utils/slurs.rs @@ -1,4 +1,4 @@ -use crate::error::LemmyError; +use crate::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use regex::{Regex, RegexBuilder}; pub fn remove_slurs(test: &str, slur_regex: &Option) -> String { @@ -41,10 +41,7 @@ pub fn build_slur_regex(regex_str: Option<&str>) -> Option { pub fn check_slurs(text: &str, slur_regex: &Option) -> Result<(), LemmyError> { if let Err(slurs) = slur_check(text, slur_regex) { - Err(LemmyError::from_error_message( - anyhow::anyhow!("{}", slurs_vec_to_str(&slurs)), - "slurs", - )) + Err(anyhow::anyhow!("{}", slurs_vec_to_str(&slurs))).with_lemmy_type(LemmyErrorType::Slurs) } else { Ok(()) } diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index b5ae5b74f..ec2d20b97 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -1,4 +1,4 @@ -use crate::error::{LemmyError, LemmyResult}; +use crate::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; use itertools::Itertools; use once_cell::sync::Lazy; use regex::{Regex, RegexBuilder}; @@ -90,7 +90,7 @@ pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyRes && VALID_ACTOR_NAME_REGEX.is_match(name) && !has_newline(name); if !check { - Err(LemmyError::from_message("invalid_name")) + Err(LemmyErrorType::InvalidName.into()) } else { Ok(()) } @@ -104,7 +104,7 @@ pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyR && name.chars().count() <= actor_name_max_length && !has_newline(name); if !check { - Err(LemmyError::from_message("invalid_username")) + Err(LemmyErrorType::InvalidDisplayName.into()) } else { Ok(()) } @@ -113,7 +113,7 @@ pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyR pub fn is_valid_matrix_id(matrix_id: &str) -> LemmyResult<()> { let check = VALID_MATRIX_ID_REGEX.is_match(matrix_id) && !has_newline(matrix_id); if !check { - Err(LemmyError::from_message("invalid_matrix_id")) + Err(LemmyErrorType::InvalidMatrixId.into()) } else { Ok(()) } @@ -122,7 +122,7 @@ pub fn is_valid_matrix_id(matrix_id: &str) -> LemmyResult<()> { pub fn is_valid_post_title(title: &str) -> LemmyResult<()> { let check = VALID_POST_TITLE_REGEX.is_match(title) && !has_newline(title); if !check { - Err(LemmyError::from_message("invalid_post_title")) + Err(LemmyErrorType::InvalidPostTitle.into()) } else { Ok(()) } @@ -138,7 +138,7 @@ pub fn is_valid_body_field(body: &Option, post: bool) -> LemmyResult<()> }; if !check { - Err(LemmyError::from_message("invalid_body_field")) + Err(LemmyErrorType::InvalidBodyField.into()) } else { Ok(()) } @@ -148,7 +148,7 @@ pub fn is_valid_body_field(body: &Option, post: bool) -> LemmyResult<()> } pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> { - max_length_check(bio, BIO_MAX_LENGTH, String::from("bio_length_overflow")) + max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow) } /// Checks the site name length, the limit as defined in the DB. @@ -157,8 +157,8 @@ pub fn site_name_length_check(name: &str) -> LemmyResult<()> { name, SITE_NAME_MIN_LENGTH, SITE_NAME_MAX_LENGTH, - String::from("site_name_required"), - String::from("site_name_length_overflow"), + LemmyErrorType::SiteNameRequired, + LemmyErrorType::SiteNameLengthOverflow, ) } @@ -167,13 +167,13 @@ pub fn site_description_length_check(description: &str) -> LemmyResult<()> { max_length_check( description, SITE_DESCRIPTION_MAX_LENGTH, - String::from("site_description_length_overflow"), + LemmyErrorType::SiteDescriptionLengthOverflow, ) } -fn max_length_check(item: &str, max_length: usize, msg: String) -> LemmyResult<()> { +fn max_length_check(item: &str, max_length: usize, error_type: LemmyErrorType) -> LemmyResult<()> { if item.len() > max_length { - Err(LemmyError::from_message(&msg)) + Err(error_type.into()) } else { Ok(()) } @@ -183,13 +183,13 @@ fn min_max_length_check( item: &str, min_length: usize, max_length: usize, - min_msg: String, - max_msg: String, + min_msg: LemmyErrorType, + max_msg: LemmyErrorType, ) -> LemmyResult<()> { if item.len() > max_length { - Err(LemmyError::from_message(&max_msg)) + Err(max_msg.into()) } else if item.len() < min_length { - Err(LemmyError::from_message(&min_msg)) + Err(min_msg.into()) } else { Ok(()) } @@ -209,14 +209,14 @@ pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult