]> Untitled Git - lemmy.git/commitdiff
Simplify config using macros (#1686)
authorDessalines <dessalines@users.noreply.github.com>
Wed, 4 Aug 2021 21:13:51 +0000 (17:13 -0400)
committerGitHub <noreply@github.com>
Wed, 4 Aug 2021 21:13:51 +0000 (21:13 +0000)
Co-authored-by: Felix Ableitner <me@nutomic.com>
29 files changed:
Cargo.lock
crates/api/src/local_user.rs
crates/api_common/src/lib.rs
crates/api_crud/src/post/create.rs
crates/api_crud/src/post/update.rs
crates/api_crud/src/site/read.rs
crates/api_crud/src/user/create.rs
crates/apub/src/activities/community/mod.rs
crates/apub/src/activity_queue.rs
crates/apub/src/http/mod.rs
crates/apub/src/http/routes.rs
crates/apub/src/lib.rs
crates/apub/src/objects/mod.rs
crates/apub/src/objects/person.rs
crates/apub/src/objects/post.rs
crates/routes/src/images.rs
crates/routes/src/nodeinfo.rs
crates/routes/src/webfinger.rs
crates/utils/Cargo.toml
crates/utils/src/claims.rs
crates/utils/src/email.rs
crates/utils/src/lib.rs
crates/utils/src/rate_limit/mod.rs
crates/utils/src/request.rs
crates/utils/src/settings/defaults.rs [deleted file]
crates/utils/src/settings/mod.rs
crates/utils/src/settings/structs.rs
crates/utils/src/utils.rs
src/main.rs

index 622c260c6dac578ef7fe1d2d99e8d46b699b1c47..ee05a46ce3b45fb401ee9874d96f5a706fb41bf4 100644 (file)
@@ -1001,15 +1001,6 @@ dependencies = [
  "termcolor",
 ]
 
-[[package]]
-name = "envy"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "event-listener"
 version = "2.5.1"
@@ -1882,7 +1873,6 @@ dependencies = [
  "comrak",
  "deser-hjson",
  "diesel",
- "envy",
  "futures",
  "http",
  "itertools",
@@ -1890,7 +1880,6 @@ dependencies = [
  "lazy_static",
  "lettre",
  "log",
- "merge",
  "openssl",
  "percent-encoding",
  "rand 0.8.4",
@@ -1898,6 +1887,7 @@ dependencies = [
  "reqwest",
  "serde",
  "serde_json",
+ "smart-default",
  "strum",
  "strum_macros",
  "thiserror",
@@ -2037,28 +2027,6 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "merge"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
-dependencies = [
- "merge_derive",
- "num-traits",
-]
-
-[[package]]
-name = "merge_derive"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
-dependencies = [
- "proc-macro-error",
- "proc-macro2 1.0.27",
- "quote 1.0.9",
- "syn 1.0.73",
-]
-
 [[package]]
 name = "migrations_internals"
 version = "1.4.1"
@@ -2444,30 +2412,6 @@ dependencies = [
  "vcpkg",
 ]
 
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2 1.0.27",
- "quote 1.0.9",
- "syn 1.0.73",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2 1.0.27",
- "quote 1.0.9",
- "version_check",
-]
-
 [[package]]
 name = "proc-macro-hack"
 version = "0.5.19"
@@ -3021,6 +2965,17 @@ version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
 
+[[package]]
+name = "smart-default"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
+dependencies = [
+ "proc-macro2 1.0.27",
+ "quote 1.0.9",
+ "syn 1.0.73",
+]
+
 [[package]]
 name = "socket2"
 version = "0.4.0"
index 4b48a00835091d2fb85c549a185897595758f8ac..54fe6d76beadd7bbf463ebd90b0e9e91e7a52ba0 100644 (file)
@@ -115,7 +115,7 @@ impl Perform for GetCaptcha {
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
   ) -> Result<Self::Response, LemmyError> {
-    let captcha_settings = Settings::get().captcha();
+    let captcha_settings = Settings::get().captcha;
 
     if !captcha_settings.enabled {
       return Ok(GetCaptchaResponse { ok: None });
index 63c0bd530ca82f0180490a8f4fd132126e5f5372..4e3cd751bd84437b598e24db8dc01eae5a004bc5 100644 (file)
@@ -198,7 +198,7 @@ pub fn send_email_to_user(
     let subject = &format!(
       "{} - {} {}",
       subject_text,
-      Settings::get().hostname(),
+      Settings::get().hostname,
       local_user_view.person.name,
     );
     let html = &format!(
@@ -386,14 +386,14 @@ pub async fn collect_moderated_communities(
 pub async fn build_federated_instances(
   pool: &DbPool,
 ) -> Result<Option<FederatedInstances>, LemmyError> {
-  if Settings::get().federation().enabled {
+  if Settings::get().federation.enabled {
     let distinct_communities = blocking(pool, move |conn| {
       Community::distinct_federated_communities(conn)
     })
     .await??;
 
-    let allowed = Settings::get().get_allowed_instances();
-    let blocked = Settings::get().get_blocked_instances();
+    let allowed = Settings::get().federation.allowed_instances;
+    let blocked = Settings::get().federation.blocked_instances;
 
     let mut linked = distinct_communities
       .iter()
@@ -405,7 +405,7 @@ pub async fn build_federated_instances(
     }
 
     if let Some(blocked) = blocked.as_ref() {
-      linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
+      linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname));
     }
 
     // Sort and remove dupes
index bbb720fafe77b38f23e00e1d51cfa4db61e9360e..f041a00b1b0896d71e8164ffcabbd180f46c8764 100644 (file)
@@ -52,8 +52,11 @@ impl PerformCrud for CreatePost {
 
     // Fetch Iframely and pictrs cached image
     let data_url = data.url.as_ref();
-    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
-      fetch_iframely_and_pictrs_data(context.client(), data_url).await;
+    let (iframely_response, pictrs_thumbnail) =
+      fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
+    let (embed_title, embed_description, embed_html) = iframely_response
+      .map(|u| (u.title, u.description, u.html))
+      .unwrap_or((None, None, None));
 
     let post_form = PostForm {
       name: data.name.trim().to_owned(),
@@ -62,9 +65,9 @@ impl PerformCrud for CreatePost {
       community_id: data.community_id,
       creator_id: local_user_view.person.id,
       nsfw: data.nsfw,
-      embed_title: iframely_title,
-      embed_description: iframely_description,
-      embed_html: iframely_html,
+      embed_title,
+      embed_description,
+      embed_html,
       thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
       ..PostForm::default()
     };
index dade6f42bef0ac2977d5678f7b5619e934d80e15..c9fe7e3320b511c57a1fbae87df7e89004f0e217 100644 (file)
@@ -52,8 +52,11 @@ impl PerformCrud for EditPost {
 
     // Fetch Iframely and Pictrs cached image
     let data_url = data.url.as_ref();
-    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
-      fetch_iframely_and_pictrs_data(context.client(), data_url).await;
+    let (iframely_response, pictrs_thumbnail) =
+      fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
+    let (embed_title, embed_description, embed_html) = iframely_response
+      .map(|u| (u.title, u.description, u.html))
+      .unwrap_or((None, None, None));
 
     let post_form = PostForm {
       creator_id: orig_post.creator_id.to_owned(),
@@ -63,9 +66,9 @@ impl PerformCrud for EditPost {
       body: data.body.to_owned(),
       nsfw: data.nsfw,
       updated: Some(naive_now()),
-      embed_title: iframely_title,
-      embed_description: iframely_description,
-      embed_html: iframely_html,
+      embed_title,
+      embed_description,
+      embed_html,
       thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
       ..PostForm::default()
     };
index da379e322cb732c69bf7d8957030d81e3686ed5c..8b2df7117426d1bf3b6b55931b4cb8382df4da02 100644 (file)
@@ -28,7 +28,7 @@ impl PerformCrud for GetSite {
       Ok(site_view) => Some(site_view),
       // If the site isn't created yet, check the setup
       Err(_) => {
-        if let Some(setup) = Settings::get().setup().as_ref() {
+        if let Some(setup) = Settings::get().setup.as_ref() {
           let register = Register {
             username: setup.admin_username.to_owned(),
             email: setup.admin_email.to_owned(),
index 8047369fa722e24bb272650871fd647a35e111e6..6f9216b361a02a0ce8fac3d1b0a33ba2a24fe25d 100644 (file)
@@ -69,7 +69,7 @@ impl PerformCrud for Register {
     .await??;
 
     // If its not the admin, check the captcha
-    if !no_admins && Settings::get().captcha().enabled {
+    if !no_admins && Settings::get().captcha.enabled {
       let check = context
         .chat_server()
         .send(CheckCaptcha {
index 62b39c6e8ce0a087955730240fec5f04fccbc71c..4151471ad1634a2d90c83e829f54681b925ea28b 100644 (file)
@@ -50,7 +50,7 @@ async fn list_community_follower_inboxes(
     .iter()
     .flatten()
     .unique()
-    .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
+    .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
     .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
     .map(|inbox| inbox.to_owned())
     .collect(),
index 3f8a019fd3375daea0ebfb85b5e6bbbe9c026f13..c7ff0a81d30aaa01f7a6a53fb048485192e03780 100644 (file)
@@ -83,7 +83,7 @@ where
   .iter()
   .flatten()
   .unique()
-  .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
+  .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
   .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
   .map(|inbox| inbox.to_owned())
   .collect();
@@ -170,7 +170,7 @@ pub(crate) async fn send_activity_new<T>(
 where
   T: Serialize,
 {
-  if !Settings::get().federation().enabled || inboxes.is_empty() {
+  if !Settings::get().federation.enabled || inboxes.is_empty() {
     return Ok(());
   }
 
@@ -230,7 +230,7 @@ where
   Kind: Serialize,
   <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
 {
-  if !Settings::get().federation().enabled || inboxes.is_empty() {
+  if !Settings::get().federation.enabled || inboxes.is_empty() {
     return Ok(());
   }
 
index f366cb48c5cb56a18b043a967e2d58a04c5207fe..477345fd4700f72d5c55ccbd3e1e7e0619f4cdb1 100644 (file)
@@ -175,7 +175,7 @@ fn assert_activity_not_local<T: Debug + ActivityHandler>(activity: &T) -> Result
     .domain()
     .context(location_info!())?;
 
-  if activity_domain == Settings::get().hostname() {
+  if activity_domain == Settings::get().hostname {
     return Err(
       anyhow!(
         "Error: received activity which was sent by local instance: {:?}",
index 1afc3f1e509cbdaa7893d34200f49ac01376f755..cd6b11486ee5afa08a9864a0fdb2425f95b9d819 100644 (file)
@@ -25,8 +25,8 @@ static APUB_JSON_CONTENT_TYPE_LONG: &str =
   "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
 
 pub fn config(cfg: &mut web::ServiceConfig) {
-  if Settings::get().federation().enabled {
-    println!("federation enabled, host is {}", Settings::get().hostname());
+  if Settings::get().federation.enabled {
+    println!("federation enabled, host is {}", Settings::get().hostname);
     let digest_verifier = VerifyDigest::new(Sha256::new());
 
     let header_guard_accept = guard::Any(guard::Header("Accept", APUB_JSON_CONTENT_TYPE))
index 19123057911e5c9e2861f96a451e6e302a6b988c..60d7c48715408866f47d3394d49edcb8ce6cc52c 100644 (file)
@@ -75,7 +75,7 @@ pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Resu
   let domain = apub_id.domain().context(location_info!())?.to_string();
   let local_instance = settings.get_hostname_without_port()?;
 
-  if !settings.federation().enabled {
+  if !settings.federation.enabled {
     return if domain == local_instance {
       Ok(())
     } else {
@@ -102,18 +102,15 @@ pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Resu
   // TODO: might be good to put the part above in one method, and below in another
   //       (which only gets called in apub::objects)
   //        -> no that doesnt make sense, we still need the code below for blocklist and strict allowlist
-  if let Some(blocked) = Settings::get().get_blocked_instances() {
+  if let Some(blocked) = Settings::get().federation.blocked_instances {
     if blocked.contains(&domain) {
       return Err(anyhow!("{} is in federation blocklist", domain).into());
     }
   }
 
-  if let Some(mut allowed) = Settings::get().get_allowed_instances() {
+  if let Some(mut allowed) = Settings::get().federation.allowed_instances {
     // Only check allowlist if this is a community, or strict allowlist is enabled.
-    let strict_allowlist = Settings::get()
-      .federation()
-      .strict_allowlist
-      .unwrap_or(true);
+    let strict_allowlist = Settings::get().federation.strict_allowlist;
     if use_strict_allowlist || strict_allowlist {
       // need to allow this explicitly because apub receive might contain objects from our local
       // instance.
index c4f57c51abdd3e9acc01933989e12419b07747e7..0a29f29a36855d2ffd1e1309f7ddb1ab49e7a1c1 100644 (file)
@@ -193,7 +193,7 @@ where
   let domain = object_id.domain().context(location_info!())?;
 
   // if its a local object, return it directly from the database
-  if Settings::get().hostname() == domain {
+  if Settings::get().hostname == domain {
     let object = blocking(context.pool(), move |conn| {
       To::read_from_apub_id(conn, &object_id.into())
     })
index 29935ec074adda9d3c16c3a3d9301b75f3234363..cbc80b505a7053124b79c33af35199f5af6f3065 100644 (file)
@@ -109,7 +109,7 @@ impl FromApub for DbPerson {
   ) -> Result<DbPerson, LemmyError> {
     let person_id = person.id_unchecked().context(location_info!())?.to_owned();
     let domain = person_id.domain().context(location_info!())?;
-    if domain == Settings::get().hostname() {
+    if domain == Settings::get().hostname {
       let person = blocking(context.pool(), move |conn| {
         DbPerson::read_from_apub_id(conn, &person_id.into())
       })
index 1f2172d16eb0353b749e71ef77674c686f53430a..2fdf8d67b5ce99f696438f17e281873816da70ee 100644 (file)
@@ -178,12 +178,14 @@ impl FromApub for Post {
     let community = extract_community(&page.to, context, request_counter).await?;
 
     let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
-    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
-      if let Some(url) = &page.url {
-        fetch_iframely_and_pictrs_data(context.client(), Some(url)).await
-      } else {
-        (None, None, None, thumbnail_url)
-      };
+    let (iframely_response, pictrs_thumbnail) = if let Some(url) = &page.url {
+      fetch_iframely_and_pictrs_data(context.client(), Some(url)).await?
+    } else {
+      (None, thumbnail_url)
+    };
+    let (embed_title, embed_description, embed_html) = iframely_response
+      .map(|u| (u.title, u.description, u.html))
+      .unwrap_or((None, None, None));
 
     let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content));
     let form = PostForm {
@@ -199,9 +201,9 @@ impl FromApub for Post {
       deleted: None,
       nsfw: page.sensitive,
       stickied: page.stickied,
-      embed_title: iframely_title,
-      embed_description: iframely_description,
-      embed_html: iframely_html,
+      embed_title,
+      embed_description,
+      embed_html,
       thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
       ap_id: Some(page.id.clone().into()),
       local: Some(false),
index ff2b838777ca041d3dbb8deca6020b4f86ad47bd..a89bde0f1c601cb89df97acd6de08b8ff568825d 100644 (file)
@@ -1,6 +1,7 @@
 use actix_web::{body::BodyStream, http::StatusCode, web::Data, *};
+use anyhow::anyhow;
 use awc::Client;
-use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings};
+use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings, LemmyError};
 use serde::{Deserialize, Serialize};
 use std::time::Duration;
 
@@ -54,10 +55,7 @@ async fn upload(
     return Ok(HttpResponse::Unauthorized().finish());
   };
 
-  let mut client_req = client.request_from(
-    format!("{}/image", Settings::get().pictrs_url()),
-    req.head(),
-  );
+  let mut client_req = client.request_from(format!("{}/image", pictrs_url()?), req.head());
 
   if let Some(addr) = req.head().peer_addr {
     client_req = client_req.insert_header(("X-Forwarded-For", addr.to_string()))
@@ -83,17 +81,12 @@ async fn full_res(
 
   // If there are no query params, the URL is original
   let url = if params.format.is_none() && params.thumbnail.is_none() {
-    format!("{}/image/original/{}", Settings::get().pictrs_url(), name,)
+    format!("{}/image/original/{}", pictrs_url()?, name,)
   } else {
     // Use jpg as a default when none is given
     let format = params.format.unwrap_or_else(|| "jpg".to_string());
 
-    let mut url = format!(
-      "{}/image/process.{}?src={}",
-      Settings::get().pictrs_url(),
-      format,
-      name,
-    );
+    let mut url = format!("{}/image/process.{}?src={}", pictrs_url()?, format, name,);
 
     if let Some(size) = params.thumbnail {
       url = format!("{}&thumbnail={}", url, size,);
@@ -141,12 +134,7 @@ async fn delete(
 ) -> Result<HttpResponse, Error> {
   let (token, file) = components.into_inner();
 
-  let url = format!(
-    "{}/image/delete/{}/{}",
-    Settings::get().pictrs_url(),
-    &token,
-    &file
-  );
+  let url = format!("{}/image/delete/{}/{}", pictrs_url()?, &token, &file);
 
   let mut client_req = client.request_from(url, req.head());
 
@@ -162,3 +150,9 @@ async fn delete(
 
   Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
 }
+
+fn pictrs_url() -> Result<String, LemmyError> {
+  Settings::get()
+    .pictrs_url
+    .ok_or_else(|| anyhow!("images_disabled").into())
+}
index d06f609283da55adcc58f743f169a15f18a7c1c9..86d625900db52599da54e6533af0b3370046794f 100644 (file)
@@ -31,7 +31,7 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
     .await?
     .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
 
-  let protocols = if Settings::get().federation().enabled {
+  let protocols = if Settings::get().federation.enabled {
     vec!["activitypub".to_string()]
   } else {
     vec![]
index 82c993daf4eab6d5212ea78dd5bd6ba351c2132b..5d4be74c4bf6e6c7a991d627bc3b3807c608cf5e 100644 (file)
@@ -18,7 +18,7 @@ struct Params {
 }
 
 pub fn config(cfg: &mut web::ServiceConfig) {
-  if Settings::get().federation().enabled {
+  if Settings::get().federation.enabled {
     cfg.route(
       ".well-known/webfinger",
       web::get().to(get_webfinger_response),
index 160689af1f4ac5acfc801aa4601f9ed84775e1a6..46921eca5926efec0bec9e8a4d11cb6fac103603 100644 (file)
@@ -35,5 +35,4 @@ diesel = "1.4.7"
 http = "0.2.4"
 jsonwebtoken = "7.2.0"
 deser-hjson = "1.0.1"
-merge = "0.1.0"
-envy = "0.4.2"
+smart-default = "0.6.0"
index 8cd5796ef4b594d9702f3deed2faa4a8f66e175c..e1c3ba158223010cf56b29a9c897830c7072e6bd 100644 (file)
@@ -22,7 +22,7 @@ impl Claims {
     };
     decode::<Claims>(
       jwt,
-      &DecodingKey::from_secret(Settings::get().jwt_secret().as_ref()),
+      &DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
       &v,
     )
   }
@@ -30,13 +30,13 @@ impl Claims {
   pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> {
     let my_claims = Claims {
       sub: local_user_id,
-      iss: Settings::get().hostname(),
+      iss: Settings::get().hostname,
       iat: Utc::now().timestamp(),
     };
     encode(
       &Header::default(),
       &my_claims,
-      &EncodingKey::from_secret(Settings::get().jwt_secret().as_ref()),
+      &EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
     )
   }
 }
index e2853fa49842c11afc058d068eaf7d0e9e80155d..3e5307421b40b54f3a8cf93796963b2eb737423e 100644 (file)
@@ -19,8 +19,8 @@ pub fn send_email(
   to_username: &str,
   html: &str,
 ) -> Result<(), String> {
-  let email_config = Settings::get().email().ok_or("no_email_setup")?;
-  let domain = Settings::get().hostname();
+  let email_config = Settings::get().email.ok_or("no_email_setup")?;
+  let domain = Settings::get().hostname;
 
   let (smtp_server, smtp_port) = {
     let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
index ca544bd49aed0375014a991ec160dbed6c244157..6bf3237e49daf3bd657dba1d9d093de4cac53d20 100644 (file)
@@ -2,6 +2,8 @@
 extern crate lazy_static;
 #[macro_use]
 extern crate strum_macros;
+#[macro_use]
+extern crate smart_default;
 
 pub mod apub;
 pub mod claims;
@@ -90,12 +92,12 @@ impl actix_web::error::ResponseError for LemmyError {
 lazy_static! {
   pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
     "^group:([a-z0-9_]{{3,}})@{}$",
-    Settings::get().hostname()
+    Settings::get().hostname
   ))
   .expect("compile webfinger regex");
   pub static ref WEBFINGER_USERNAME_REGEX: Regex = Regex::new(&format!(
     "^acct:([a-z0-9_]{{3,}})@{}$",
-    Settings::get().hostname()
+    Settings::get().hostname
   ))
   .expect("compile webfinger regex");
 }
index 5fc77d27da127b233f5cac167f657d3e705723e3..0701be25b2f9375041ded7a7d5788c92ca23c7e2 100644 (file)
@@ -71,7 +71,9 @@ impl RateLimited {
   {
     // Does not need to be blocking because the RwLock in settings never held across await points,
     // and the operation here locks only long enough to clone
-    let rate_limit: RateLimitConfig = Settings::get().rate_limit();
+    let rate_limit: RateLimitConfig = Settings::get()
+      .rate_limit
+      .unwrap_or_else(RateLimitConfig::default);
 
     // before
     {
index f14a6c1ac23c7bf941c3c80065ca01e573adbe80..c59180c556e8e53b51dfbc68ddea87dd4ab925ea 100644 (file)
@@ -48,26 +48,30 @@ where
 }
 
 #[derive(Deserialize, Debug)]
-pub(crate) struct IframelyResponse {
-  title: Option<String>,
-  description: Option<String>,
+pub struct IframelyResponse {
+  pub title: Option<String>,
+  pub description: Option<String>,
   thumbnail_url: Option<Url>,
-  html: Option<String>,
+  pub html: Option<String>,
 }
 
 pub(crate) async fn fetch_iframely(
   client: &Client,
   url: &Url,
 ) -> Result<IframelyResponse, LemmyError> {
-  let fetch_url = format!("{}/oembed?url={}", Settings::get().iframely_url(), url);
+  if let Some(iframely_url) = Settings::get().iframely_url {
+    let fetch_url = format!("{}/oembed?url={}", iframely_url, url);
 
-  let response = retry(|| client.get(&fetch_url).send()).await?;
+    let response = retry(|| client.get(&fetch_url).send()).await?;
 
-  let res: IframelyResponse = response
-    .json()
-    .await
-    .map_err(|e| RecvError(e.to_string()))?;
-  Ok(res)
+    let res: IframelyResponse = response
+      .json()
+      .await
+      .map_err(|e| RecvError(e.to_string()))?;
+    Ok(res)
+  } else {
+    Err(anyhow!("Missing Iframely URL in config.").into())
+  }
 }
 
 #[derive(Deserialize, Debug, Clone)]
@@ -85,91 +89,73 @@ pub(crate) struct PictrsFile {
 pub(crate) async fn fetch_pictrs(
   client: &Client,
   image_url: &Url,
-) -> Result<PictrsResponse, LemmyError> {
-  is_image_content_type(client, image_url).await?;
-
-  let fetch_url = format!(
-    "{}/image/download?url={}",
-    Settings::get().pictrs_url(),
-    utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
-  );
-
-  let response = retry(|| client.get(&fetch_url).send()).await?;
-
-  let response: PictrsResponse = response
-    .json()
-    .await
-    .map_err(|e| RecvError(e.to_string()))?;
-
-  if response.msg == "ok" {
-    Ok(response)
+) -> Result<Option<PictrsResponse>, LemmyError> {
+  if let Some(pictrs_url) = Settings::get().pictrs_url {
+    is_image_content_type(client, image_url).await?;
+
+    let fetch_url = format!(
+      "{}/image/download?url={}",
+      pictrs_url,
+      utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
+    );
+
+    let response = retry(|| client.get(&fetch_url).send()).await?;
+
+    let response: PictrsResponse = response
+      .json()
+      .await
+      .map_err(|e| RecvError(e.to_string()))?;
+
+    if response.msg == "ok" {
+      Ok(Some(response))
+    } else {
+      Err(anyhow!("{}", &response.msg).into())
+    }
   } else {
-    Err(anyhow!("{}", &response.msg).into())
+    Ok(None)
   }
 }
 
 pub async fn fetch_iframely_and_pictrs_data(
   client: &Client,
   url: Option<&Url>,
-) -> (Option<String>, Option<String>, Option<String>, Option<Url>) {
+) -> Result<(Option<IframelyResponse>, Option<Url>), LemmyError> {
   match &url {
     Some(url) => {
       // Fetch iframely data
-      let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
-        match fetch_iframely(client, url).await {
-          Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
-          Err(e) => {
-            error!("iframely err: {}", e);
-            (None, None, None, None)
-          }
-        };
+      let iframely_res_option = fetch_iframely(client, url).await.ok();
 
       // Fetch pictrs thumbnail
-      let pictrs_hash = match iframely_thumbnail_url {
-        Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
-          Ok(res) => Some(res.files[0].file.to_owned()),
-          Err(e) => {
-            error!("pictrs err: {}", e);
-            None
-          }
-        },
-        // Try to generate a small thumbnail if iframely is not supported
-        None => match fetch_pictrs(client, url).await {
-          Ok(res) => Some(res.files[0].file.to_owned()),
-          Err(e) => {
-            error!("pictrs err: {}", e);
-            None
-          }
+      let pictrs_hash = match &iframely_res_option {
+        Some(iframely_res) => match &iframely_res.thumbnail_url {
+          Some(iframely_thumbnail_url) => fetch_pictrs(client, iframely_thumbnail_url)
+            .await?
+            .map(|r| r.files[0].file.to_owned()),
+          // Try to generate a small thumbnail if iframely is not supported
+          None => fetch_pictrs(client, url)
+            .await?
+            .map(|r| r.files[0].file.to_owned()),
         },
+        None => fetch_pictrs(client, url)
+          .await?
+          .map(|r| r.files[0].file.to_owned()),
       };
 
       // The full urls are necessary for federation
-      let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
-        let url = Url::parse(&format!(
-          "{}/pictrs/image/{}",
-          Settings::get().get_protocol_and_hostname(),
-          pictrs_hash
-        ));
-        match url {
-          Ok(parsed_url) => Some(parsed_url),
-          Err(e) => {
-            // This really shouldn't happen unless the settings or hash are malformed
-            error!("Unexpected error constructing pictrs thumbnail URL: {}", e);
-            None
-          }
-        }
-      } else {
-        None
-      };
-
-      (
-        iframely_title,
-        iframely_description,
-        iframely_html,
-        pictrs_thumbnail,
-      )
+      let pictrs_thumbnail = pictrs_hash
+        .map(|p| {
+          Url::parse(&format!(
+            "{}/pictrs/image/{}",
+            Settings::get().get_protocol_and_hostname(),
+            p
+          ))
+          .ok()
+        })
+        .flatten();
+
+      Ok((iframely_res_option, pictrs_thumbnail))
     }
-    None => (None, None, None, None),
+    None => Ok((None, None)),
   }
 }
 
diff --git a/crates/utils/src/settings/defaults.rs b/crates/utils/src/settings/defaults.rs
deleted file mode 100644 (file)
index 1333ebe..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-use crate::settings::{CaptchaConfig, DatabaseConfig, FederationConfig, RateLimitConfig, Settings};
-use std::net::{IpAddr, Ipv4Addr};
-
-impl Default for Settings {
-  fn default() -> Self {
-    Self {
-      database: Some(DatabaseConfig::default()),
-      rate_limit: Some(RateLimitConfig::default()),
-      federation: Some(FederationConfig::default()),
-      captcha: Some(CaptchaConfig::default()),
-      email: None,
-      setup: None,
-      hostname: None,
-      bind: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
-      port: Some(8536),
-      tls_enabled: Some(true),
-      jwt_secret: Some("changeme".into()),
-      pictrs_url: Some("http://pictrs:8080".into()),
-      iframely_url: Some("http://iframely".into()),
-      additional_slurs: None,
-      actor_name_max_length: Some(20),
-    }
-  }
-}
-
-impl Default for DatabaseConfig {
-  fn default() -> Self {
-    Self {
-      user: Some("lemmy".to_string()),
-      password: "password".into(),
-      host: "localhost".into(),
-      port: Some(5432),
-      database: Some("lemmy".to_string()),
-      pool_size: Some(5),
-    }
-  }
-}
-
-impl Default for CaptchaConfig {
-  fn default() -> Self {
-    Self {
-      enabled: true,
-      difficulty: "medium".into(),
-    }
-  }
-}
-
-impl Default for FederationConfig {
-  fn default() -> Self {
-    Self {
-      enabled: false,
-      allowed_instances: None,
-      blocked_instances: None,
-      strict_allowlist: Some(true),
-    }
-  }
-}
-
-impl Default for RateLimitConfig {
-  fn default() -> Self {
-    Self {
-      message: 180,
-      message_per_second: 60,
-      post: 6,
-      post_per_second: 600,
-      register: 3,
-      register_per_second: 3600,
-      image: 6,
-      image_per_second: 3600,
-    }
-  }
-}
index b247bae93697ce9cdc76e66c441f8473b40f0f77..d4776326f698d50cce04c4378cb14122519b2dca 100644 (file)
@@ -1,22 +1,8 @@
-use crate::{
-  location_info,
-  settings::structs::{
-    CaptchaConfig,
-    DatabaseConfig,
-    EmailConfig,
-    FederationConfig,
-    RateLimitConfig,
-    Settings,
-    SetupConfig,
-  },
-  LemmyError,
-};
+use crate::{location_info, settings::structs::Settings, LemmyError};
 use anyhow::{anyhow, Context};
 use deser_hjson::from_str;
-use merge::Merge;
-use std::{env, fs, io::Error, net::IpAddr, sync::RwLock};
+use std::{env, fs, io::Error, sync::RwLock};
 
-pub mod defaults;
 pub mod structs;
 
 static CONFIG_FILE: &str = "config/config.hjson";
@@ -28,27 +14,18 @@ lazy_static! {
 
 impl Settings {
   /// Reads config from configuration file.
-  /// Then values from the environment (with prefix LEMMY) are added to the config.
-  /// And then default values are merged into config.
-  /// Defaults are controlled by Default trait implemntation for Settings structs.
   ///
   /// Note: The env var `LEMMY_DATABASE_URL` is parsed in
   /// `lemmy_db_queries/src/lib.rs::get_database_url_from_env()`
   fn init() -> Result<Self, LemmyError> {
     // Read the config file
-    let mut custom_config = from_str::<Settings>(&Self::read_config_file()?)?;
+    let config = from_str::<Settings>(&Self::read_config_file()?)?;
 
-    // Merge with env vars
-    custom_config.merge(envy::prefixed("LEMMY_").from_env::<Settings>()?);
-
-    // Merge with default
-    custom_config.merge(Settings::default());
-
-    if custom_config.hostname == Settings::default().hostname {
+    if config.hostname == "unset" {
       return Err(anyhow!("Hostname variable is not set!").into());
     }
 
-    Ok(custom_config)
+    Ok(config)
   }
 
   /// Returns the config as a struct.
@@ -57,14 +34,10 @@ impl Settings {
   }
 
   pub fn get_database_url(&self) -> String {
-    let conf = self.database();
+    let conf = &self.database;
     format!(
       "postgres://{}:{}@{}:{}/{}",
-      conf.user(),
-      conf.password,
-      conf.host,
-      conf.port(),
-      conf.database(),
+      conf.user, conf.password, conf.host, conf.port, conf.database,
     )
   }
 
@@ -76,22 +49,10 @@ impl Settings {
     fs::read_to_string(Self::get_config_location())
   }
 
-  pub fn get_allowed_instances(&self) -> Option<Vec<String>> {
-    self.federation().allowed_instances
-  }
-
-  pub fn get_blocked_instances(&self) -> Option<Vec<String>> {
-    self.federation().blocked_instances
-  }
-
   /// Returns either "http" or "https", depending on tls_enabled setting
   pub fn get_protocol_string(&self) -> &'static str {
-    if let Some(tls_enabled) = self.tls_enabled {
-      if tls_enabled {
-        "https"
-      } else {
-        "http"
-      }
+    if self.tls_enabled {
+      "https"
     } else {
       "http"
     }
@@ -100,7 +61,7 @@ impl Settings {
   /// Returns something like `http://localhost` or `https://lemmy.ml`,
   /// with the correct protocol and hostname.
   pub fn get_protocol_and_hostname(&self) -> String {
-    format!("{}://{}", self.get_protocol_string(), self.hostname())
+    format!("{}://{}", self.get_protocol_string(), self.hostname)
   }
 
   /// When running the federation test setup in `api_tests/` or `docker/federation`, the `hostname`
@@ -109,7 +70,7 @@ impl Settings {
   pub fn get_hostname_without_port(&self) -> Result<String, anyhow::Error> {
     Ok(
       self
-        .hostname()
+        .hostname
         .split(':')
         .collect::<Vec<&str>>()
         .first()
@@ -131,67 +92,4 @@ impl Settings {
 
     Ok(Self::read_config_file()?)
   }
-
-  pub fn hostname(&self) -> String {
-    self.hostname.to_owned().expect("No hostname given")
-  }
-  pub fn bind(&self) -> IpAddr {
-    self.bind.expect("return bind address")
-  }
-  pub fn port(&self) -> u16 {
-    self
-      .port
-      .unwrap_or_else(|| Settings::default().port.expect("missing port"))
-  }
-  pub fn tls_enabled(&self) -> bool {
-    self.tls_enabled.unwrap_or_else(|| {
-      Settings::default()
-        .tls_enabled
-        .expect("missing tls_enabled")
-    })
-  }
-  pub fn jwt_secret(&self) -> String {
-    self
-      .jwt_secret
-      .to_owned()
-      .unwrap_or_else(|| Settings::default().jwt_secret.expect("missing jwt_secret"))
-  }
-  pub fn pictrs_url(&self) -> String {
-    self
-      .pictrs_url
-      .to_owned()
-      .unwrap_or_else(|| Settings::default().pictrs_url.expect("missing pictrs_url"))
-  }
-  pub fn iframely_url(&self) -> String {
-    self.iframely_url.to_owned().unwrap_or_else(|| {
-      Settings::default()
-        .iframely_url
-        .expect("missing iframely_url")
-    })
-  }
-  pub fn actor_name_max_length(&self) -> usize {
-    self.actor_name_max_length.unwrap_or_else(|| {
-      Settings::default()
-        .actor_name_max_length
-        .expect("missing actor name length")
-    })
-  }
-  pub fn database(&self) -> DatabaseConfig {
-    self.database.to_owned().unwrap_or_default()
-  }
-  pub fn rate_limit(&self) -> RateLimitConfig {
-    self.rate_limit.to_owned().unwrap_or_default()
-  }
-  pub fn federation(&self) -> FederationConfig {
-    self.federation.to_owned().unwrap_or_default()
-  }
-  pub fn captcha(&self) -> CaptchaConfig {
-    self.captcha.to_owned().unwrap_or_default()
-  }
-  pub fn email(&self) -> Option<EmailConfig> {
-    self.email.to_owned()
-  }
-  pub fn setup(&self) -> Option<SetupConfig> {
-    self.setup.to_owned()
-  }
 }
index 1046bd147d6987b13bc8ebea543630cf70e52ffd..46dde919296c2520033b8138189877cb52437b15 100644 (file)
@@ -1,68 +1,65 @@
-use merge::Merge;
 use serde::Deserialize;
-use std::net::IpAddr;
+use std::net::{IpAddr, Ipv4Addr};
 
-#[derive(Debug, Deserialize, Clone, Merge)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
 pub struct Settings {
-  pub(crate) database: Option<DatabaseConfig>,
-  pub(crate) rate_limit: Option<RateLimitConfig>,
-  pub(crate) federation: Option<FederationConfig>,
-  pub(crate) hostname: Option<String>,
-  pub(crate) bind: Option<IpAddr>,
-  pub(crate) port: Option<u16>,
-  pub(crate) tls_enabled: Option<bool>,
-  pub(crate) jwt_secret: Option<String>,
-  pub(crate) pictrs_url: Option<String>,
-  pub(crate) iframely_url: Option<String>,
-  pub(crate) captcha: Option<CaptchaConfig>,
-  pub(crate) email: Option<EmailConfig>,
-  pub(crate) setup: Option<SetupConfig>,
-  pub(crate) additional_slurs: Option<String>,
-  pub(crate) actor_name_max_length: Option<usize>,
+  #[serde(default)]
+  pub database: DatabaseConfig,
+  #[default(Some(RateLimitConfig::default()))]
+  pub rate_limit: Option<RateLimitConfig>,
+  #[default(FederationConfig::default())]
+  pub federation: FederationConfig,
+  #[default(CaptchaConfig::default())]
+  pub captcha: CaptchaConfig,
+  #[default(None)]
+  pub email: Option<EmailConfig>,
+  #[default(None)]
+  pub setup: Option<SetupConfig>,
+  #[default("unset")]
+  pub hostname: String,
+  #[default(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))]
+  pub bind: IpAddr,
+  #[default(8536)]
+  pub port: u16,
+  #[default(true)]
+  pub tls_enabled: bool,
+  #[default("changeme")]
+  pub jwt_secret: String,
+  #[default(None)]
+  pub pictrs_url: Option<String>,
+  #[default(None)]
+  pub iframely_url: Option<String>,
+  #[default(None)]
+  pub additional_slurs: Option<String>,
+  #[default(20)]
+  pub actor_name_max_length: usize,
 }
 
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
 pub struct CaptchaConfig {
+  #[default(false)]
   pub enabled: bool,
+  #[default("medium")]
   pub difficulty: String,
 }
 
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
 pub struct DatabaseConfig {
-  pub(super) user: Option<String>,
+  #[default("lemmy")]
+  pub(super) user: String,
+  #[default("password")]
   pub password: String,
+  #[default("localhost")]
   pub host: String,
-  pub(super) port: Option<i32>,
-  pub(super) database: Option<String>,
-  pub(super) pool_size: Option<u32>,
-}
-
-impl DatabaseConfig {
-  pub fn user(&self) -> String {
-    self
-      .user
-      .to_owned()
-      .unwrap_or_else(|| DatabaseConfig::default().user.expect("missing user"))
-  }
-  pub fn port(&self) -> i32 {
-    self
-      .port
-      .unwrap_or_else(|| DatabaseConfig::default().port.expect("missing port"))
-  }
-  pub fn database(&self) -> String {
-    self.database.to_owned().unwrap_or_else(|| {
-      DatabaseConfig::default()
-        .database
-        .expect("missing database")
-    })
-  }
-  pub fn pool_size(&self) -> u32 {
-    self.pool_size.unwrap_or_else(|| {
-      DatabaseConfig::default()
-        .pool_size
-        .expect("missing pool_size")
-    })
-  }
+  #[default(5432)]
+  pub(super) port: i32,
+  #[default("lemmy")]
+  pub(super) database: String,
+  #[default(5)]
+  pub pool_size: u32,
 }
 
 #[derive(Debug, Deserialize, Clone)]
@@ -74,23 +71,37 @@ pub struct EmailConfig {
   pub use_tls: bool,
 }
 
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
 pub struct FederationConfig {
+  #[default(false)]
   pub enabled: bool,
+  #[default(None)]
   pub allowed_instances: Option<Vec<String>>,
+  #[default(None)]
   pub blocked_instances: Option<Vec<String>>,
-  pub strict_allowlist: Option<bool>,
+  #[default(true)]
+  pub strict_allowlist: bool,
 }
 
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, SmartDefault)]
+#[serde(default)]
 pub struct RateLimitConfig {
+  #[default(180)]
   pub message: i32,
+  #[default(60)]
   pub message_per_second: i32,
+  #[default(6)]
   pub post: i32,
+  #[default(600)]
   pub post_per_second: i32,
+  #[default(3)]
   pub register: i32,
+  #[default(3600)]
   pub register_per_second: i32,
+  #[default(6)]
   pub image: i32,
+  #[default(3600)]
   pub image_per_second: i32,
 }
 
index d6e1f25d4c2b34dc8a470e47346149f50cd504c3..1254aff0a9fd016227174ea934235e5e16685669 100644 (file)
@@ -97,7 +97,7 @@ pub struct MentionData {
 
 impl MentionData {
   pub fn is_local(&self) -> bool {
-    Settings::get().hostname().eq(&self.domain)
+    Settings::get().hostname.eq(&self.domain)
   }
   pub fn full_name(&self) -> String {
     format!("@{}@{}", &self.name, &self.domain)
@@ -116,7 +116,7 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
 }
 
 pub fn is_valid_actor_name(name: &str) -> bool {
-  name.chars().count() <= Settings::get().actor_name_max_length()
+  name.chars().count() <= Settings::get().actor_name_max_length
     && VALID_ACTOR_NAME_REGEX.is_match(name)
 }
 
@@ -125,7 +125,7 @@ pub fn is_valid_display_name(name: &str) -> bool {
   !name.starts_with('@')
     && !name.starts_with('\u{200b}')
     && name.chars().count() >= 3
-    && name.chars().count() <= Settings::get().actor_name_max_length()
+    && name.chars().count() <= Settings::get().actor_name_max_length
 }
 
 pub fn is_valid_matrix_id(matrix_id: &str) -> bool {
index 8076fb3653124cd8d40fde4be02516110a6f43b8..6b4ac3697be150e09751a737a856cee85d119364 100644 (file)
@@ -38,7 +38,7 @@ async fn main() -> Result<(), LemmyError> {
   };
   let manager = ConnectionManager::<PgConnection>::new(&db_url);
   let pool = Pool::builder()
-    .max_size(settings.database().pool_size())
+    .max_size(settings.database.pool_size)
     .build(manager)
     .unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
 
@@ -62,8 +62,7 @@ async fn main() -> Result<(), LemmyError> {
 
   println!(
     "Starting http server at {}:{}",
-    settings.bind(),
-    settings.port()
+    settings.bind, settings.port
   );
 
   let activity_queue = create_activity_queue();
@@ -97,7 +96,7 @@ async fn main() -> Result<(), LemmyError> {
       .configure(nodeinfo::config)
       .configure(webfinger::config)
   })
-  .bind((settings.bind(), settings.port()))?
+  .bind((settings.bind, settings.port))?
   .run()
   .await?;