Make conditional compilation gates for utils

error-expose
SleeplessOne1917 2024-02-09 17:32:43 -05:00
parent a9e6f33038
commit 75f0bde6b2
8 changed files with 178 additions and 117 deletions

1
Cargo.lock generated
View File

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

View File

@ -13,42 +13,95 @@ 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",
"request",
"response",
"settings",
"misc",
"rate-limit",
"apub",
"cache-header",
"email"
]
full = ["default", "ts-rs"]
ts-rs = ["dep:ts-rs"]
error-type = ["dep:serde", "dep:strum"]
error = [
"error-type",
"dep:serde_json",
"dep:anyhow",
"dep:tracing-error",
"dep:diesel",
"dep:http",
"dep:actix-web"
]
request = ["dep:reqwest-middleware", "dep:tracing", "dep:strum"]
response = ["error", "dep:actix-web"]
settings = [
"error",
"dep:deser-hjson",
"dep:regex",
"dep:urlencoding",
"dep:doku",
"dep:url",
"dep:once_cell",
"dep:smart-default"
]
rate-limit = [
"error",
"dep:enum-map",
"dep:tracing",
"dep:actix-web",
"dep:futures",
"dep:tokio",
"dep:once_cell"
]
apub = ["dep:openssl"]
cache-header = ["dep:actix-web"]
email = ["error", "settings", "dep:html2text", "dep:lettre", "dep:uuid", "dep:rosetta-i18n"]
misc = ["settings", "dep:itertools", "dep:markdown-it", "dep:tokio", "dep:futures", "dep:tracing"]
[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,78 +1,13 @@
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))]
#[cfg_attr(feature = "ts-rs", derive(TS))]
#[cfg_attr(feature = "ts-rs", 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 {
@ -231,6 +166,74 @@ pub enum LemmyErrorType {
Unknown(String),
}
cfg_if! {
if #[cfg(feature = "error")] {
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,
}
/// 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 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);
@ -273,6 +276,9 @@ impl<T> LemmyErrorExt2<T> for Result<T, LemmyError> {
}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]

View File

@ -1,23 +1,24 @@
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate smart_default;
#[cfg(feature = "apub")]
pub mod apub;
#[cfg(feature = "cache-header")]
pub mod cache_header;
#[cfg(feature = "email")]
pub mod email;
#[cfg(feature = "error-type")]
pub mod error;
#[cfg(feature = "rate-limit")]
pub mod rate_limit;
#[cfg(feature = "request")]
pub mod request;
#[cfg(feature = "response")]
pub mod response;
#[cfg(feature = "settings")]
pub mod settings;
#[cfg(feature = "misc")]
pub mod utils;
pub mod version;
use error::LemmyError;
use futures::Future;
use std::time::Duration;
use tracing::Instrument;
pub type ConnectionId = usize;
@ -35,10 +36,14 @@ macro_rules! location_info {
};
}
#[cfg(feature = "misc")]
/// 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

@ -2,8 +2,7 @@ use crate::error::{LemmyError, LemmyErrorType};
use actix_web::dev::{ConnectionInfo, Service, ServiceRequest, ServiceResponse, Transform};
use enum_map::{enum_map, EnumMap};
use futures::future::{ok, Ready};
pub use rate_limiter::{ActionType, BucketConfig};
use rate_limiter::{InstantSecs, RateLimitState};
use rate_limiter::{ActionType, BucketConfig, InstantSecs, RateLimitState};
use std::{
future::Future,
net::{IpAddr, Ipv4Addr, SocketAddr},

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},