diff --git a/Cargo.lock b/Cargo.lock index d59485e76..82c9afa6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2875,6 +2875,7 @@ version = "0.19.3" dependencies = [ "actix-web", "anyhow", + "cfg-if", "deser-hjson", "diesel", "doku", diff --git a/Cargo.toml b/Cargo.toml index 76694e607..98bf79b8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 66dcea19b..21b07420a 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -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"] } diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index d9310a58d..9d144ddb4 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -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 } diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 2b69a13ff..b55dff32f 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -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; diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml index d72ed27c9..2211d84ad 100644 --- a/crates/api_crud/Cargo.toml +++ b/crates/api_crud/Cargo.toml @@ -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"] } diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 175efbd45..33778a1b0 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -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"] } diff --git a/crates/db_perf/Cargo.toml b/crates/db_perf/Cargo.toml index 1e74b4966..f1e8faba3 100644 --- a/crates/db_perf/Cargo.toml +++ b/crates/db_perf/Cargo.toml @@ -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 } diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 91ff39bfe..6d227ad40 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -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", diff --git a/crates/db_views/Cargo.toml b/crates/db_views/Cargo.toml index cdd44869c..e70030b64 100644 --- a/crates/db_views/Cargo.toml +++ b/crates/db_views/Cargo.toml @@ -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 } diff --git a/crates/routes/Cargo.toml b/crates/routes/Cargo.toml index d70014678..324894ab9 100644 --- a/crates/routes/Cargo.toml +++ b/crates/routes/Cargo.toml @@ -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 } diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index e61d92b78..326cbf2e4 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -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 } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index d05b8c3a7..a4ee4ee7d 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -1,79 +1,15 @@ +use cfg_if::cfg_if; use serde::{Deserialize, Serialize}; -use std::{ - fmt, - fmt::{Debug, Display}, -}; -use tracing_error::SpanTrace; +use std::fmt::Debug; +use strum_macros::{Display, EnumIter}; #[cfg(feature = "full")] use ts_rs::TS; -pub type LemmyResult = Result; - -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 From for LemmyError -where - T: Into, -{ - 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::() { - 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 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 = Result; + + pub struct LemmyError { + pub error_type: LemmyErrorType, + pub inner: anyhow::Error, + pub context: SpanTrace, } - } -} -pub trait LemmyErrorExt> { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; -} + /// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]] + pub const MAX_API_PARAM_ELEMENTS: usize = 10_000; -impl> LemmyErrorExt for Result { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { - self.map_err(|error| LemmyError { - error_type, - inner: error.into(), - context: SpanTrace::capture(), - }) - } -} -pub trait LemmyErrorExt2 { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; - fn into_anyhow(self) -> Result; -} + impl From for LemmyError + where + T: Into, + { + fn from(t: T) -> Self { + let cause = t.into(); + LemmyError { + error_type: LemmyErrorType::Unknown(format!("{}", &cause)), + inner: cause, + context: SpanTrace::capture(), + } + } + } -impl LemmyErrorExt2 for Result { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { - 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 { - 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::() { + 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 for LemmyError { + fn from(error_type: LemmyErrorType) -> Self { + let inner = anyhow::anyhow!("{}", error_type); + LemmyError { + error_type, + inner, + context: SpanTrace::capture(), + } + } + } + + pub trait LemmyErrorExt> { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; + } + + impl> LemmyErrorExt for Result { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { + self.map_err(|error| LemmyError { + error_type, + inner: error.into(), + context: SpanTrace::capture(), + }) + } + } + pub trait LemmyErrorExt2 { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; + fn into_anyhow(self) -> Result; + } + + impl LemmyErrorExt2 for Result { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { + 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 { + self.map_err(|e| e.inner) + } + } } } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 6f261febd..65dbaaa45 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -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> + Send + 'static) { +pub fn spawn_try_task( + task: impl futures::Future> + Send + 'static, +) { + use tracing::Instrument; tokio::spawn( async { if let Err(e) = task.await { diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index 93ef1beec..7c68003d8 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -6,6 +6,7 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, time::Instant, }; +use strum_macros::AsRefStr; use tracing::debug; static START_TIME: Lazy = Lazy::new(Instant::now); diff --git a/crates/utils/src/settings/mod.rs b/crates/utils/src/settings/mod.rs index 4642a67cf..f630d0217 100644 --- a/crates/utils/src/settings/mod.rs +++ b/crates/utils/src/settings/mod.rs @@ -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"; diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 46e9b747c..4a8d8afb6 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -1,5 +1,6 @@ use doku::Document; use serde::{Deserialize, Serialize}; +use smart_default::SmartDefault; use std::{ env, net::{IpAddr, Ipv4Addr},