Error enum fixed (#3487)
authorNutomic <me@nutomic.com>
Mon, 10 Jul 2023 14:50:07 +0000 (16:50 +0200)
committerGitHub <noreply@github.com>
Mon, 10 Jul 2023 14:50:07 +0000 (16:50 +0200)
* Create error type enum

* Replace magic string slices with LemmyErrorTypes

* Remove unused enum

* Add rename snake case to error enum

* Rename functions

* clippy

* Fix merge errors

* Serialize in PascalCase instead of snake_case

* Revert src/lib

* Add serialization tests

* Update translations

* Fix compilation error in test

* Fix another compilation error

* Add code for generating typescript types

* Various fixes to avoid breaking api

* impl From<LemmyErrorType> for LemmyError

* with_lemmy_type

* trigger ci

---------

Co-authored-by: SleeplessOne1917 <abias1122@gmail.com>
82 files changed:
Cargo.lock
crates/api/src/comment/distinguish.rs
crates/api/src/comment/like.rs
crates/api/src/comment/save.rs
crates/api/src/comment_report/create.rs
crates/api/src/comment_report/resolve.rs
crates/api/src/community/add_mod.rs
crates/api/src/community/ban.rs
crates/api/src/community/block.rs
crates/api/src/community/follow.rs
crates/api/src/community/hide.rs
crates/api/src/community/transfer.rs
crates/api/src/lib.rs
crates/api/src/local_user/add_admin.rs
crates/api/src/local_user/ban_person.rs
crates/api/src/local_user/block.rs
crates/api/src/local_user/change_password.rs
crates/api/src/local_user/change_password_after_reset.rs
crates/api/src/local_user/login.rs
crates/api/src/local_user/notifications/mark_all_read.rs
crates/api/src/local_user/notifications/mark_mention_read.rs
crates/api/src/local_user/notifications/mark_reply_read.rs
crates/api/src/local_user/reset_password.rs
crates/api/src/local_user/save_settings.rs
crates/api/src/local_user/verify_email.rs
crates/api/src/post/like.rs
crates/api/src/post/save.rs
crates/api/src/post_report/create.rs
crates/api/src/post_report/resolve.rs
crates/api/src/private_message/mark_read.rs
crates/api/src/private_message_report/create.rs
crates/api/src/private_message_report/resolve.rs
crates/api/src/site/leave_admin.rs
crates/api_common/src/request.rs
crates/api_common/src/utils.rs
crates/api_crud/src/comment/create.rs
crates/api_crud/src/comment/delete.rs
crates/api_crud/src/comment/remove.rs
crates/api_crud/src/comment/update.rs
crates/api_crud/src/community/create.rs
crates/api_crud/src/community/delete.rs
crates/api_crud/src/community/remove.rs
crates/api_crud/src/community/update.rs
crates/api_crud/src/post/create.rs
crates/api_crud/src/post/delete.rs
crates/api_crud/src/post/read.rs
crates/api_crud/src/post/update.rs
crates/api_crud/src/private_message/create.rs
crates/api_crud/src/private_message/delete.rs
crates/api_crud/src/private_message/update.rs
crates/api_crud/src/site/create.rs
crates/api_crud/src/site/mod.rs
crates/api_crud/src/site/read.rs
crates/api_crud/src/site/update.rs
crates/api_crud/src/user/create.rs
crates/api_crud/src/user/delete.rs
crates/apub/src/activities/community/announce.rs
crates/apub/src/activities/create_or_update/post.rs
crates/apub/src/activities/deletion/delete.rs
crates/apub/src/activities/deletion/undo_delete.rs
crates/apub/src/activities/mod.rs
crates/apub/src/api/list_comments.rs
crates/apub/src/api/list_posts.rs
crates/apub/src/api/read_community.rs
crates/apub/src/api/read_person.rs
crates/apub/src/api/resolve_object.rs
crates/apub/src/fetcher/search.rs
crates/apub/src/http/community.rs
crates/apub/src/http/mod.rs
crates/apub/src/lib.rs
crates/apub/src/objects/comment.rs
crates/apub/src/objects/private_message.rs
crates/apub/src/protocol/activities/voting/vote.rs
crates/apub/src/protocol/objects/page.rs
crates/db_schema/src/impls/actor_language.rs
crates/db_schema/src/utils.rs
crates/utils/Cargo.toml
crates/utils/src/email.rs
crates/utils/src/error.rs
crates/utils/src/rate_limit/mod.rs
crates/utils/src/utils/slurs.rs
crates/utils/src/utils/validation.rs

index e6ee29d22089ab11901c3d5331d1c5c6a50b2565..b2d3a7bf405b94d4ca1d3427ac7710ded137eb12 100644 (file)
@@ -2911,6 +2911,7 @@ dependencies = [
  "totp-rs",
  "tracing",
  "tracing-error",
+ "ts-rs",
  "typed-builder",
  "url",
  "uuid",
index 17f45bfd4b292bf4aa3779dcdb401055cf639e96..1478ee220b47d72f0cedf8f209da5ce7738deb10 100644 (file)
@@ -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;
index 6c4bdebc79cc71b76cb62a6159c37fc2f9c9d0ec..f44e03dcf53be890ac318a64c82ead4f8b86053e 100644 (file)
@@ -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(
index 70c9745f8b884bb42af1bbe4c18ec9e4f5d7b205..42c91bf05fbf6e336b5adf40d16d9dc5948b9b82 100644 (file)
@@ -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;
index 8ef975786b6f06125dcefcec2266fac4efc723d2..5c3157833ac80b65f62a6a2e74f34faf694ba160 100644 (file)
@@ -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?;
 
index 5f1ae97dddca86d890b2e44589f68de67f5d9394..88fd70071547ef28a47fdc2a9b0a105b24b136ee 100644 (file)
@@ -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;
index a7afd952d549b59ce15104e820871271598d1310..2d703577e4468ef9504fdad2149734eb7db50a7a 100644 (file)
@@ -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
index 330c2c56de9a49d50250399bfa05f8553c90cbd0..1d3865e138022f95cdc958e49b434fa161d4bfa2 100644 (file)
@@ -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
index fe3fb6e0791b5630d6b293cc5ba8d05b1bde40fe..20c601eb307eb17ddc07376425f435c1c02df277 100644 (file)
@@ -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 =
index 39feeff25942f16abba4e70efbfe23c2d1967010..6d3d8285494fae6749ae6f1a76d92f10e7157513 100644 (file)
@@ -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;
index f8d4b1fd7d200b97fe79063614e5a60d21074e23..ecc9d390d59001b212aef9b472f177f2729a97b7 100644 (file)
@@ -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?;
 
index 10ba1c4fac0f8481c344e64b1e8d136e9d643cf8..192db9dafc3dd7e0820528ca922cfb8f05a7f5f8 100644 (file)
@@ -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 {
index 44e7a76b59c458a25af7e4facc8ac4192b5a7b82..f47d1bd56dc3b07c54a2a2ad079d37da06e10eee 100644 (file)
@@ -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<String, LemmyEr
     if let Some(samples16) = samples.as_sixteen() {
       concat_samples.extend(samples16);
     } else {
-      return Err(LemmyError::from_message("couldnt_create_audio_captcha"));
+      return Err(LemmyErrorType::CouldntCreateAudioCaptcha)?;
     }
   }
 
@@ -45,19 +48,14 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyEr
   let mut output_buffer = Cursor::new(vec![]);
   let header = match any_header {
     Some(header) => 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(())
 }
index 0d2db3771c7c82136fe37e0448603292493eeb8b..a58174fb4aed02347cf086a91f39bccfd323fd57 100644 (file)
@@ -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 {
index 2c2d363e313345250c21308ef44c96fecf665f6b..15bc11cc6b128a136e1beca496121f384d2f0862 100644 (file)
@@ -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);
index c9eaa272587935bf3c84ab8c7c9273ffb2e80cfe..67b1f521d42f6346bf0776383042cf6aaf5670f9 100644 (file)
@@ -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 {
index 60e0b960d462a574530cd7831a4e3e407b9f1331..ffe452c92c71436afed8cfd6719016726f2e663c 100644 (file)
@@ -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;
index de05f8e59848df0bf380270c59e665cec114425b..b6da6c324d8c2be48a200b1afb14b08790c6925b 100644 (file)
@@ -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?;
index 3bf177666c2a5d7f90973cf6786213e4739a6e54..f93ef335e5dc495a79f21a937559bd4da831d44d 100644 (file)
@@ -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?;
index cc2999698105b0aca6cf6599c356c82f8da07b10..fada65118228e1d8a3f8ff25c99d66f87df02bf8 100644 (file)
@@ -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![] })
   }
index dd3c413f29cbaf8554c01c9432d72081eb5e1137..668286db909a968bb25605843324e421abc66098 100644 (file)
@@ -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;
index a8291cd5074f26a0b02f23c2a24aeb10842e2159..2e338367ebb343b91810383e59bd0193e7ebc805 100644 (file)
@@ -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;
index b5325608d0521bf75b7767e49445bb6fc7c6cbc9..0f896f477c287d27f73d96aac38fde4b0438fec8 100644 (file)
@@ -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.
index 8f8d3194e34f4e20d415d1742c397378adf4e675..578d177323322c233d3261a26045b2595bd4c0ba 100644 (file)
@@ -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);
       }
     };
 
index 0807eebe0947b2de274353ce79bca1d586d634c2..2a0b2f3c531f3ad5b629123da5d203d8dacb5512 100644 (file)
@@ -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
index 5811107c2994a0a48a0de974fd520e9960900455..896ab5ba22d99ebfbe15b52230abcf11f0803982 100644 (file)
@@ -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
index 3213d2be6cd5fd4c610239925444b446c551e709..bc43610af5b2012ecf90135ea2e20c1ddede9080 100644 (file)
@@ -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;
index 52bc3ce75fa0da7e9d440f15cf045914465afe3a..0915a0a8e044efa0aff3a4d4ae666d672068e6f1 100644 (file)
@@ -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?;
 
index cc6d0510ffbcb7ab136646698c1c47b17eff212d..91243c09869a3ecacd8ab11d08e54e033dd533a1 100644 (file)
@@ -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?;
index ca343174237bad401607fb4bcdbdce6610191d87..bb81a62c0874ec9880f3838e1841b8f85d9a6441 100644 (file)
@@ -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 {
index 2a2feef346500ebdd28ae2402fbd0f50e6a17004..d732b41af3f9a83067b7d25e010759680d6c42bc 100644 (file)
@@ -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?;
index e4fcfc85681aeb13bc05e9cd95389eb1f2b626f2..964610536bfed489f5844fc60d2319ccbe8cb7f6 100644 (file)
@@ -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 =
index d7c0cf3aa222159173fad1c674ba220c111c1342..d236838000c0b137538e6e45a7b1b182b0007244 100644 (file)
@@ -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;
index 64563bc0d6429149ec0bddf343437b556ec22b21..dc09ecaa7f3f69a88c852498064b71eb34ee1d53 100644 (file)
@@ -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<SiteMetadata, L
     .trim_start()
     .lines()
     .next()
-    .ok_or_else(|| LemmyError::from_message("No lines in html"))?
+    .ok_or(LemmyErrorType::NoLinesInHtml)?
     .to_lowercase();
 
   if !first_line.starts_with("<!doctype html>") {
-    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)?
   }
 }
 
index 2ef06f52802ab727e3dbc21cbbb45e5647f17b38..793cc70c3fb9dc1d901ab2b8782c26cfb809b924 100644 (file)
@@ -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, LemmyError> {
   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<LocalUserView, LemmyError> {
   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<String>) -> 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(&registration_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?;
index 1bcc78483c3b8659ab81c3df51164051446551ef..c227e84da7520164b55f01ab0b24e1c675b941c3 100644 (file)
@@ -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(())
   }
index 864daded8cad381e9ca6a43877928bd7ad2e3207..da2403bcfe1fa9e8eed9823b461bb036c18c8def 100644 (file)
@@ -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?;
index 6e4e5528194d690a4a2338816cf9b086a5b4232a..21cb3c8a603e13ac0b526a20c9bfff282caa3dd0 100644 (file)
@@ -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 {
index 3504e784d5b189c71e5007edede4832a4c6c6d68..7b37d900430f4086a6d5e8cd792d73c629dbf731 100644 (file)
@@ -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();
index 0e55beac9a288f80944b15960050bfbfde137b68..5532e3150ca5ed2e46c9ee37e890e48c6b9e041d 100644 (file)
@@ -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?;
     }
index 019e9f1da7cd9f24cc550cc756c32dd58cce0b75..97641f57ece30defab415baeec8791183a3b6d83 100644 (file)
@@ -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
   }
index 52a503c45aadf5e15de685954d4fede4fb593bdd..fd011c6c26bca78721eecd72b51fcdf73d685143 100644 (file)
@@ -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);
index dec62865f25c8c8664e19025ee45d3699e823d30..9bef9388ba58d04029340c9b4c1038aaf7c1d54e 100644 (file)
@@ -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
   }
index 4264c26d4e759e065d4acffb48eb6d3f14ebe8f2..20f6cfd3bac368104d03a70f24b929445594ffec 100644 (file)
@@ -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 {
index 6e6a4c076b18d7038838ee1b136dbb8abf42cd25..d112459491be7a0b4fae6ff5de2d868d101f81d1 100644 (file)
@@ -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
index d1851f33e39f4357577273170299bd70479cdde3..519b748ccd35663bee8b6bbefe803c396fd413aa 100644 (file)
@@ -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?;
index a8c5715bb46b2ab88445c3d2107a5d095f2a4d6b..9366109662e30103e05b4de28dc0a200ffbf1db8 100644 (file)
@@ -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,
index 187b3f90ae6149dd6ac0fdb6458e37dfb4159f9e..d399ffb76fd2b188bdcd7816b69e687c371373d3 100644 (file)
@@ -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?;
 
index b8e3c3b1101a40b1ef9436356a87875c70c59fc5..be9ed695f7c89db9d53d30e59aeb60e7e550349a 100644 (file)
@@ -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 {
index b2d8e48f9987da2cb28721002701bf0300e2f846..e20a7115ef9b21e7936c5ec92191337bf1de029e 100644 (file)
@@ -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?;
 
index 838d5bc409f6f937fb6976275e925567acc32a80..be8411e246fff3abb4d0950018c4142e64df35c7 100644 (file)
@@ -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::<String>,
@@ -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::<String>,
@@ -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::<String>,
@@ -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::<String>,
@@ -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::<String>,
@@ -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
index a98f2057c682f8f795315c97ac68e498152d60c8..d7ae94aca7ed3a1cfdbb852fe11989eb36dc0315 100644 (file)
@@ -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(())
   }
index f6c663ac087447148e26365b50ae2c06bade6aa2..4368da99d3788a08974e9a1d2179e063784fd2f5 100644 (file)
@@ -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,
index c9f97e8dfab9d0c7f295a3ba3207dc3792d478a3..1e354d9cd4f49de2cd86b472b3436a3abc2c8cac 100644 (file)
@@ -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::<String>,
           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::<String>,
           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::<String>,
           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::<String>,
           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
index 302e2f98e62543265bd8415af69f0ae2ad94d507..2bfd48ef0c74c2c7aa7bfa8c65b8e973d80d0a9a 100644 (file)
@@ -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.
index 7ce0312cf56cbff6a606b6605c4a04c90203156a..5a8b4d03628176a4fb226685a8e52d26b70b1e80 100644 (file)
@@ -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 {})
index 116b027269b1d13c4e9bcf0e61f4d319d6e2811e..e33e9fbf482ed1a02d978101574f04669187ccc4 100644 (file)
@@ -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
index 916111fc5488fcd3b682636ea09bf4f8a340635d..7d5ce33659bd9705fdd65b32932d1d6693a892a1 100644 (file)
@@ -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 => {
index dfa709c36d3bcefc4b783c323ad4c49dc2c4e76e..d083309287467fa2b870c266c6cd4b72924d0c44 100644 (file)
@@ -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,
index 00768580dda0a3fb8dfcb06137a60d187a0b71fa..e5c5148113bbe3f3e1b354c00f86ff3d75f604b0 100644 (file)
@@ -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,
index 4b50c5c835818f3bac937fbb7ed61b2f406a1ba2..29d15701dde24098e51388c96ce140f8fffd00cd 100644 (file)
@@ -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<ApubCommunity> = 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(())
   }
index edb70dbaad1f52aa30b79b4d91ea0fc30c391741..531a6eddbbd00a4cd16616eaaa2f618445336fdd 100644 (file)
@@ -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 }))
 }
index ff7ac1089c43da5bda12caa4d164a9169bac3543..929cb95c30006b3dc04f3731d1b12b3a653360cc 100644 (file)
@@ -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 }))
 }
index e524694d351a9f955df5c9e9ed5cb65745ee2ac5..5c8e8cac3e482b47a61492d3b28379a2935f981a 100644 (file)
@@ -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::<ApubCommunity, Community>(&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?;
index fb4755e3836b742e1a31effe935dfd7c6172aeb2..35fd59cfe83e0f1232300d7685d2aca88bedd84e 100644 (file)
@@ -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<Json<GetPersonDetailsResponse>, 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::<ApubPerson, Person>(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)?;
       }
     }
   };
index 09689def1b1003769a3b310705a78e9919ceba92..30f381d768db1376f26faba78b56d33e779700ae 100644 (file)
@@ -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(
index 41bcce375d43d6b026b7a86aa27ae5c7d4f3c926..39ecbc1be253b4bc42f013fcd02333cd1b9c5eb9 100644 (file)
@@ -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::<LemmyContext, ApubCommunity>(identifier, context).await?,
         ),
-        _ => return Err(LemmyError::from_message("invalid query")),
+        _ => return Err(LemmyErrorType::InvalidQuery)?,
       }
     }
   })
index 04ac8e3fd93504e99372e7fb6606d20d53a38548..19793674c48389ae9b343ebe37d165096a98dc6f 100644 (file)
@@ -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)
index 66d5a0f9fc3240d52b782d0abe753a0c941d0645..fec287e19f994ea4b1efa4e9c0d57d5d6ff39e1c 100644 (file)
@@ -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<T: Into<Url>>(id: T) -> LemmyResult<HttpRespon
 }
 
 fn err_object_not_local() -> LemmyError {
-  LemmyError::from_message("Object not local, fetch it from original instance")
+  LemmyErrorType::ObjectNotLocal.into()
 }
 
 #[derive(Deserialize)]
index 1c36f9852ecf823f2d68a833ee6fec8004ff6beb..f7ef22eece91375f1e06d24027578a2805ba16c0 100644 (file)
@@ -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(())
index 46898effecfb14570539b851502c926212d57d40..da844ecc59046861f0076aae613c50dad850955f 100644 (file)
@@ -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(&note.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(())
   }
index 75c9e460b72c1baa8b117a445cd98add7026520e..e2571bd938ad9227a061859e1a1db14da1b7c62d 100644 (file)
@@ -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(())
   }
index 4450fed8bb2f1e71fff73078738e5ce898dfe8cf..0c199c729bd482671d1445e9910320279d103843 100644 (file)
@@ -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<i16> for VoteType {
     match value {
       1 => Ok(VoteType::Like),
       -1 => Ok(VoteType::Dislike),
-      _ => Err(LemmyError::from_message("invalid vote value")),
+      _ => Err(LemmyErrorType::InvalidVoteValue.into()),
     }
   }
 }
index ad89cf35b04607869957553e02a878f22cb564e4..c48cabfa743faf6306870a20646ee24a46652793 100644 (file)
@@ -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::<ApubPerson>::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::<ApubCommunity>::from(a.id.clone().into_inner()))
-          .ok_or_else(|| LemmyError::from_message("page does not specify group"))?
+          .ok_or(LemmyErrorType::PageDoesNotSpecifyGroup)?
           .dereference(context)
           .await?
       }
index dbb9c70fe4988d93a37b179d57530eb0e3f7bf98..403ef7170fa3d12b20fce07cc48bb3e58dde53e7 100644 (file)
@@ -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(())
index 1dc2f9afa299af1b5da16a6a8b2bdf5c73544205..267540554800ca8cbdf68e5f39da51c0a1f855a3 100644 (file)
@@ -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<String>) -> Option<Option<String>> {
 pub fn diesel_option_overwrite_to_url(
   opt: &Option<String>,
 ) -> Result<Option<Option<DbUrl>>, 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<String>,
 ) -> Result<Option<DbUrl>, 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),
   }
 }
index 14709e724c4360cca622d73603ca6e483ae74b25..b97ce8bdc7ea50f7ede6f598daed98a81db2e556 100644 (file)
@@ -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]
index fba624666e7c45283de569001889f4c5f88917f0..3c8d7a1a126780f919998a23f8ee2972a9fa412b 100644 (file)
@@ -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::<Vec<&str>>();
     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::<u16>()?;
 
     (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(())
 }
index c89e84dbb753f69799cfc64a77359e40ee4f54dd..cdb484722e71935fb95624b4b690f35b32676a49 100644 (file)
@@ -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<T> = Result<T, LemmyError>;
 
 pub struct LemmyError {
-  pub message: Option<String>,
+  pub error_type: Option<LemmyErrorType>,
   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<E>(error: E, message: &str) -> Self
-  where
-    E: Into<anyhow::Error>,
-  {
-    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<String, Self> {
-    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<T> From<T> for LemmyError
 where
   T: Into<anyhow::Error>,
 {
   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<LemmyErrorType> 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<T, E: Into<anyhow::Error>> {
+  fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
+}
+
+impl<T, E: Into<anyhow::Error>> LemmyErrorExt<T, E> for Result<T, E> {
+  fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError> {
+    self.map_err(|error| LemmyError {
+      error_type: Some(error_type),
+      inner: error.into(),
+      context: SpanTrace::capture(),
+    })
+  }
+}
+pub trait LemmyErrorExt2<T> {
+  fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
+}
+
+impl<T> LemmyErrorExt2<T> for Result<T, LemmyError> {
+  fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError> {
+    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}");
+    });
+  }
+}
index d1c51265d5f1d59d8ccec10a52781cfb61bfa288..7a5c1ec685759ee4a89a90562c92309d2a725edd 100644 (file)
@@ -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,
         ))
       }
index b92650ea31375bbf6bef0480c534e2e2249931e9..b041eb46049461c1475142d290d6516108a52546 100644 (file)
@@ -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<Regex>) -> String {
@@ -41,10 +41,7 @@ pub fn build_slur_regex(regex_str: Option<&str>) -> Option<Regex> {
 
 pub fn check_slurs(text: &str, slur_regex: &Option<Regex>) -> 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(())
   }
index b5ae5b74f3d473efd9474f002d41a6c15ab1d3dd..ec2d20b979db499639fa9df48acf482cee32dcd8 100644 (file)
@@ -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<String>, 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<String>, 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<Option
       RegexBuilder::new(regex_str)
         .case_insensitive(true)
         .build()
-        .map_err(|e| LemmyError::from_error_message(e, "invalid_regex"))
+        .with_lemmy_type(LemmyErrorType::InvalidRegex)
         .and_then(|regex| {
           // NOTE: It is difficult to know, in the universe of user-crafted regex, which ones
           // may match against any string text. To keep it simple, we'll match the regex
           // against an innocuous string - a single number - which should help catch a regex
           // that accidentally matches against all strings.
           if regex.is_match("1") {
-            return Err(LemmyError::from_message("permissive_regex"));
+            return Err(LemmyErrorType::PermissiveRegex.into());
           }
 
           Ok(Some(regex))
@@ -249,13 +249,13 @@ pub fn check_totp_2fa_valid(
     // Throw an error if their token is missing
     let token = totp_token
       .as_deref()
-      .ok_or_else(|| LemmyError::from_message("missing_totp_token"))?;
+      .ok_or(LemmyErrorType::MissingTotpToken)?;
 
     let totp = build_totp_2fa(site_name, username, totp_secret)?;
 
     let check_passed = totp.check_current(token)?;
     if !check_passed {
-      return Err(LemmyError::from_message("incorrect_totp token"));
+      return Err(LemmyErrorType::IncorrectTotpToken.into());
     }
   }
 
@@ -270,7 +270,7 @@ pub fn build_totp_2fa(site_name: &str, username: &str, secret: &str) -> Result<T
   let sec = Secret::Raw(secret.as_bytes().to_vec());
   let sec_bytes = sec
     .to_bytes()
-    .map_err(|_| LemmyError::from_message("Couldnt parse totp secret"))?;
+    .map_err(|_| LemmyErrorType::CouldntParseTotpSecret)?;
 
   TOTP::new(
     totp_rs::Algorithm::SHA256,
@@ -281,7 +281,7 @@ pub fn build_totp_2fa(site_name: &str, username: &str, secret: &str) -> Result<T
     Some(site_name.to_string()),
     username.to_string(),
   )
-  .map_err(|e| LemmyError::from_error_message(e, "Couldnt generate TOTP"))
+  .with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
 }
 
 pub fn check_site_visibility_valid(
@@ -294,9 +294,7 @@ pub fn check_site_visibility_valid(
   let federation_enabled = new_federation_enabled.unwrap_or(current_federation_enabled);
 
   if private_instance && federation_enabled {
-    return Err(LemmyError::from_message(
-      "cant_enable_private_instance_and_federation_together",
-    ));
+    return Err(LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether.into());
   }
 
   Ok(())
@@ -305,7 +303,7 @@ pub fn check_site_visibility_valid(
 pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
   if let Some(url) = url {
     if url.scheme() != "http" && url.scheme() != "https" {
-      return Err(LemmyError::from_message("invalid_url_scheme"));
+      return Err(LemmyErrorType::InvalidUrlScheme.into());
     }
   }
   Ok(())
@@ -314,22 +312,25 @@ pub fn check_url_scheme(url: &Option<Url>) -> LemmyResult<()> {
 #[cfg(test)]
 mod tests {
   use super::build_totp_2fa;
-  use crate::utils::validation::{
-    build_and_check_regex,
-    check_site_visibility_valid,
-    check_url_scheme,
-    clean_url_params,
-    generate_totp_2fa_secret,
-    is_valid_actor_name,
-    is_valid_bio_field,
-    is_valid_display_name,
-    is_valid_matrix_id,
-    is_valid_post_title,
-    site_description_length_check,
-    site_name_length_check,
-    BIO_MAX_LENGTH,
-    SITE_DESCRIPTION_MAX_LENGTH,
-    SITE_NAME_MAX_LENGTH,
+  use crate::{
+    error::LemmyErrorType,
+    utils::validation::{
+      build_and_check_regex,
+      check_site_visibility_valid,
+      check_url_scheme,
+      clean_url_params,
+      generate_totp_2fa_secret,
+      is_valid_actor_name,
+      is_valid_bio_field,
+      is_valid_display_name,
+      is_valid_matrix_id,
+      is_valid_post_title,
+      site_description_length_check,
+      site_name_length_check,
+      BIO_MAX_LENGTH,
+      SITE_DESCRIPTION_MAX_LENGTH,
+      SITE_NAME_MAX_LENGTH,
+    },
   };
   use url::Url;
 
@@ -409,9 +410,9 @@ mod tests {
         &(0..SITE_NAME_MAX_LENGTH + 1)
           .map(|_| 'A')
           .collect::<String>(),
-        "site_name_length_overflow",
+        LemmyErrorType::SiteNameLengthOverflow,
       ),
-      (&String::new(), "site_name_required"),
+      (&String::new(), LemmyErrorType::SiteNameRequired),
     ];
 
     valid_names.iter().for_each(|valid_name| {
@@ -425,15 +426,15 @@ mod tests {
 
     invalid_names
       .iter()
-      .for_each(|&(invalid_name, expected_err)| {
+      .for_each(|(invalid_name, expected_err)| {
         let result = site_name_length_check(invalid_name);
 
         assert!(result.is_err());
         assert!(
           result
             .unwrap_err()
-            .message
-            .eq(&Some(String::from(expected_err))),
+            .error_type
+            .eq(&Some(expected_err.clone())),
           "Testing {}, expected error {}",
           invalid_name,
           expected_err
@@ -452,8 +453,8 @@ mod tests {
       invalid_result.is_err()
         && invalid_result
           .unwrap_err()
-          .message
-          .eq(&Some(String::from("bio_length_overflow")))
+          .error_type
+          .eq(&Some(LemmyErrorType::BioLengthOverflow))
     );
   }
 
@@ -476,8 +477,8 @@ mod tests {
       invalid_result.is_err()
         && invalid_result
           .unwrap_err()
-          .message
-          .eq(&Some(String::from("site_description_length_overflow")))
+          .error_type
+          .eq(&Some(LemmyErrorType::SiteDescriptionLengthOverflow))
     );
   }
 
@@ -495,22 +496,22 @@ mod tests {
   #[test]
   fn test_too_permissive_slur_regex() {
     let match_everything_regexes = [
-      (&Some("["), "invalid_regex"),
-      (&Some("(foo|bar|)"), "permissive_regex"),
-      (&Some(".*"), "permissive_regex"),
+      (&Some("["), LemmyErrorType::InvalidRegex),
+      (&Some("(foo|bar|)"), LemmyErrorType::PermissiveRegex),
+      (&Some(".*"), LemmyErrorType::PermissiveRegex),
     ];
 
     match_everything_regexes
       .iter()
-      .for_each(|&(regex_str, expected_err)| {
+      .for_each(|(regex_str, expected_err)| {
         let result = build_and_check_regex(regex_str);
 
         assert!(result.is_err());
         assert!(
           result
             .unwrap_err()
-            .message
-            .eq(&Some(String::from(expected_err))),
+            .error_type
+            .eq(&Some(expected_err.clone())),
           "Testing regex {:?}, expected error {}",
           regex_str,
           expected_err