Removing a few expects from production and test code. (#5193)

* Removing a few expects from production and test code.

- Fixes #5192

* Using if let filter for admin emails.

* Fixing unused error.

* Adding expect_used = deny to clippy lints.

* Update src/lib.rs

Co-authored-by: Nutomic <me@nutomic.com>

* Update crates/utils/src/settings/structs.rs

Co-authored-by: Nutomic <me@nutomic.com>

* Update crates/utils/src/settings/mod.rs

Co-authored-by: Nutomic <me@nutomic.com>

* Some more cleanup.

* Fix clippy

---------

Co-authored-by: Nutomic <me@nutomic.com>
This commit is contained in:
Dessalines 2024-11-15 08:18:52 -05:00 committed by GitHub
parent 231cce9350
commit fa4825b524
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 252 additions and 173 deletions

View file

@ -79,6 +79,7 @@ unused_self = "deny"
unwrap_used = "deny"
unimplemented = "deny"
unused_async = "deny"
expect_used = "deny"
[workspace.dependencies]
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }

View file

@ -10,7 +10,7 @@ use lemmy_db_schema::source::{
login_token::LoginToken,
password_reset_request::PasswordResetRequest,
};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn change_password_after_reset(
@ -32,9 +32,7 @@ pub async fn change_password_after_reset(
// Update the user with the new password
let password = data.password.clone();
LocalUser::update_password(&mut context.pool(), local_user_id, &password)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;
LocalUser::update_password(&mut context.pool(), local_user_id, &password).await?;
LoginToken::invalidate_all(&mut context.pool(), local_user_id).await?;

View file

@ -12,6 +12,7 @@ use captcha::{gen, Difficulty};
use lemmy_api_common::{
context::LemmyContext,
person::{CaptchaResponse, GetCaptchaResponse},
LemmyErrorType,
};
use lemmy_db_schema::source::{
captcha_answer::{CaptchaAnswer, CaptchaAnswerForm},
@ -37,7 +38,9 @@ pub async fn get_captcha(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
let answer = captcha.chars_as_string();
let png = captcha.as_base64().expect("failed to generate captcha");
let png = captcha
.as_base64()
.ok_or(LemmyErrorType::CouldntCreateImageCaptcha)?;
let wav = captcha_as_wav_base64(&captcha)?;

View file

@ -55,6 +55,7 @@ impl LemmyContext {
/// Initialize a context for use in tests which blocks federation network calls.
///
/// Do not use this in production code.
#[allow(clippy::expect_used)]
pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> {
// call this to run migrations
let pool = build_db_pool_for_tests();

View file

@ -521,7 +521,7 @@ mod tests {
// root relative url
let html_bytes = b"<!DOCTYPE html><html><head><meta property='og:image' content='/image.jpg'></head><body></body></html>";
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
let metadata = extract_opengraph_data(html_bytes, &url)?;
assert_eq!(
metadata.image,
Some(Url::parse("https://example.com/image.jpg")?.into())
@ -529,7 +529,7 @@ mod tests {
// base relative url
let html_bytes = b"<!DOCTYPE html><html><head><meta property='og:image' content='image.jpg'></head><body></body></html>";
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
let metadata = extract_opengraph_data(html_bytes, &url)?;
assert_eq!(
metadata.image,
Some(Url::parse("https://example.com/one/image.jpg")?.into())
@ -537,7 +537,7 @@ mod tests {
// absolute url
let html_bytes = b"<!DOCTYPE html><html><head><meta property='og:image' content='https://cdn.host.com/image.jpg'></head><body></body></html>";
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
let metadata = extract_opengraph_data(html_bytes, &url)?;
assert_eq!(
metadata.image,
Some(Url::parse("https://cdn.host.com/image.jpg")?.into())
@ -545,7 +545,7 @@ mod tests {
// protocol relative url
let html_bytes = b"<!DOCTYPE html><html><head><meta property='og:image' content='//example.com/image.jpg'></head><body></body></html>";
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
let metadata = extract_opengraph_data(html_bytes, &url)?;
assert_eq!(
metadata.image,
Some(Url::parse("https://example.com/image.jpg")?.into())

View file

@ -514,6 +514,7 @@ pub struct ReadableFederationState {
next_retry: Option<DateTime<Utc>>,
}
#[allow(clippy::expect_used)]
impl From<FederationQueueState> for ReadableFederationState {
fn from(internal_state: FederationQueueState) -> Self {
ReadableFederationState {

View file

@ -442,7 +442,11 @@ pub async fn send_password_reset_email(
// Generate a random token
let token = uuid::Uuid::new_v4().to_string();
let email = &user.local_user.email.clone().expect("email");
let email = &user
.local_user
.email
.clone()
.ok_or(LemmyErrorType::EmailRequired)?;
let lang = get_interface_language(user);
let subject = &lang.password_reset_subject(&user.person.name);
let protocol_and_hostname = settings.get_protocol_and_hostname();
@ -492,6 +496,7 @@ pub fn get_interface_language_from_settings(user: &LocalUserView) -> Lang {
lang_str_to_lang(&user.local_user.interface_language)
}
#[allow(clippy::expect_used)]
fn lang_str_to_lang(lang: &str) -> Lang {
let lang_id = LanguageId::new(lang);
Lang::from_language_id(&lang_id).unwrap_or_else(|| {
@ -518,11 +523,11 @@ pub fn local_site_rate_limit_to_rate_limit_config(
})
}
pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<Regex> {
pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<LemmyResult<Regex>> {
build_slur_regex(local_site.slur_filter_regex.as_deref())
}
pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<Regex> {
pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<LemmyResult<Regex>> {
local_site
.as_ref()
.map(local_site_to_slur_regex)
@ -557,7 +562,11 @@ pub async fn send_application_approved_email(
user: &LocalUserView,
settings: &Settings,
) -> LemmyResult<()> {
let email = &user.local_user.email.clone().expect("email");
let email = &user
.local_user
.email
.clone()
.ok_or(LemmyErrorType::EmailRequired)?;
let lang = get_interface_language(user);
let subject = lang.registration_approved_subject(&user.person.actor_id);
let body = lang.registration_approved_body(&settings.hostname);
@ -579,7 +588,11 @@ pub async fn send_new_applicant_email_to_admins(
);
for admin in &admins {
let email = &admin.local_user.email.clone().expect("email");
let email = &admin
.local_user
.email
.clone()
.ok_or(LemmyErrorType::EmailRequired)?;
let lang = get_interface_language_from_settings(admin);
let subject = lang.new_application_subject(&settings.hostname, applicant_username);
let body = lang.new_application_body(applications_link);
@ -601,12 +614,14 @@ pub async fn send_new_report_email_to_admins(
let reports_link = &format!("{}/reports", settings.get_protocol_and_hostname(),);
for admin in &admins {
let email = &admin.local_user.email.clone().expect("email");
if let Some(email) = &admin.local_user.email {
let lang = get_interface_language_from_settings(admin);
let subject = lang.new_report_subject(&settings.hostname, reported_username, reporter_username);
let subject =
lang.new_report_subject(&settings.hostname, reported_username, reporter_username);
let body = lang.new_report_body(reports_link);
send_email(&subject, email, &admin.person.name, &body, settings).await?;
}
}
Ok(())
}
@ -1030,7 +1045,7 @@ pub fn check_conflicting_like_filters(
pub async fn process_markdown(
text: &str,
slur_regex: &Option<Regex>,
slur_regex: &Option<LemmyResult<Regex>>,
url_blocklist: &RegexSet,
context: &LemmyContext,
) -> LemmyResult<String> {
@ -1062,7 +1077,7 @@ pub async fn process_markdown(
pub async fn process_markdown_opt(
text: &Option<String>,
slur_regex: &Option<Regex>,
slur_regex: &Option<LemmyResult<Regex>>,
url_blocklist: &RegexSet,
context: &LemmyContext,
) -> LemmyResult<Option<String>> {

View file

@ -162,7 +162,7 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
.slur_filter_regex
.as_deref()
.or(local_site.slur_filter_regex.as_deref()),
)?;
);
site_name_length_check(&create_site.name)?;
check_slurs(&create_site.name, &slur_regex)?;

View file

@ -211,7 +211,7 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
.slur_filter_regex
.as_deref()
.or(local_site.slur_filter_regex.as_deref()),
)?;
);
if let Some(name) = &edit_site.name {
// The name doesn't need to be updated, but if provided it cannot be blanked out...

View file

@ -148,15 +148,16 @@ pub async fn register(
let inserted_local_user = create_local_user(&context, language_tags, &local_user_form).await?;
if local_site.site_setup && require_registration_application {
if let Some(answer) = data.answer.clone() {
// Create the registration application
let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id,
// We already made sure answer was not null above
answer: data.answer.clone().expect("must have an answer"),
answer,
};
RegistrationApplication::create(&mut context.pool(), &form).await?;
}
}
// Email the admins, only if email verification is not required
if local_site.application_email_admins && !local_site.require_email_verification {
@ -373,18 +374,20 @@ pub async fn authenticate_with_oauth(
&& !local_user.accepted_application
&& !local_user.admin
{
if let Some(answer) = data.answer.clone() {
// Create the registration application
RegistrationApplication::create(
&mut context.pool(),
&RegistrationApplicationInsertForm {
local_user_id: local_user.id,
answer: data.answer.clone().expect("must have an answer"),
answer,
},
)
.await?;
login_response.registration_created = true;
}
}
// Check email is verified when required
login_response.verify_email_sent =
@ -483,7 +486,7 @@ async fn send_verification_email_if_required(
&local_user
.email
.clone()
.expect("invalid verification email"),
.ok_or(LemmyErrorType::EmailRequired)?,
&mut context.pool(),
context.settings(),
)

View file

@ -130,7 +130,7 @@ impl AnnounceActivity {
actor: c.actor.clone().into_inner(),
other: serde_json::to_value(c.object)?
.as_object()
.expect("is object")
.ok_or(FederationError::Unreachable)?
.clone(),
};
let announce_compat = AnnounceActivity::new(announcable_page, community, context)?;

View file

@ -5,7 +5,7 @@ use activitypub_federation::{
};
use diesel::NotFound;
use itertools::Itertools;
use lemmy_api_common::context::LemmyContext;
use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
use lemmy_db_schema::traits::ApubActor;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyResult};
@ -42,7 +42,7 @@ where
let (name, domain) = identifier
.splitn(2, '@')
.collect_tuple()
.expect("invalid query");
.ok_or(LemmyErrorType::InvalidUrl)?;
let actor = DbActor::read_from_name_and_domain(&mut context.pool(), name, domain)
.await
.ok()

View file

@ -50,7 +50,8 @@ impl UrlVerifier for VerifyUrlData {
async fn verify(&self, url: &Url) -> Result<(), ActivityPubError> {
let local_site_data = local_site_data_cached(&mut (&self.0).into())
.await
.expect("read local site data");
.map_err(|e| ActivityPubError::Other(format!("Cant read local site data: {e}")))?;
use FederationError::*;
check_apub_id_valid(url, &local_site_data).map_err(|err| match err {
LemmyError {
@ -176,10 +177,7 @@ pub(crate) async fn check_apub_id_valid_with_strictness(
.domain()
.ok_or(FederationError::UrlWithoutDomain)?
.to_string();
let local_instance = context
.settings()
.get_hostname_without_port()
.expect("local hostname is valid");
let local_instance = context.settings().get_hostname_without_port()?;
if domain == local_instance {
return Ok(());
}
@ -196,10 +194,7 @@ pub(crate) async fn check_apub_id_valid_with_strictness(
.iter()
.map(|i| i.domain.clone())
.collect::<Vec<String>>();
let local_instance = context
.settings()
.get_hostname_without_port()
.expect("local hostname is valid");
let local_instance = context.settings().get_hostname_without_port()?;
allowed_and_local.push(local_instance);
let domain = apub_id

View file

@ -372,6 +372,7 @@ async fn convert_update_languages(
}
/// If all languages are returned, return empty vec instead
#[allow(clippy::expect_used)]
async fn convert_read_languages(
conn: &mut AsyncPgConnection,
language_ids: Vec<LanguageId>,
@ -510,7 +511,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_user_languages() -> Result<(), Error> {
async fn test_user_languages() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
@ -543,7 +544,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_community_languages() -> Result<(), Error> {
async fn test_community_languages() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let (site, instance) = create_test_site(pool).await?;

View file

@ -57,11 +57,12 @@ mod tests {
source::captcha_answer::{CaptchaAnswer, CaptchaAnswerForm, CheckCaptchaAnswer},
utils::build_db_pool_for_tests,
};
use lemmy_utils::error::LemmyResult;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_captcha_happy_path() {
async fn test_captcha_happy_path() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
@ -71,8 +72,7 @@ mod tests {
answer: "XYZ".to_string(),
},
)
.await
.expect("should not fail to insert captcha");
.await?;
let result = CaptchaAnswer::check_captcha(
pool,
@ -84,11 +84,12 @@ mod tests {
.await;
assert!(result.is_ok());
Ok(())
}
#[tokio::test]
#[serial]
async fn test_captcha_repeat_answer_fails() {
async fn test_captcha_repeat_answer_fails() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
@ -98,8 +99,7 @@ mod tests {
answer: "XYZ".to_string(),
},
)
.await
.expect("should not fail to insert captcha");
.await?;
let _result = CaptchaAnswer::check_captcha(
pool,
@ -120,5 +120,7 @@ mod tests {
.await;
assert!(result_repeat.is_err());
Ok(())
}
}

View file

@ -26,19 +26,19 @@ use diesel::{
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
impl LocalUser {
pub async fn create(
pool: &mut DbPool<'_>,
form: &LocalUserInsertForm,
languages: Vec<LanguageId>,
) -> Result<LocalUser, Error> {
) -> LemmyResult<LocalUser> {
let conn = &mut get_conn(pool).await?;
let mut form_with_encrypted_password = form.clone();
if let Some(password_encrypted) = &form.password_encrypted {
let password_hash = hash(password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
let password_hash = hash(password_encrypted, DEFAULT_COST)?;
form_with_encrypted_password.password_encrypted = Some(password_hash);
}
@ -84,14 +84,15 @@ impl LocalUser {
pool: &mut DbPool<'_>,
local_user_id: LocalUserId,
new_password: &str,
) -> Result<Self, Error> {
) -> LemmyResult<Self> {
let conn = &mut get_conn(pool).await?;
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
let password_hash = hash(new_password, DEFAULT_COST)?;
diesel::update(local_user::table.find(local_user_id))
.set((local_user::password_encrypted.eq(password_hash),))
.get_result::<Self>(conn)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)
}
pub async fn set_all_users_email_verified(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {

View file

@ -115,9 +115,7 @@ impl Post {
.filter(post::local.eq(true))
.filter(post::deleted.eq(false))
.filter(post::removed.eq(false))
.filter(
post::published.ge(Utc::now().naive_utc() - SITEMAP_DAYS.expect("TimeDelta out of bounds")),
)
.filter(post::published.ge(Utc::now().naive_utc() - SITEMAP_DAYS))
.order(post::published.desc())
.limit(SITEMAP_LIMIT)
.load::<(DbUrl, chrono::DateTime<Utc>)>(conn)

View file

@ -47,6 +47,7 @@ pub mod tagline;
/// This is necessary so they can be successfully deserialized from API responses, even though the
/// value is not sent by Lemmy. Necessary for crates which rely on Rust API such as
/// lemmy-stats-crawler.
#[allow(clippy::expect_used)]
fn placeholder_apub_url() -> DbUrl {
DbUrl(Box::new(
Url::parse("http://example.com").expect("parse placeholder url"),

View file

@ -68,7 +68,7 @@ use url::Url;
const FETCH_LIMIT_DEFAULT: i64 = 10;
pub const FETCH_LIMIT_MAX: i64 = 50;
pub const SITEMAP_LIMIT: i64 = 50000;
pub const SITEMAP_DAYS: Option<TimeDelta> = TimeDelta::try_days(31);
pub const SITEMAP_DAYS: TimeDelta = TimeDelta::days(31);
pub const RANK_DEFAULT: f64 = 0.0001;
/// Some connection options to speed up queries
@ -360,8 +360,8 @@ pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult<Option<DbUrl>> {
}
/// Sets a few additional config options necessary for starting lemmy
fn build_config_options_uri_segment(config: &str) -> String {
let mut url = Url::parse(config).expect("Couldn't parse postgres connection URI");
fn build_config_options_uri_segment(config: &str) -> LemmyResult<String> {
let mut url = Url::parse(config)?;
// Set `lemmy.protocol_and_hostname` so triggers can use it
let lemmy_protocol_and_hostname_option =
@ -377,7 +377,7 @@ fn build_config_options_uri_segment(config: &str) -> String {
.join(" ");
url.set_query(Some(&format!("options={options_segments}")));
url.into()
Ok(url.into())
}
fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
@ -385,8 +385,11 @@ fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConne
/// Use a once_lock to create the postgres connection config, since this config never changes
static POSTGRES_CONFIG_WITH_OPTIONS: OnceLock<String> = OnceLock::new();
let config =
POSTGRES_CONFIG_WITH_OPTIONS.get_or_init(|| build_config_options_uri_segment(config));
let config = POSTGRES_CONFIG_WITH_OPTIONS.get_or_init(|| {
build_config_options_uri_segment(config)
.inspect_err(|e| error!("Couldn't parse postgres connection URI: {e}"))
.unwrap_or_default()
});
// We only support TLS with sslmode=require currently
let conn = if config.contains("sslmode=require") {
@ -495,6 +498,7 @@ pub fn build_db_pool() -> LemmyResult<ActualDbPool> {
Ok(pool)
}
#[allow(clippy::expect_used)]
pub fn build_db_pool_for_tests() -> ActualDbPool {
build_db_pool().expect("db pool missing")
}
@ -511,6 +515,7 @@ pub fn post_to_comment_sort_type(sort: PostSortType) -> CommentSortType {
}
}
#[allow(clippy::expect_used)]
static EMAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
.expect("compile email regex")

View file

@ -20,7 +20,7 @@ use lemmy_db_schema::{
ReadFn,
},
};
use lemmy_utils::error::{LemmyError, LemmyErrorType};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
use std::future::{ready, Ready};
enum ReadBy<'a> {
@ -146,7 +146,7 @@ impl LocalUserView {
name: &str,
bio: &str,
admin: bool,
) -> Result<Self, Error> {
) -> LemmyResult<Self> {
let instance_id = Instance::read_or_create(pool, "example.com".to_string())
.await?
.id;
@ -163,7 +163,9 @@ impl LocalUserView {
};
let local_user = LocalUser::create(pool, &user_form, vec![]).await?;
LocalUserView::read(pool, local_user.id).await
LocalUserView::read(pool, local_user.id)
.await
.with_lemmy_type(LemmyErrorType::NotFound)
}
}

View file

@ -501,6 +501,7 @@ pub struct PostQuery<'a> {
}
impl<'a> PostQuery<'a> {
#[allow(clippy::expect_used)]
async fn prefetch_upper_bound_for_page_before(
&self,
site: &Site,

View file

@ -286,7 +286,7 @@ mod tests {
CommunityVisibility,
SubscribedType,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use serial_test::serial;
use url::Url;
@ -495,7 +495,7 @@ mod tests {
};
let communities = query.list(&data.site, pool).await?;
for (i, c) in communities.iter().enumerate().skip(1) {
let prev = communities.get(i - 1).expect("No previous community?");
let prev = communities.get(i - 1).ok_or(LemmyErrorType::NotFound)?;
assert!(c.community.title.cmp(&prev.community.title).is_ge());
}
@ -505,7 +505,7 @@ mod tests {
};
let communities = query.list(&data.site, pool).await?;
for (i, c) in communities.iter().enumerate().skip(1) {
let prev = communities.get(i - 1).expect("No previous community?");
let prev = communities.get(i - 1).ok_or(LemmyErrorType::NotFound)?;
assert!(c.community.title.cmp(&prev.community.title).is_le());
}

View file

@ -23,6 +23,7 @@ use std::{
/// currently fairly high because of the current structure of storing inboxes for every person, not
/// having a separate list of shared_inboxes, and the architecture of having every instance queue be
/// fully separate. (see https://github.com/LemmyNet/lemmy/issues/3958)
#[allow(clippy::expect_used)]
static FOLLOW_ADDITIONS_RECHECK_DELAY: LazyLock<chrono::TimeDelta> = LazyLock::new(|| {
if *LEMMY_TEST_FAST_FEDERATION {
chrono::TimeDelta::try_seconds(1).expect("TimeDelta out of bounds")
@ -33,6 +34,7 @@ static FOLLOW_ADDITIONS_RECHECK_DELAY: LazyLock<chrono::TimeDelta> = LazyLock::n
/// The same as FOLLOW_ADDITIONS_RECHECK_DELAY, but triggering when the last person on an instance
/// unfollows a specific remote community. This is expected to happen pretty rarely and updating it
/// in a timely manner is not too important.
#[allow(clippy::expect_used)]
static FOLLOW_REMOVALS_RECHECK_DELAY: LazyLock<chrono::TimeDelta> =
LazyLock::new(|| chrono::TimeDelta::try_hours(1).expect("TimeDelta out of bounds"));
@ -472,6 +474,7 @@ mod tests {
Ok(())
}
#[allow(clippy::expect_used)]
#[tokio::test]
async fn test_update_communities() -> LemmyResult<()> {
let mut collector = setup_collector();

View file

@ -331,7 +331,7 @@ impl InstanceWorker {
self.state.last_successful_published_time = next.published;
}
let save_state_every = chrono::Duration::from_std(SAVE_STATE_EVERY_TIME).expect("not negative");
let save_state_every = chrono::Duration::from_std(SAVE_STATE_EVERY_TIME)?;
if force_write || (Utc::now() - self.last_state_insert) > save_state_every {
self.save_and_send_state().await?;
}

View file

@ -312,12 +312,16 @@ where
}
// TODO: remove these conversions after actix-web upgrades to http 1.0
#[allow(clippy::expect_used)]
fn convert_status(status: http::StatusCode) -> StatusCode {
StatusCode::from_u16(status.as_u16()).expect("status can be converted")
}
#[allow(clippy::expect_used)]
fn convert_method(method: &Method) -> http::Method {
http::Method::from_bytes(method.as_str().as_bytes()).expect("method can be converted")
}
fn convert_header<'a>(name: &'a http::HeaderName, value: &'a HeaderValue) -> (&'a str, &'a [u8]) {
(name.as_str(), value.as_bytes())
}

View file

@ -3,13 +3,16 @@ use activitypub_federation::{
fetch::webfinger::{extract_webfinger_name, Webfinger, WebfingerLink, WEBFINGER_CONTENT_TYPE},
};
use actix_web::{web, web::Query, HttpResponse};
use lemmy_api_common::context::LemmyContext;
use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
use lemmy_db_schema::{
source::{community::Community, person::Person},
traits::ApubActor,
CommunityVisibility,
};
use lemmy_utils::{cache_header::cache_3days, error::LemmyResult};
use lemmy_utils::{
cache_header::cache_3days,
error::{LemmyErrorExt, LemmyResult},
};
use serde::Deserialize;
use std::collections::HashMap;
use url::Url;
@ -41,7 +44,7 @@ async fn get_webfinger_response(
let links = if name == context.settings().hostname {
// webfinger response for instance actor (required for mastodon authorized fetch)
let url = Url::parse(&context.settings().get_protocol_and_hostname())?;
vec![webfinger_link_for_actor(Some(url), "none", &context)]
vec![webfinger_link_for_actor(Some(url), "none", &context)?]
} else {
// webfinger response for user/community
let user_id: Option<Url> = Person::read_from_name(&mut context.pool(), name, false)
@ -65,8 +68,8 @@ async fn get_webfinger_response(
// Mastodon seems to prioritize the last webfinger item in case of duplicates. Put
// community last so that it gets prioritized. For Lemmy the order doesn't matter.
vec![
webfinger_link_for_actor(user_id, "Person", &context),
webfinger_link_for_actor(community_id, "Group", &context),
webfinger_link_for_actor(user_id, "Person", &context)?,
webfinger_link_for_actor(community_id, "Group", &context)?,
]
}
.into_iter()
@ -94,11 +97,11 @@ fn webfinger_link_for_actor(
url: Option<Url>,
kind: &str,
context: &LemmyContext,
) -> Vec<WebfingerLink> {
) -> LemmyResult<Vec<WebfingerLink>> {
if let Some(url) = url {
let type_key = "https://www.w3.org/ns/activitystreams#type"
.parse()
.expect("parse url");
.with_lemmy_type(LemmyErrorType::InvalidUrl)?;
let mut vec = vec![
WebfingerLink {
@ -128,8 +131,8 @@ fn webfinger_link_for_actor(
..Default::default()
});
}
vec
Ok(vec)
} else {
vec![]
Ok(vec![])
}
}

View file

@ -33,7 +33,7 @@ pub async fn send_email(
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
let email = *email_and_port
.first()
.ok_or(LemmyErrorType::MissingAnEmail)?;
.ok_or(LemmyErrorType::EmailRequired)?;
let port = email_and_port
.get(1)
.ok_or(LemmyErrorType::EmailSmtpServerNeedsAPort)?
@ -45,16 +45,20 @@ pub async fn send_email(
// use usize::MAX as the line wrap length, since lettre handles the wrapping for us
let plain_text = html2text::from_read(html.as_bytes(), usize::MAX);
let smtp_from_address = &email_config.smtp_from_address;
let email = Message::builder()
.from(
email_config
.smtp_from_address
smtp_from_address
.parse()
.expect("email from address isn't valid"),
.with_lemmy_type(LemmyErrorType::InvalidEmailAddress(
smtp_from_address.into(),
))?,
)
.to(Mailbox::new(
Some(to_username.to_string()),
Address::from_str(to_email).expect("email to address isn't valid"),
Address::from_str(to_email)
.with_lemmy_type(LemmyErrorType::InvalidEmailAddress(to_email.into()))?,
))
.message_id(Some(format!("<{}@{}>", Uuid::new_v4(), settings.hostname)))
.subject(subject)
@ -62,7 +66,7 @@ pub async fn send_email(
plain_text,
html.to_string(),
))
.expect("email built incorrectly");
.with_lemmy_type(LemmyErrorType::EmailSendFailed)?;
// don't worry about 'dangeous'. it's just that leaving it at the default configuration
// is bad.

View file

@ -73,7 +73,7 @@ pub enum LemmyErrorType {
NoEmailSetup,
LocalSiteNotSetup,
EmailSmtpServerNeedsAPort,
MissingAnEmail,
InvalidEmailAddress(String),
RateLimitError,
InvalidName,
InvalidDisplayName,
@ -129,6 +129,7 @@ pub enum LemmyErrorType {
InvalidRegex,
CaptchaIncorrect,
CouldntCreateAudioCaptcha,
CouldntCreateImageCaptcha,
InvalidUrlScheme,
CouldntSendWebmention,
ContradictingFilters,
@ -185,6 +186,7 @@ pub enum FederationError {
CantDeleteSite,
ObjectIsNotPublic,
ObjectIsNotPrivate,
Unreachable,
}
cfg_if! {

View file

@ -29,6 +29,7 @@ pub struct RateLimitCell {
state: Arc<Mutex<RateLimitState>>,
}
#[allow(clippy::expect_used)]
impl RateLimitCell {
pub fn new(rate_limit_config: EnumMap<ActionType, BucketConfig>) -> Self {
let state = Arc::new(Mutex::new(RateLimitState::new(rate_limit_config)));
@ -133,6 +134,7 @@ pub struct RateLimitedMiddleware<S> {
service: Rc<S>,
}
#[allow(clippy::expect_used)]
impl RateLimitChecker {
/// Returns true if the request passed the rate limit, false if it failed and should be rejected.
pub fn check(self, ip_addr: IpAddr) -> bool {

View file

@ -18,6 +18,7 @@ pub struct InstantSecs {
secs: u32,
}
#[allow(clippy::expect_used)]
impl InstantSecs {
pub fn now() -> Self {
InstantSecs {

View file

@ -10,6 +10,7 @@ where
}
#[tracing::instrument(skip_all)]
#[allow(clippy::expect_used)]
async fn retry_custom<F, Fut, T>(f: F) -> Result<T, reqwest_middleware::Error>
where
F: Fn() -> Fut,

View file

@ -11,19 +11,20 @@ pub fn jsonify_plain_text_errors<BODY>(
return Ok(ErrorHandlerResponse::Response(res.map_into_left_body()));
}
// We're assuming that any LemmyError is already in JSON format, so we don't need to do anything
if maybe_error
.expect("http responses with 400-599 statuses should have an error object")
.as_error::<LemmyError>()
.is_some()
{
if let Some(maybe_error) = maybe_error {
if maybe_error.as_error::<LemmyError>().is_some() {
return Ok(ErrorHandlerResponse::Response(res.map_into_left_body()));
}
}
let (req, res) = res.into_parts();
let error = res
.error()
.expect("expected an error object in the response");
let response = HttpResponse::build(res.status()).json(LemmyErrorType::Unknown(error.to_string()));
let (req, res_parts) = res.into_parts();
let lemmy_err_type = if let Some(error) = res_parts.error() {
LemmyErrorType::Unknown(error.to_string())
} else {
LemmyErrorType::Unknown("couldnt build json".into())
};
let response = HttpResponse::build(res_parts.status()).json(lemmy_err_type);
let service_response = ServiceResponse::new(req, response);
Ok(ErrorHandlerResponse::Response(

View file

@ -3,6 +3,7 @@ use anyhow::{anyhow, Context};
use deser_hjson::from_str;
use regex::Regex;
use std::{env, fs, io::Error, sync::LazyLock};
use url::Url;
use urlencoding::encode;
pub mod structs;
@ -11,6 +12,7 @@ use structs::{DatabaseConnection, PictrsConfig, PictrsImageMode, Settings};
static DEFAULT_CONFIG_FILE: &str = "config/config.hjson";
#[allow(clippy::expect_used)]
pub static SETTINGS: LazyLock<Settings> = LazyLock::new(|| {
if env::var("LEMMY_INITIALIZE_WITH_DEFAULT_SETTINGS").is_ok() {
println!(
@ -23,6 +25,7 @@ pub static SETTINGS: LazyLock<Settings> = LazyLock::new(|| {
}
});
#[allow(clippy::expect_used)]
static WEBFINGER_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(&format!(
"^acct:([a-zA-Z0-9_]{{3,}})@{}$",
@ -128,3 +131,9 @@ impl PictrsConfig {
}
}
}
#[allow(clippy::expect_used)]
/// Necessary to avoid URL expect failures
fn pictrs_placeholder_url() -> Url {
Url::parse("http://localhost:8080").expect("parse pictrs url")
}

View file

@ -1,3 +1,4 @@
use super::pictrs_placeholder_url;
use doku::Document;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
@ -68,7 +69,7 @@ impl Settings {
#[serde(default, deny_unknown_fields)]
pub struct PictrsConfig {
/// Address where pictrs is available (for image hosting)
#[default(Url::parse("http://localhost:8080").expect("parse pictrs url"))]
#[default(pictrs_placeholder_url())]
#[doku(example = "http://localhost:8080")]
pub url: Url,

View file

@ -58,12 +58,14 @@ fn find_urls<T: NodeValue + UrlAndTitle>(src: &str) -> Vec<(usize, usize)> {
let mut links_offsets = vec![];
ast.walk(|node, _depth| {
if let Some(image) = node.cast::<T>() {
let (_, node_offset) = node.srcmap.expect("srcmap is none").get_byte_offsets();
if let Some(srcmap) = node.srcmap {
let (_, node_offset) = srcmap.get_byte_offsets();
let start_offset = node_offset - image.url_len() - 1 - image.title_len();
let end_offset = node_offset - 1;
links_offsets.push((start_offset, end_offset));
}
}
});
links_offsets
}

View file

@ -2,6 +2,7 @@ use itertools::Itertools;
use regex::Regex;
use std::sync::LazyLock;
#[allow(clippy::expect_used)]
static MENTIONS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._:-]+)").expect("compile regex")
});

View file

@ -1,8 +1,8 @@
use crate::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use regex::{Regex, RegexBuilder};
pub fn remove_slurs(test: &str, slur_regex: &Option<Regex>) -> String {
if let Some(slur_regex) = slur_regex {
pub fn remove_slurs(test: &str, slur_regex: &Option<LemmyResult<Regex>>) -> String {
if let Some(Ok(slur_regex)) = slur_regex {
slur_regex.replace_all(test, "*removed*").to_string()
} else {
test.to_string()
@ -11,9 +11,9 @@ pub fn remove_slurs(test: &str, slur_regex: &Option<Regex>) -> String {
pub(crate) fn slur_check<'a>(
test: &'a str,
slur_regex: &'a Option<Regex>,
slur_regex: &'a Option<LemmyResult<Regex>>,
) -> Result<(), Vec<&'a str>> {
if let Some(slur_regex) = slur_regex {
if let Some(Ok(slur_regex)) = slur_regex {
let mut matches: Vec<&str> = slur_regex.find_iter(test).map(|mat| mat.as_str()).collect();
// Unique
@ -30,16 +30,16 @@ pub(crate) fn slur_check<'a>(
}
}
pub fn build_slur_regex(regex_str: Option<&str>) -> Option<Regex> {
pub fn build_slur_regex(regex_str: Option<&str>) -> Option<LemmyResult<Regex>> {
regex_str.map(|slurs| {
RegexBuilder::new(slurs)
.case_insensitive(true)
.build()
.expect("compile regex")
.with_lemmy_type(LemmyErrorType::InvalidRegex)
})
}
pub fn check_slurs(text: &str, slur_regex: &Option<Regex>) -> LemmyResult<()> {
pub fn check_slurs(text: &str, slur_regex: &Option<LemmyResult<Regex>>) -> LemmyResult<()> {
if let Err(slurs) = slur_check(text, slur_regex) {
Err(anyhow::anyhow!("{}", slurs_vec_to_str(&slurs))).with_lemmy_type(LemmyErrorType::Slurs)
} else {
@ -47,7 +47,10 @@ pub fn check_slurs(text: &str, slur_regex: &Option<Regex>) -> LemmyResult<()> {
}
}
pub fn check_slurs_opt(text: &Option<String>, slur_regex: &Option<Regex>) -> LemmyResult<()> {
pub fn check_slurs_opt(
text: &Option<String>,
slur_regex: &Option<LemmyResult<Regex>>,
) -> LemmyResult<()> {
match text {
Some(t) => check_slurs(t, slur_regex),
None => Ok(()),
@ -64,7 +67,7 @@ pub(crate) fn slurs_vec_to_str(slurs: &[&str]) -> String {
mod test {
use crate::{
error::LemmyResult,
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str},
};
use pretty_assertions::assert_eq;
@ -72,7 +75,7 @@ mod test {
#[test]
fn test_slur_filter() -> LemmyResult<()> {
let slur_regex = Some(RegexBuilder::new(r"(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build()?);
let slur_regex = Some(RegexBuilder::new(r"(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().with_lemmy_type(LemmyErrorType::InvalidRegex));
let test =
"faggot test kike tranny cocksucker retardeds. Capitalized Niggerz. This is a bunch of other safe text.";
let slur_free = "No slurs here";

View file

@ -6,11 +6,13 @@ use std::sync::LazyLock;
use url::{ParseError, Url};
// From here: https://github.com/vector-im/element-android/blob/develop/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt#L35
#[allow(clippy::expect_used)]
static VALID_MATRIX_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^@[A-Za-z0-9\x21-\x39\x3B-\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$")
.expect("compile regex")
});
// taken from https://en.wikipedia.org/wiki/UTM_parameters
#[allow(clippy::expect_used)]
static URL_CLEANER: LazyLock<UrlCleaner> =
LazyLock::new(|| UrlCleaner::from_embedded_rules().expect("compile clearurls"));
const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"];
@ -88,6 +90,7 @@ pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyRes
// Only allow characters from a single alphabet per username. This avoids problems with lookalike
// characters like `o` which looks identical in Latin and Cyrillic, and can be used to imitate
// other users. Checks for additional alphabets can be added in the same way.
#[allow(clippy::expect_used)]
static VALID_ACTOR_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?:[a-zA-Z0-9_]+|[0-9_\p{Arabic}]+|[0-9_\p{Cyrillic}]+)$").expect("compile regex")
});
@ -218,17 +221,13 @@ fn min_length_check(item: &str, min_length: usize, min_msg: LemmyErrorType) -> L
}
/// Attempts to build a regex and check it for common errors before inserting into the DB.
pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult<Option<Regex>> {
regex_str_opt.map_or_else(
|| Ok(None::<Regex>),
|regex_str| {
if regex_str.is_empty() {
// If the proposed regex is empty, return as having no regex at all; this is the same
// behavior that happens downstream before the write to the database.
return Ok(None::<Regex>);
}
RegexBuilder::new(regex_str)
pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> Option<LemmyResult<Regex>> {
if let Some(regex) = regex_str_opt {
if regex.is_empty() {
None
} else {
Some(
RegexBuilder::new(regex)
.case_insensitive(true)
.build()
.with_lemmy_type(LemmyErrorType::InvalidRegex)
@ -240,12 +239,15 @@ pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult<Option
if regex.is_match("1") {
Err(LemmyErrorType::PermissiveRegex.into())
} else {
Ok(Some(regex))
Ok(regex)
}
})
},
}),
)
}
} else {
None
}
}
/// Cleans a url of tracking parameters.
pub fn clean_url(url: &Url) -> Url {
@ -565,13 +567,27 @@ Line3",
#[test]
fn test_valid_slur_regex() {
let valid_regexes = [&None, &Some(""), &Some("(foo|bar)")];
let valid_regex = Some("(foo|bar)");
let result = build_and_check_regex(&valid_regex);
assert!(
result.is_some_and(|x| x.is_ok()),
"Testing regex: {:?}",
valid_regex
);
}
valid_regexes.iter().for_each(|regex| {
let result = build_and_check_regex(regex);
#[test]
fn test_missing_slur_regex() {
let missing_regex = None;
let result = build_and_check_regex(&missing_regex);
assert!(result.is_none());
}
assert!(result.is_ok(), "Testing regex: {:?}", regex);
});
#[test]
fn test_empty_slur_regex() {
let empty = Some("");
let result = build_and_check_regex(&empty);
assert!(result.is_none());
}
#[test]
@ -587,9 +603,9 @@ Line3",
.for_each(|(regex_str, expected_err)| {
let result = build_and_check_regex(regex_str);
assert!(result.is_err());
assert!(result.as_ref().is_some_and(Result::is_err));
assert!(
result.is_err_and(|e| e.error_type.eq(&expected_err.clone())),
result.is_some_and(|x| x.is_err_and(|e| e.error_type.eq(&expected_err.clone()))),
"Testing regex {:?}, expected error {}",
regex_str,
expected_err

View file

@ -29,7 +29,10 @@ use lemmy_db_schema::{
traits::Crud,
utils::{get_conn, DbPool},
};
use lemmy_utils::{error::LemmyResult, settings::structs::Settings};
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
settings::structs::Settings,
};
use tracing::info;
use url::Url;
@ -421,7 +424,7 @@ async fn initialize_local_site_2022_10_10(
let domain = settings
.get_hostname_without_port()
.expect("must have domain");
.with_lemmy_type(LemmyErrorType::Unknown("must have domain".into()))?;
// Upsert this to the instance table
let instance = Instance::read_or_create(pool, domain).await?;

View file

@ -37,7 +37,7 @@ use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool};
use lemmy_federate::{Opts, SendManager};
use lemmy_routes::{feeds, images, nodeinfo, webfinger};
use lemmy_utils::{
error::LemmyResult,
error::{LemmyErrorType, LemmyResult},
rate_limit::RateLimitCell,
response::jsonify_plain_text_errors,
settings::{structs::Settings, SETTINGS},
@ -178,7 +178,8 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> {
.set(Box::new(move |d, c| {
Box::pin(match_outgoing_activities(d, c))
}))
.expect("set function pointer");
.map_err(|_| LemmyErrorType::Unknown("couldnt set function pointer".into()))?;
let request_data = federation_config.to_request_data();
let outgoing_activities_task = tokio::task::spawn(handle_outgoing_activities(
request_data.reset_request_count(),
@ -281,7 +282,7 @@ fn create_http_server(
let prom_api_metrics = PrometheusMetricsBuilder::new("lemmy_api")
.registry(default_registry().clone())
.build()
.expect("Should always be buildable");
.map_err(|e| LemmyErrorType::Unknown(format!("Should always be buildable: {e}")))?;
let context: LemmyContext = federation_config.deref().clone();
let rate_limit_cell = federation_config.rate_limit_cell().clone();

View file

@ -1,6 +1,6 @@
use clap::Parser;
use lemmy_server::{start_lemmy_server, CmdArgs};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
@ -17,7 +17,7 @@ pub async fn main() -> LemmyResult<()> {
rustls::crypto::ring::default_provider()
.install_default()
.expect("Failed to install rustls crypto provider");
.map_err(|_| LemmyErrorType::Unknown("Failed to install rustls crypto provider".into()))?;
start_lemmy_server(args).await?;
Ok(())

View file

@ -169,10 +169,7 @@ async fn process_ranks_in_batches(
where_clause: &str,
set_clause: &str,
) {
let process_start_time: DateTime<Utc> = Utc
.timestamp_opt(0, 0)
.single()
.expect("0 timestamp creation");
let process_start_time: DateTime<Utc> = Utc.timestamp_opt(0, 0).single().unwrap_or_default();
let update_batch_size = 1000; // Bigger batches than this tend to cause seq scans
let mut processed_rows_count = 0;
@ -220,10 +217,7 @@ async fn process_ranks_in_batches(
/// Post aggregates is a special case, since it needs to join to the community_aggregates
/// table, to get the active monthly user counts.
async fn process_post_aggregates_ranks_in_batches(conn: &mut AsyncPgConnection) {
let process_start_time: DateTime<Utc> = Utc
.timestamp_opt(0, 0)
.single()
.expect("0 timestamp creation");
let process_start_time: DateTime<Utc> = Utc.timestamp_opt(0, 0).single().unwrap_or_default();
let update_batch_size = 1000; // Bigger batches than this tend to cause seq scans
let mut processed_rows_count = 0;