Merge remote-tracking branch 'origin/main' into vote_display_mode

This commit is contained in:
Dessalines 2024-02-27 09:57:35 -05:00
commit b2add170b3
33 changed files with 2454 additions and 1192 deletions

View File

@ -60,7 +60,7 @@ steps:
# store cargo data in repo folder so that it gets cached between steps
CARGO_HOME: .cargo_home
commands:
# need make existing toolchain available
- rustup component add rustfmt
- cargo +nightly fmt -- --check
cargo_machete:

1
Cargo.lock generated
View File

@ -2875,6 +2875,7 @@ version = "0.19.3"
dependencies = [
"actix-web",
"anyhow",
"cfg-if",
"deser-hjson",
"diesel",
"doku",

View File

@ -89,7 +89,7 @@ unwrap_used = "deny"
lemmy_api = { version = "=0.19.3", path = "./crates/api" }
lemmy_api_crud = { version = "=0.19.3", path = "./crates/api_crud" }
lemmy_apub = { version = "=0.19.3", path = "./crates/apub" }
lemmy_utils = { version = "=0.19.3", path = "./crates/utils" }
lemmy_utils = { version = "=0.19.3", path = "./crates/utils", default-features = false }
lemmy_db_schema = { version = "=0.19.3", path = "./crates/db_schema" }
lemmy_api_common = { version = "=0.19.3", path = "./crates/api_common" }
lemmy_routes = { version = "=0.19.3", path = "./crates/routes" }

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LE
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
# pictrs setup
if [ ! -f "pict-rs" ]; then
if [ ! -f "api_tests/pict-rs" ]; then
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
chmod +x api_tests/pict-rs
fi

View File

@ -37,9 +37,11 @@ import {
waitForPost,
alphaUrl,
loginUser,
createCommunity,
getPosts,
} from "./shared";
import { PostView } from "lemmy-js-client/dist/types/PostView";
import { EditSite, ResolveObject } from "lemmy-js-client";
import { EditSite, ListingType, ResolveObject } from "lemmy-js-client";
let betaCommunity: CommunityView | undefined;
@ -225,6 +227,26 @@ test("Sticky a post", async () => {
expect(betaPost3?.post.featured_community).toBe(false);
});
test("Collection of featured posts gets federated", async () => {
// create a new community and feature a post
let community = await createCommunity(alpha);
let post = await createPost(alpha, community.community_view.community.id);
let featuredPost = await featurePost(alpha, true, post.post_view.post);
expect(featuredPost.post_view.post.featured_community).toBe(true);
// fetch the community, ensure that post is also fetched and marked as featured
let betaCommunity = await resolveCommunity(
beta,
community.community_view.community.actor_id,
);
const betaPost = await waitForPost(
beta,
post.post_view.post,
post => post?.post.featured_community === true,
);
});
test("Lock a post", async () => {
if (!betaCommunity) {
throw "Missing beta community";

View File

@ -18,7 +18,7 @@ doctest = false
workspace = true
[dependencies]
lemmy_utils = { workspace = true }
lemmy_utils = { workspace = true, features = ["default"] }
lemmy_db_schema = { workspace = true, features = ["full"] }
lemmy_db_views = { workspace = true, features = ["full"] }
lemmy_db_views_moderator = { workspace = true, features = ["full"] }

View File

@ -20,10 +20,10 @@ workspace = true
full = [
"tracing",
"rosetta-i18n",
"lemmy_utils",
"lemmy_db_views/full",
"lemmy_db_views_actor/full",
"lemmy_db_views_moderator/full",
"lemmy_utils/default",
"activitypub_federation",
"encoding",
"reqwest-middleware",
@ -44,7 +44,7 @@ lemmy_db_views = { workspace = true }
lemmy_db_views_moderator = { workspace = true }
lemmy_db_views_actor = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true, optional = true }
lemmy_utils = { workspace = true, features = ["error-type"] }
activitypub_federation = { workspace = true, optional = true }
serde = { workspace = true }
serde_with = { workspace = true }

View File

@ -23,7 +23,9 @@ pub extern crate lemmy_db_schema;
pub extern crate lemmy_db_views;
pub extern crate lemmy_db_views_actor;
pub extern crate lemmy_db_views_moderator;
pub extern crate lemmy_utils;
pub use lemmy_utils::LemmyErrorType;
use serde::{Deserialize, Serialize};
use std::time::Duration;

View File

@ -53,7 +53,9 @@ pub async fn fetch_link_metadata(
// https://github.com/LemmyNet/lemmy/issues/1964
let html_bytes = response.bytes().await.map_err(LemmyError::from)?.to_vec();
let opengraph_data = extract_opengraph_data(&html_bytes, url).unwrap_or_default();
let opengraph_data = extract_opengraph_data(&html_bytes, url)
.map_err(|e| info!("{e}"))
.unwrap_or_default();
let thumbnail = extract_thumbnail_from_opengraph_data(
url,
&opengraph_data,
@ -96,7 +98,7 @@ fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result<OpenGraphData,
.ok_or(LemmyErrorType::NoLinesInHtml)?
.to_lowercase();
if !first_line.starts_with("<!doctype html>") {
if !first_line.starts_with("<!doctype html") {
Err(LemmyErrorType::SiteMetadataPageIsNotDoctypeHtml)?
}

View File

@ -967,8 +967,6 @@ mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::utils::{honeypot_check, limit_expire_time, password_length_check};
use chrono::{Days, Utc};
use pretty_assertions::assert_eq;
use serial_test::serial;

View File

@ -13,7 +13,7 @@ repository.workspace = true
workspace = true
[dependencies]
lemmy_utils = { workspace = true }
lemmy_utils = { workspace = true, features = ["default"] }
lemmy_db_schema = { workspace = true, features = ["full"] }
lemmy_db_views = { workspace = true, features = ["full"] }
lemmy_db_views_actor = { workspace = true, features = ["full"] }

View File

@ -18,7 +18,7 @@ doctest = false
workspace = true
[dependencies]
lemmy_utils = { workspace = true }
lemmy_utils = { workspace = true, features = ["default"] }
lemmy_db_schema = { workspace = true, features = ["full"] }
lemmy_db_views = { workspace = true, features = ["full"] }
lemmy_db_views_actor = { workspace = true, features = ["full"] }

View File

@ -6,11 +6,14 @@ use activitypub_federation::{
config::Data,
kinds::collection::OrderedCollectionType,
protocol::verification::verify_domains_match,
traits::{ActivityHandler, Collection, Object},
traits::{Collection, Object},
};
use futures::future::{join_all, try_join_all};
use lemmy_api_common::{context::LemmyContext, utils::generate_featured_url};
use lemmy_db_schema::{source::post::Post, utils::FETCH_LIMIT_MAX};
use lemmy_db_schema::{
source::{community::Community, post::Post},
utils::FETCH_LIMIT_MAX,
};
use lemmy_utils::error::LemmyError;
use url::Url;
@ -55,35 +58,36 @@ impl Collection for ApubCommunityFeatured {
async fn from_json(
apub: Self::Kind,
_owner: &Self::Owner,
data: &Data<Self::DataType>,
owner: &Self::Owner,
context: &Data<Self::DataType>,
) -> Result<Self, Self::Error>
where
Self: Sized,
{
let mut posts = apub.ordered_items;
if posts.len() as i64 > FETCH_LIMIT_MAX {
posts = posts
let mut pages = apub.ordered_items;
if pages.len() as i64 > FETCH_LIMIT_MAX {
pages = pages
.get(0..(FETCH_LIMIT_MAX as usize))
.unwrap_or_default()
.to_vec();
}
// We intentionally ignore errors here. This is because the outbox might contain posts from old
// Lemmy versions, or from other software which we cant parse. In that case, we simply skip the
// item and only parse the ones that work.
// process items in parallel, to avoid long delay from fetch_site_metadata() and other processing
join_all(posts.into_iter().map(|post| {
let stickied_posts: Vec<Post> = join_all(pages.into_iter().map(|page| {
async {
// use separate request counter for each item, otherwise there will be problems with
// parallel processing
let verify = post.verify(data).await;
if verify.is_ok() {
post.receive(data).await.ok();
}
ApubPost::verify(&page, &apub.id, context).await?;
ApubPost::from_json(page, context).await
}
}))
.await;
.await
// ignore any failed or unparseable items
.into_iter()
.filter_map(|p| p.ok().map(|p| p.0))
.collect();
Community::set_featured_posts(owner.id, stickied_posts, &mut context.pool()).await?;
// This return value is unused, so just set an empty vec
Ok(ApubCommunityFeatured(()))

View File

@ -123,10 +123,7 @@ pub(crate) mod tests {
use crate::protocol::objects::{group::Group, tombstone::Tombstone};
use actix_web::body::to_bytes;
use lemmy_db_schema::{
source::{
community::{Community, CommunityInsertForm},
instance::Instance,
},
source::{community::CommunityInsertForm, instance::Instance},
traits::Crud,
CommunityVisibility,
};

View File

@ -166,7 +166,7 @@ impl Object for ApubCommunity {
moderators_url: group.attributed_to.clone().map(Into::into),
posting_restricted_to_mods: group.posting_restricted_to_mods,
instance_id,
featured_url: group.featured.map(Into::into),
featured_url: group.featured.clone().map(Into::into),
..Default::default()
};
let languages =
@ -184,6 +184,9 @@ impl Object for ApubCommunity {
spawn_try_task(async move {
group.outbox.dereference(&community_, &context_).await?;
group.followers.dereference(&community_, &context_).await?;
if let Some(featured) = group.featured {
featured.dereference(&community_, &context_).await?;
}
if let Some(moderators) = group.attributed_to {
moderators.dereference(&community_, &context_).await?;
}
@ -254,7 +257,7 @@ pub(crate) mod tests {
protocol::tests::file_to_json_object,
};
use activitypub_federation::fetch::collection_id::CollectionId;
use lemmy_db_schema::{source::site::Site, traits::Crud};
use lemmy_db_schema::source::site::Site;
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;

View File

@ -218,7 +218,6 @@ pub(in crate::objects) async fn fetch_instance_actor_for_object<T: Into<Url> + C
pub(crate) mod tests {
use super::*;
use crate::protocol::tests::file_to_json_object;
use lemmy_db_schema::traits::Crud;
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;

View File

@ -225,7 +225,7 @@ pub(crate) mod tests {
protocol::{objects::instance::Instance, tests::file_to_json_object},
};
use activitypub_federation::fetch::object_id::ObjectId;
use lemmy_db_schema::{source::site::Site, traits::Crud};
use lemmy_db_schema::source::site::Site;
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;

View File

@ -34,7 +34,6 @@ use lemmy_api_common::{
},
};
use lemmy_db_schema::{
self,
source::{
community::Community,
local_site::LocalSite,
@ -297,7 +296,6 @@ mod tests {
community::{tests::parse_lemmy_community, ApubCommunity},
instance::ApubSite,
person::{tests::parse_lemmy_person, ApubPerson},
post::ApubPost,
},
protocol::tests::file_to_json_object,
};

View File

@ -8,7 +8,6 @@ use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::{LemmyError, LemmyErrorType};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use strum_macros::Display;
use url::Url;

View File

@ -19,6 +19,6 @@ diesel = { workspace = true }
diesel-async = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_db_views = { workspace = true, features = ["full"] }
lemmy_utils = { workspace = true }
lemmy_utils = { workspace = true, features = ["default"] }
tokio = { workspace = true }
url = { workspace = true }

View File

@ -48,7 +48,7 @@ strum = { workspace = true }
strum_macros = { workspace = true }
serde_json = { workspace = true, optional = true }
activitypub_federation = { workspace = true, optional = true }
lemmy_utils = { workspace = true, optional = true }
lemmy_utils = { workspace = true, optional = true, features = ["default"] }
bcrypt = { workspace = true, optional = true }
diesel = { workspace = true, features = [
"postgres",

View File

@ -306,9 +306,9 @@ impl CommunityLanguage {
// tracing::warn!("unique error: {_info:#?}");
// _info.constraint_name() should be = "community_language_community_id_language_id_key"
return Ok(());
} else {
insert_res?;
}
insert_res?;
Ok(())
}) as _
})
@ -391,27 +391,13 @@ mod tests {
use super::*;
use crate::{
impls::actor_language::{
convert_read_languages,
convert_update_languages,
default_post_language,
get_conn,
CommunityLanguage,
DbPool,
Language,
LanguageId,
LocalUserLanguage,
QueryDsl,
RunQueryDsl,
SiteLanguage,
},
source::{
community::{Community, CommunityInsertForm},
instance::Instance,
local_site::{LocalSite, LocalSiteInsertForm},
local_user::{LocalUser, LocalUserInsertForm},
person::{Person, PersonInsertForm},
site::{Site, SiteInsertForm},
site::SiteInsertForm,
},
traits::Crud,
utils::build_db_pool_for_tests,

View File

@ -14,6 +14,7 @@ use crate::{
CommunityPersonBanForm,
CommunityUpdateForm,
},
post::Post,
},
traits::{ApubActor, Bannable, Crud, Followable, Joinable},
utils::{functions::lower, get_conn, DbPool},
@ -27,6 +28,7 @@ use diesel::{
result::Error,
select,
sql_types,
update,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl,
@ -137,6 +139,42 @@ impl Community {
}
Err(diesel::NotFound)
}
pub async fn set_featured_posts(
community_id: CommunityId,
posts: Vec<Post>,
pool: &mut DbPool<'_>,
) -> Result<(), Error> {
use crate::schema::post;
let conn = &mut get_conn(pool).await?;
for p in &posts {
debug_assert!(p.community_id == community_id);
}
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
update(
// first remove all existing featured posts
post::table,
)
.filter(post::dsl::community_id.eq(community_id))
.set(post::dsl::featured_community.eq(false))
.execute(conn)
.await?;
// then mark the given posts as featured
let post_ids: Vec<_> = posts.iter().map(|p| p.id).collect();
update(post::table)
.filter(post::dsl::id.eq_any(post_ids))
.set(post::dsl::featured_community.eq(true))
.execute(conn)
.await?;
Ok(())
}) as _
})
.await
}
}
impl CommunityModerator {

View File

@ -248,9 +248,8 @@ pub fn limit_and_offset(
Some(page) => {
if page < 1 {
return Err(QueryBuilderError("Page is < 1".into()));
} else {
page
}
page
}
None => 1,
};
@ -260,9 +259,8 @@ pub fn limit_and_offset(
return Err(QueryBuilderError(
format!("Fetch limit is > {FETCH_LIMIT_MAX}").into(),
));
} else {
limit
}
limit
}
None => FETCH_LIMIT_DEFAULT,
};
@ -542,8 +540,7 @@ mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::{fuzzy_search, *};
use crate::utils::is_email_regex;
use super::*;
use pretty_assertions::assert_eq;
#[test]

View File

@ -29,7 +29,7 @@ full = [
[dependencies]
lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true, optional = true }
lemmy_utils = { workspace = true, optional = true, features = ["default"] }
diesel = { workspace = true, optional = true }
diesel-async = { workspace = true, optional = true }
diesel_ltree = { workspace = true, optional = true }

View File

@ -16,7 +16,7 @@ doctest = false
workspace = true
[dependencies]
lemmy_utils = { workspace = true }
lemmy_utils = { workspace = true, features = ["default"] }
lemmy_db_views = { workspace = true }
lemmy_db_views_actor = { workspace = true }
lemmy_db_schema = { workspace = true }

View File

@ -13,42 +13,82 @@ name = "lemmy_utils"
path = "src/lib.rs"
doctest = false
[[bin]]
name = "lemmy_util_bin"
path = "src/main.rs"
required-features = ["default"]
[lints]
workspace = true
[features]
full = ["ts-rs"]
default = [
"error-type",
"dep:serde_json",
"dep:anyhow",
"dep:tracing-error",
"dep:diesel",
"dep:http",
"dep:actix-web",
"dep:reqwest-middleware",
"dep:tracing",
"dep:actix-web",
"dep:deser-hjson",
"dep:regex",
"dep:urlencoding",
"dep:doku",
"dep:url",
"dep:once_cell",
"dep:smart-default",
"dep:enum-map",
"dep:futures",
"dep:tokio",
"dep:openssl",
"dep:html2text",
"dep:lettre",
"dep:uuid",
"dep:rosetta-i18n",
"dep:itertools",
"dep:markdown-it",
]
full = ["default", "dep:ts-rs"]
error-type = ["dep:serde", "dep:strum"]
[dependencies]
regex = { workspace = true }
tracing = { workspace = true }
tracing-error = { workspace = true }
itertools = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
once_cell = { workspace = true }
url = { workspace = true }
actix-web = { workspace = true }
anyhow = { workspace = true }
reqwest-middleware = { workspace = true }
strum = { workspace = true }
regex = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }
tracing-error = { workspace = true, optional = true }
itertools = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
once_cell = { workspace = true, optional = true }
url = { workspace = true, optional = true }
actix-web = { workspace = true, optional = true }
anyhow = { workspace = true, optional = true }
reqwest-middleware = { workspace = true, optional = true }
strum = { workspace = true, optional = true }
strum_macros = { workspace = true }
futures = { workspace = true }
diesel = { workspace = true, features = ["chrono"] }
http = { workspace = true }
doku = { workspace = true, features = ["url-2"] }
uuid = { workspace = true, features = ["serde", "v4"] }
rosetta-i18n = { workspace = true }
tokio = { workspace = true }
urlencoding = { workspace = true }
openssl = "0.10.63"
html2text = "0.6.0"
deser-hjson = "2.2.4"
smart-default = "0.7.1"
lettre = { version = "0.11.3", features = ["tokio1", "tokio1-native-tls"] }
markdown-it = "0.6.0"
futures = { workspace = true, optional = true }
diesel = { workspace = true, features = ["chrono"], optional = true }
http = { workspace = true, optional = true }
doku = { workspace = true, features = ["url-2"], optional = true }
uuid = { workspace = true, features = ["serde", "v4"], optional = true }
rosetta-i18n = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
urlencoding = { workspace = true, optional = true }
openssl = { version = "0.10.63", optional = true }
html2text = { version = "0.6.0", optional = true }
deser-hjson = { version = "2.2.4", optional = true }
smart-default = { version = "0.7.1", optional = true }
lettre = { version = "0.11.3", features = [
"tokio1",
"tokio1-native-tls",
], optional = true }
markdown-it = { version = "0.6.0", optional = true }
ts-rs = { workspace = true, optional = true }
enum-map = { workspace = true }
enum-map = { workspace = true, optional = true }
cfg-if = "1"
[dev-dependencies]
reqwest = { workspace = true }

View File

@ -1,79 +1,15 @@
use cfg_if::cfg_if;
use serde::{Deserialize, Serialize};
use std::{
fmt,
fmt::{Debug, Display},
};
use tracing_error::SpanTrace;
#[cfg(feature = "full")]
use std::fmt::Debug;
use strum_macros::{Display, EnumIter};
#[cfg(feature = "ts-rs")]
use ts_rs::TS;
pub type LemmyResult<T> = Result<T, LemmyError>;
pub struct LemmyError {
pub error_type: LemmyErrorType,
pub inner: anyhow::Error,
pub context: SpanTrace,
}
/// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]]
pub const MAX_API_PARAM_ELEMENTS: usize = 10_000;
impl<T> From<T> for LemmyError
where
T: Into<anyhow::Error>,
{
fn from(t: T) -> Self {
let cause = t.into();
LemmyError {
error_type: LemmyErrorType::Unknown(format!("{}", &cause)),
inner: cause,
context: SpanTrace::capture(),
}
}
}
impl Debug for LemmyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LemmyError")
.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 {
write!(f, "{}: ", &self.error_type)?;
// print anyhow including trace
// https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
// this will print the anyhow trace (only if it exists)
// and if RUST_BACKTRACE=1, also a full backtrace
writeln!(f, "{:?}", self.inner)?;
fmt::Display::fmt(&self.context, f)
}
}
impl actix_web::error::ResponseError for LemmyError {
fn status_code(&self) -> http::StatusCode {
if self.error_type == LemmyErrorType::IncorrectLogin {
return http::StatusCode::UNAUTHORIZED;
}
match self.inner.downcast_ref::<diesel::result::Error>() {
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
_ => http::StatusCode::BAD_REQUEST,
}
}
fn error_response(&self) -> actix_web::HttpResponse {
actix_web::HttpResponse::build(self.status_code()).json(&self.error_type)
}
}
#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, EnumIter)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, EnumIter, Hash)]
#[cfg_attr(feature = "ts-rs", derive(TS))]
#[cfg_attr(feature = "ts-rs", ts(export))]
#[serde(tag = "error", content = "message", rename_all = "snake_case")]
#[non_exhaustive]
// TODO: order these based on the crate they belong to (utils, federation, db, api)
pub enum LemmyErrorType {
ReportReasonRequired,
@ -231,45 +167,115 @@ pub enum LemmyErrorType {
Unknown(String),
}
impl From<LemmyErrorType> for LemmyError {
fn from(error_type: LemmyErrorType) -> Self {
let inner = anyhow::anyhow!("{}", error_type);
LemmyError {
error_type,
inner,
context: SpanTrace::capture(),
cfg_if! {
if #[cfg(feature = "default")] {
use tracing_error::SpanTrace;
use std::fmt;
pub type LemmyResult<T> = Result<T, LemmyError>;
pub struct LemmyError {
pub error_type: LemmyErrorType,
pub inner: anyhow::Error,
pub context: SpanTrace,
}
}
}
pub trait LemmyErrorExt<T, E: Into<anyhow::Error>> {
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
}
/// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]]
pub const MAX_API_PARAM_ELEMENTS: usize = 10_000;
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,
inner: error.into(),
context: SpanTrace::capture(),
})
}
}
pub trait LemmyErrorExt2<T> {
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
fn into_anyhow(self) -> Result<T, anyhow::Error>;
}
impl<T> From<T> for LemmyError
where
T: Into<anyhow::Error>,
{
fn from(t: T) -> Self {
let cause = t.into();
LemmyError {
error_type: LemmyErrorType::Unknown(format!("{}", &cause)),
inner: cause,
context: SpanTrace::capture(),
}
}
}
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 = error_type;
e
})
}
// this function can't be an impl From or similar because it would conflict with one of the other broad Into<> implementations
fn into_anyhow(self) -> Result<T, anyhow::Error> {
self.map_err(|e| e.inner)
impl Debug for LemmyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LemmyError")
.field("message", &self.error_type)
.field("inner", &self.inner)
.field("context", &self.context)
.finish()
}
}
impl fmt::Display for LemmyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: ", &self.error_type)?;
// print anyhow including trace
// https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
// this will print the anyhow trace (only if it exists)
// and if RUST_BACKTRACE=1, also a full backtrace
writeln!(f, "{:?}", self.inner)?;
fmt::Display::fmt(&self.context, f)
}
}
impl actix_web::error::ResponseError for LemmyError {
fn status_code(&self) -> http::StatusCode {
if self.error_type == LemmyErrorType::IncorrectLogin {
return http::StatusCode::UNAUTHORIZED;
}
match self.inner.downcast_ref::<diesel::result::Error>() {
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
_ => http::StatusCode::BAD_REQUEST,
}
}
fn error_response(&self) -> actix_web::HttpResponse {
actix_web::HttpResponse::build(self.status_code()).json(&self.error_type)
}
}
impl From<LemmyErrorType> for LemmyError {
fn from(error_type: LemmyErrorType) -> Self {
let inner = anyhow::anyhow!("{}", error_type);
LemmyError {
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,
inner: error.into(),
context: SpanTrace::capture(),
})
}
}
pub trait LemmyErrorExt2<T> {
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
fn into_anyhow(self) -> Result<T, anyhow::Error>;
}
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 = error_type;
e
})
}
// this function can't be an impl From or similar because it would conflict with one of the other broad Into<> implementations
fn into_anyhow(self) -> Result<T, anyhow::Error> {
self.map_err(|e| e.inner)
}
}
}
}

View File

@ -1,23 +1,29 @@
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate smart_default;
use cfg_if::cfg_if;
pub mod apub;
pub mod cache_header;
pub mod email;
pub mod error;
pub mod rate_limit;
pub mod request;
pub mod response;
pub mod settings;
pub mod utils;
pub mod version;
cfg_if! {
if #[cfg(feature = "default")] {
pub mod apub;
pub mod cache_header;
pub mod email;
pub mod error;
pub mod rate_limit;
pub mod request;
pub mod response;
pub mod settings;
pub mod utils;
pub mod version;
} else {
mod error;
}
}
cfg_if! {
if #[cfg(feature = "error-type")] {
pub use error::LemmyErrorType;
}
}
use error::LemmyError;
use futures::Future;
use std::time::Duration;
use tracing::Instrument;
pub type ConnectionId = usize;
@ -35,10 +41,14 @@ macro_rules! location_info {
};
}
#[cfg(feature = "default")]
/// tokio::spawn, but accepts a future that may fail and also
/// * logs errors
/// * attaches the spawned task to the tracing span of the caller for better logging
pub fn spawn_try_task(task: impl Future<Output = Result<(), LemmyError>> + Send + 'static) {
pub fn spawn_try_task(
task: impl futures::Future<Output = Result<(), error::LemmyError>> + Send + 'static,
) {
use tracing::Instrument;
tokio::spawn(
async {
if let Err(e) = task.await {

View File

@ -6,6 +6,7 @@ use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
time::Instant,
};
use strum_macros::AsRefStr;
use tracing::debug;
static START_TIME: Lazy<Instant> = Lazy::new(Instant::now);

View File

@ -1,8 +1,4 @@
use crate::{
error::LemmyError,
location_info,
settings::structs::{PictrsConfig, Settings},
};
use crate::{error::LemmyError, location_info};
use anyhow::{anyhow, Context};
use deser_hjson::from_str;
use once_cell::sync::Lazy;
@ -12,8 +8,7 @@ use urlencoding::encode;
pub mod structs;
use crate::settings::structs::PictrsImageMode;
use structs::DatabaseConnection;
use structs::{DatabaseConnection, PictrsConfig, PictrsImageMode, Settings};
static DEFAULT_CONFIG_FILE: &str = "config/config.hjson";

View File

@ -1,5 +1,6 @@
use doku::Document;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use std::{
env,
net::{IpAddr, Ipv4Addr},