"totp-rs",
"tracing",
"tracing-error",
+ "ts-rs",
"typed-builder",
"url",
"uuid",
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 {
.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;
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 {
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(
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 {
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;
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)]
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?;
};
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)]
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;
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 {
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
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
};
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},
};
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 {
} 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
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 {
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 {
} 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 =
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 {
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;
},
traits::Crud,
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
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?;
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.
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.
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
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 {
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;
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)?;
}
}
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()))
}
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(())
}
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 {
&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 {
};
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},
};
.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);
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 {
// 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 {
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 {
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 {
// 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
)
.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;
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 {
// 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?;
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 {
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(
)
.unwrap_or(false);
if !valid {
- return Err(LemmyError::from_message("incorrect_login"));
+ return Err(LemmyErrorType::IncorrectLogin)?;
}
check_user_valid(
local_user_view.person.banned,
&& 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?;
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 {
// 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![] })
}
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 {
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;
&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;
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 {
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;
&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;
};
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 {
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(
)
.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.
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,
// 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)?;
}
}
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?;
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);
}
};
},
traits::Crud,
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
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
},
traits::{Crud, Likeable},
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
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
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 {
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;
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)]
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?;
};
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)]
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?;
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 {
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
&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 {
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 {
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?;
};
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 {
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 =
};
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 {
// 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;
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,
.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)?;
if response.msg == "ok" {
Ok(response)
} else {
- Err(LemmyError::from_message(&response.msg))
+ Err(LemmyErrorType::PictrsResponseError(response.msg))?
}
}
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)
if response.msg == "ok" {
Ok(())
} else {
- Err(LemmyError::from_message(&response.msg))
+ Err(LemmyErrorType::PictrsPurgeResponseError(response.msg))?
}
}
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)?
}
}
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,
) -> 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(())
}
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(())
}
.map(|cm| cm.moderator.id)
.unwrap_or(PersonId(0))
{
- return Err(LemmyError::from_message("not_top_mod"));
+ return Err(LemmyErrorType::NotTopMod)?;
}
Ok(())
}
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)]
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)]
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)]
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?;
) -> 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(())
}
) -> 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(())
.await
.is_ok();
if is_banned {
- Err(LemmyError::from_message("community_ban"))
+ Err(LemmyErrorType::BannedFromCommunity)?
} else {
Ok(())
}
) -> 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(())
}
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(())
}
.await
.is_ok();
if is_blocked {
- Err(LemmyError::from_message("person_block"))
+ Err(LemmyErrorType::PersonIsBlocked)?
} else {
Ok(())
}
#[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(())
}
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(())
}
/// 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(())
}
/// 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(())
}
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
if let Some(deny_reason) = registration.deny_reason {
let lang = get_interface_language(local_user_view);
- let registration_denied_message = format!("{}: {}", lang.registration_denied(), &deny_reason);
- return Err(LemmyError::from_message(®istration_denied_message));
+ let registration_denied_message = format!("{}: {}", lang.registration_denied(), deny_reason);
+ return Err(LemmyErrorType::RegistrationDenied(
+ registration_denied_message,
+ ))?;
} else {
- return Err(LemmyError::from_message("registration_application_pending"));
+ return Err(LemmyErrorType::RegistrationApplicationIsPending)?;
}
}
Ok(())
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(())
}
// 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?;
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)]
// 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
// 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)?;
}
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;
&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);
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 {
&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
&PersonMentionUpdateForm { read: Some(true) },
)
.await
- .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_person_mentions"))?;
+ .with_lemmy_type(LemmyErrorType::CouldntUpdatePersonMentions)?;
}
}
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(())
}
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 {
// 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(
// 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
&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?;
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 {
&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 {
};
use lemmy_db_views::structs::CommentView;
use lemmy_utils::{
- error::LemmyError,
+ error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{
mention::scrape_text_for_mentions,
slurs::remove_slurs,
// 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;
.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();
};
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},
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
)?;
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
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 {
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 {
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;
// 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?;
}
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 {
.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
}
},
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 {
.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);
};
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},
};
.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;
// 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?;
}
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
}
};
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},
)
.await?;
if !is_mod {
- return Err(LemmyError::from_message("only_mods_can_post_in_community"));
+ return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?;
}
}
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();
&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;
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?;
{
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 {
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 {
// 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(
// 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
};
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 {
} 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
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;
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
};
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?;
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},
// 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
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,
};
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},
};
.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();
.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?;
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 {
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
.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 {
};
use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::{
- error::LemmyError,
+ error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{slurs::remove_slurs, validation::is_valid_body_field},
};
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
.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?;
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::{
- error::{LemmyError, LemmyResult},
+ error::{LemmyError, LemmyErrorType, LemmyResult},
utils::{
slurs::{check_slurs, check_slurs_opt},
validation::{
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...
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>,
),
(
"CreateSite name matches LocalSite slur filter",
- "slurs",
+ LemmyErrorType::Slurs,
&generate_local_site(
false,
Some(String::from("(foo|bar)")),
),
(
"CreateSite name matches new slur filter",
- "slurs",
+ LemmyErrorType::Slurs,
&generate_local_site(
false,
Some(String::from("(foo|bar)")),
),
(
"CreateSite listing type is Subscribed, which is invalid",
- "invalid_default_post_listing_type",
+ LemmyErrorType::InvalidDefaultPostListingType,
&generate_local_site(
false,
None::<String>,
),
(
"CreateSite is both private and federated",
- "cant_enable_private_instance_and_federation_together",
+ LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site(
false,
None::<String>,
),
(
"LocalSite is private, but CreateSite also makes it federated",
- "cant_enable_private_instance_and_federation_together",
+ LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site(
false,
None::<String>,
),
(
"CreateSite requires application, but neither it nor LocalSite has an application question",
- "application_question_required",
+ LemmyErrorType::ApplicationQuestionRequired,
&generate_local_site(
false,
None::<String>,
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,
}
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
use lemmy_db_schema::{ListingType, RegistrationMode};
-use lemmy_utils::error::{LemmyError, LemmyResult};
+use lemmy_utils::error::{LemmyErrorType, LemmyResult};
mod create;
mod read;
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(())
}
if registration_mode == RegistrationMode::RequireApplication
&& (has_no_question || is_nullifying_question)
{
- Err(LemmyError::from_message("application_question_required"))
+ Err(LemmyErrorType::ApplicationQuestionRequired)?
} else {
Ok(())
}
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 {
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,
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::{
- error::{LemmyError, LemmyResult},
+ error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::{
slurs::check_slurs_opt,
validation::{
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
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();
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,
),
(
"EditSite name matches new slur filter",
- "slurs",
+ LemmyErrorType::Slurs,
&generate_local_site(
Some(String::from("(foo|bar)")),
true,
),
(
"EditSite listing type is Subscribed, which is invalid",
- "invalid_default_post_listing_type",
+ LemmyErrorType::InvalidDefaultPostListingType,
&generate_local_site(
None::<String>,
true,
),
(
"EditSite is both private and federated",
- "cant_enable_private_instance_and_federation_together",
+ LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site(
None::<String>,
true,
),
(
"LocalSite is private, but EditSite also makes it federated",
- "cant_enable_private_instance_and_federation_together",
+ LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether,
&generate_local_site(
None::<String>,
true,
),
(
"EditSite requires application, but neither it nor LocalSite has an application question",
- "application_question_required",
+ LemmyErrorType::ApplicationQuestionRequired,
&generate_local_site(
None::<String>,
true,
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(_) => {
}
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
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,
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 {
)
.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)?;
}
}
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)?;
}
}
// 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.
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 {
)
.unwrap_or(false);
if !valid {
- return Err(LemmyError::from_message("password_incorrect"));
+ return Err(LemmyErrorType::IncorrectLogin)?;
}
Ok(DeleteAccountResponse {})
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;
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();
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
},
traits::{Crud, Likeable},
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorType};
use url::Url;
#[async_trait::async_trait]
// 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 => {
},
traits::Crud,
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorType};
use url::Url;
#[async_trait::async_trait]
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,
},
traits::Crud,
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorType};
use url::Url;
#[async_trait::async_trait]
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,
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;
) -> 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(())
}
) -> 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;
.await
.is_ok();
if is_banned {
- return Err(LemmyError::from_message("Person is banned from community"));
+ return Err(LemmyErrorType::PersonIsBannedFromCommunity)?;
}
Ok(())
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(())
}
{
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(())
}
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(
.build()
.list()
.await
- .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?;
+ .with_lemmy_type(LemmyErrorType::CouldntGetComments)?;
Ok(Json(GetCommentsResponse { comments }))
}
};
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(
.build()
.list()
.await
- .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?;
+ .with_lemmy_type(LemmyErrorType::CouldntGetPosts)?;
Ok(Json(GetPostsResponse { posts }))
}
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(
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)?;
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
}
};
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?;
};
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(
) -> 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;
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)?;
}
}
};
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(
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(
};
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;
Some('!') => SearchableObjects::Community(
webfinger_resolve_actor::<LemmyContext, ApubCommunity>(identifier, context).await?,
),
- _ => return Err(LemmyError::from_message("invalid query")),
+ _ => return Err(LemmyErrorType::InvalidQuery)?,
}
}
})
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)]
.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)
.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)
.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)
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;
}
fn err_object_not_local() -> LemmyError {
- LemmyError::from_message("Object not local, fetch it from original instance")
+ LemmyErrorType::ObjectNotLocal.into()
}
#[derive(Deserialize)]
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;
}
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() {
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(())
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;
verify_person_in_community(¬e.attributed_to, &community, context).await?;
let (post, _) = note.get_parents(context).await?;
if post.locked {
- return Err(LemmyError::from_message("Post is locked"));
+ return Err(LemmyErrorType::PostIsLocked)?;
}
Ok(())
}
traits::Crud,
};
use lemmy_utils::{
- error::LemmyError,
+ error::{LemmyError, LemmyErrorType},
utils::{markdown::markdown_to_html, time::convert_datetime},
};
use std::ops::Deref;
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(())
}
};
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;
match value {
1 => Ok(VoteType::Like),
-1 => Ok(VoteType::Dislike),
- _ => Err(LemmyError::from_message("invalid vote value")),
+ _ => Err(LemmyErrorType::InvalidVoteValue.into()),
}
}
}
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;
.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()),
}
}
}
break c;
}
} else {
- return Err(LemmyError::from_message("No community found in cc"));
+ return Err(LemmyErrorType::NoCommunityFoundInCc)?;
}
}
}
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?
}
AsyncPgConnection,
RunQueryDsl,
};
-use lemmy_utils::error::LemmyError;
+use lemmy_utils::error::{LemmyError, LemmyErrorType};
use tokio::sync::OnceCell;
pub const UNDETERMINED_ID: LanguageId = LanguageId(0);
if is_allowed {
Ok(())
} else {
- Err(LemmyError::from_message("language_not_allowed"))
+ Err(LemmyErrorType::LanguageNotAllowed)?
}
} else {
Ok(())
};
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::{
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),
}
}
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),
}
}
path = "src/lib.rs"
doctest = false
+[features]
+full = ["ts-rs"]
+
[dependencies]
regex = { workspace = true }
chrono = { workspace = true }
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]
-use crate::{error::LemmyError, settings::structs::Settings};
+use crate::{
+ error::{LemmyError, LemmyErrorExt, LemmyErrorType},
+ settings::structs::Settings,
+};
use html2text;
use lettre::{
message::{Mailbox, MultiPart},
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)
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(())
}
+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(),
}
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()
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
}
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")
}
}
}
+
+#[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}");
+ });
+ }
+}
-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};
} else {
let (http_req, _) = req.into_parts();
Ok(ServiceResponse::from_err(
- LemmyError::from_message("rate_limit_error"),
+ LemmyError::from(LemmyErrorType::RateLimitError),
http_req,
))
}
-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 {
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(())
}
-use crate::error::{LemmyError, LemmyResult};
+use crate::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::{Regex, RegexBuilder};
&& VALID_ACTOR_NAME_REGEX.is_match(name)
&& !has_newline(name);
if !check {
- Err(LemmyError::from_message("invalid_name"))
+ Err(LemmyErrorType::InvalidName.into())
} else {
Ok(())
}
&& name.chars().count() <= actor_name_max_length
&& !has_newline(name);
if !check {
- Err(LemmyError::from_message("invalid_username"))
+ Err(LemmyErrorType::InvalidDisplayName.into())
} else {
Ok(())
}
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(())
}
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(())
}
};
if !check {
- Err(LemmyError::from_message("invalid_body_field"))
+ Err(LemmyErrorType::InvalidBodyField.into())
} else {
Ok(())
}
}
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.
name,
SITE_NAME_MIN_LENGTH,
SITE_NAME_MAX_LENGTH,
- String::from("site_name_required"),
- String::from("site_name_length_overflow"),
+ LemmyErrorType::SiteNameRequired,
+ LemmyErrorType::SiteNameLengthOverflow,
)
}
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(())
}
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(())
}
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))
// 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());
}
}
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,
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(
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(())
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(())
#[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;
&(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| {
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
invalid_result.is_err()
&& invalid_result
.unwrap_err()
- .message
- .eq(&Some(String::from("bio_length_overflow")))
+ .error_type
+ .eq(&Some(LemmyErrorType::BioLengthOverflow))
);
}
invalid_result.is_err()
&& invalid_result
.unwrap_err()
- .message
- .eq(&Some(String::from("site_description_length_overflow")))
+ .error_type
+ .eq(&Some(LemmyErrorType::SiteDescriptionLengthOverflow))
);
}
#[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