From 8ce70eadaceec93174cf9dacf525ee446badc56c Mon Sep 17 00:00:00 2001 From: "Dotan J. Nahum" Date: Thu, 31 Oct 2024 12:40:27 +0200 Subject: [PATCH] remove lazy_static (#941) * remove lazy_static --- Cargo.toml | 2 - examples/demo/Cargo.lock | 2 - examples/demo/src/app.rs | 4 +- examples/demo/tests/requests/notes.rs | 9 +- loco-gen/Cargo.toml | 1 - loco-gen/src/lib.rs | 11 +-- loco-gen/src/model.rs | 8 +- loco-gen/src/scaffold.rs | 9 +- src/config.rs | 11 +-- src/controller/app_routes.rs | 11 +-- src/controller/backtrace.rs | 91 ++++++++++++-------- src/controller/describe.rs | 11 ++- src/controller/middleware/powered_by.rs | 15 ++-- src/controller/middleware/remote_ip.rs | 37 ++++---- src/controller/middleware/request_id.rs | 12 +-- src/controller/middleware/secure_headers.rs | 15 ++-- src/db.rs | 20 ++--- src/doctor.rs | 13 +-- src/scheduler.rs | 9 +- src/testing.rs | 94 +++++++++++++-------- xtask/Cargo.toml | 1 - xtask/src/bump_version.rs | 33 ++++---- 22 files changed, 242 insertions(+), 177 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc67cb384..5f1acd055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,6 @@ async-trait = { workspace = true } axum = { workspace = true } axum-extra = { version = "0.9", features = ["cookie"] } regex = { workspace = true } -lazy_static = { workspace = true } fs-err = "2.11.0" # mailer tera = "1.19.1" @@ -143,7 +142,6 @@ regex = "1" thiserror = "1" serde = "1" serde_json = "1" -lazy_static = "1.4.0" async-trait = { version = "0.1.74" } axum = { version = "0.7.5", features = ["macros"] } tower = "0.4" diff --git a/examples/demo/Cargo.lock b/examples/demo/Cargo.lock index 44152ae66..51b6663af 100644 --- a/examples/demo/Cargo.lock +++ b/examples/demo/Cargo.lock @@ -2608,7 +2608,6 @@ dependencies = [ "clap", "dialoguer", "duct", - "lazy_static", "regex", "rrgen", "serde", @@ -2644,7 +2643,6 @@ dependencies = [ "include_dir", "ipnetwork", "jsonwebtoken", - "lazy_static", "lettre", "loco-gen", "mime", diff --git a/examples/demo/src/app.rs b/examples/demo/src/app.rs index b2ba52f48..6649c3223 100644 --- a/examples/demo/src/app.rs +++ b/examples/demo/src/app.rs @@ -42,8 +42,8 @@ impl Hooks for App { } // - async fn initializers(ctx: &AppContext) -> Result>> { - let mut initializers: Vec> = vec![ + async fn initializers(_ctx: &AppContext) -> Result>> { + let initializers: Vec> = vec![ Box::new(initializers::axum_session::AxumSessionInitializer), Box::new(initializers::view_engine::ViewEngineInitializer), Box::new(initializers::hello_view_engine::HelloViewEngineInitializer), diff --git a/examples/demo/tests/requests/notes.rs b/examples/demo/tests/requests/notes.rs index 8b7352ef4..ec7ec2f83 100644 --- a/examples/demo/tests/requests/notes.rs +++ b/examples/demo/tests/requests/notes.rs @@ -3,7 +3,6 @@ use insta::{assert_debug_snapshot, with_settings}; use loco_rs::testing; use rstest::rstest; use sea_orm::entity::prelude::*; -use serde_json; use serial_test::serial; // TODO: see how to dedup / extract this to app-local test utils @@ -34,7 +33,7 @@ async fn can_get_notes(#[case] test_name: &str, #[case] params: serde_json::Valu with_settings!({ filters => { - let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + let mut combined_filters = testing::get_cleanup_date().clone(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } @@ -62,7 +61,7 @@ async fn can_add_note() { with_settings!({ filters => { - let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + let mut combined_filters = testing::get_cleanup_date().clone(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } @@ -87,7 +86,7 @@ async fn can_get_note() { with_settings!({ filters => { - let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + let mut combined_filters = testing::get_cleanup_date().clone(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } @@ -113,7 +112,7 @@ async fn can_delete_note() { with_settings!({ filters => { - let mut combined_filters = testing::CLEANUP_DATE.to_vec(); + let mut combined_filters = testing::get_cleanup_date().clone(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } diff --git a/loco-gen/Cargo.toml b/loco-gen/Cargo.toml index 07038d80a..2f14271ca 100644 --- a/loco-gen/Cargo.toml +++ b/loco-gen/Cargo.toml @@ -14,7 +14,6 @@ path = "src/lib.rs" [dependencies] -lazy_static = { workspace = true } rrgen = "0.5.3" serde = { workspace = true } serde_json = { workspace = true } diff --git a/loco-gen/src/lib.rs b/loco-gen/src/lib.rs index c59ce1e09..2f5f11daa 100644 --- a/loco-gen/src/lib.rs +++ b/loco-gen/src/lib.rs @@ -2,7 +2,6 @@ // TODO: should be more properly aligned with extracting out the db-related gen // code and then feature toggling it #![allow(dead_code)] -use lazy_static::lazy_static; use rrgen::{GenResult, RRgen}; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -14,7 +13,7 @@ mod model; mod scaffold; #[cfg(test)] mod testutil; -use std::str::FromStr; +use std::{str::FromStr, sync::OnceLock}; const CONTROLLER_T: &str = include_str!("templates/controller.t"); const CONTROLLER_TEST_T: &str = include_str!("templates/request_test.t"); @@ -109,11 +108,13 @@ impl Mappings { } } -lazy_static! { - static ref MAPPINGS: Mappings = { +static MAPPINGS: OnceLock = OnceLock::new(); + +fn get_mappings() -> &'static Mappings { + MAPPINGS.get_or_init(|| { let json_data = include_str!("./mappings.json"); serde_json::from_str(json_data).expect("JSON was not well-formatted") - }; + }) } #[derive(clap::ValueEnum, Clone, Debug)] diff --git a/loco-gen/src/model.rs b/loco-gen/src/model.rs index 8b91ef241..a689bc126 100644 --- a/loco-gen/src/model.rs +++ b/loco-gen/src/model.rs @@ -6,11 +6,12 @@ use rrgen::RRgen; use serde_json::json; use super::{Error, Result}; +use crate::get_mappings; const MODEL_T: &str = include_str!("templates/model.t"); const MODEL_TEST_T: &str = include_str!("templates/model_test.t"); -use super::{collect_messages, AppInfo, MAPPINGS}; +use super::{collect_messages, AppInfo}; /// skipping some fields from the generated models. /// For example, the `created_at` and `updated_at` fields are automatically @@ -44,11 +45,12 @@ pub fn generate( // user, user_id references.push((fname, fkey)); } else { - let schema_type = MAPPINGS.schema_field(ftype.as_str()).ok_or_else(|| { + let mappings = get_mappings(); + let schema_type = mappings.schema_field(ftype.as_str()).ok_or_else(|| { Error::Message(format!( "type: {} not found. try any of: {:?}", ftype, - MAPPINGS.schema_fields() + mappings.schema_fields() )) })?; columns.push((fname.to_string(), schema_type.as_str())); diff --git a/loco-gen/src/scaffold.rs b/loco-gen/src/scaffold.rs index d057f482b..bf4d945ad 100644 --- a/loco-gen/src/scaffold.rs +++ b/loco-gen/src/scaffold.rs @@ -1,7 +1,7 @@ use rrgen::RRgen; use serde_json::json; -use crate as gen; +use crate::{self as gen, get_mappings}; const API_CONTROLLER_SCAFFOLD_T: &str = include_str!("templates/scaffold/api/controller.t"); const API_CONTROLLER_TEST_T: &str = include_str!("templates/scaffold/api/test.t"); @@ -22,7 +22,7 @@ const HTML_VIEW_CREATE_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/ const HTML_VIEW_SHOW_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/view_show.t"); const HTML_VIEW_LIST_SCAFFOLD_T: &str = include_str!("templates/scaffold/html/view_list.t"); -use super::{collect_messages, model, AppInfo, Error, Result, MAPPINGS}; +use super::{collect_messages, model, AppInfo, Error, Result}; pub fn generate( rrgen: &RRgen, @@ -35,6 +35,7 @@ pub fn generate( // - never run with migration_only, because the controllers will refer to the // models. the models only arrive after migration and entities sync. let model_messages = model::generate(rrgen, name, false, false, fields, appinfo)?; + let mappings = get_mappings(); let mut columns = Vec::new(); for (fname, ftype) in fields { @@ -46,11 +47,11 @@ pub fn generate( continue; } if ftype != "references" { - let schema_type = MAPPINGS.rust_field(ftype.as_str()).ok_or_else(|| { + let schema_type = mappings.rust_field(ftype.as_str()).ok_or_else(|| { Error::Message(format!( "type: {} not found. try any of: {:?}", ftype, - MAPPINGS.rust_fields() + mappings.rust_fields() )) })?; columns.push((fname.to_string(), schema_type.as_str(), ftype)); diff --git a/src/config.rs b/src/config.rs index 0b5322e31..e3faec3e4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,20 +24,21 @@ Notes: use std::{ collections::BTreeMap, path::{Path, PathBuf}, + sync::OnceLock, }; use fs_err as fs; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::info; use crate::{controller::middleware, environment::Environment, logger, scheduler, Error, Result}; -lazy_static! { - static ref DEFAULT_FOLDER: PathBuf = PathBuf::from("config"); -} +static DEFAULT_FOLDER: OnceLock = OnceLock::new(); +fn get_default_folder() -> &'static PathBuf { + DEFAULT_FOLDER.get_or_init(|| PathBuf::from("config")) +} /// Main application configuration structure. /// /// This struct encapsulates various configuration settings. The configuration @@ -496,7 +497,7 @@ impl Config { /// Config::new(environment).expect("configuration loading") /// } pub fn new(env: &Environment) -> Result { - let config = Self::from_folder(env, DEFAULT_FOLDER.as_path())?; + let config = Self::from_folder(env, get_default_folder().as_path())?; Ok(config) } diff --git a/src/controller/app_routes.rs b/src/controller/app_routes.rs index 5031aaaeb..32b6cfa5e 100644 --- a/src/controller/app_routes.rs +++ b/src/controller/app_routes.rs @@ -2,10 +2,9 @@ //! configuring routes in an Axum application. It allows you to define route //! prefixes, add routes, and configure middlewares for the application. -use std::fmt; +use std::{fmt, sync::OnceLock}; use axum::Router as AXRouter; -use lazy_static::lazy_static; use regex::Regex; #[cfg(feature = "channels")] @@ -16,8 +15,10 @@ use crate::{ Result, }; -lazy_static! { - static ref NORMALIZE_URL: Regex = Regex::new(r"/+").unwrap(); +static NORMALIZE_URL: OnceLock = OnceLock::new(); + +fn get_normalize_url() -> &'static Regex { + NORMALIZE_URL.get_or_init(|| Regex::new(r"/+").unwrap()) } /// Represents the routes of the application. @@ -91,7 +92,7 @@ impl AppRoutes { parts.push(handler.uri.to_string()); let joined_parts = parts.join("/"); - let normalized = NORMALIZE_URL.replace_all(&joined_parts, "/"); + let normalized = get_normalize_url().replace_all(&joined_parts, "/"); let uri = if normalized == "/" { normalized.to_string() } else { diff --git a/src/controller/backtrace.rs b/src/controller/backtrace.rs index 778d745f4..10df3f7a3 100644 --- a/src/controller/backtrace.rs +++ b/src/controller/backtrace.rs @@ -1,44 +1,67 @@ -use lazy_static::lazy_static; +use std::sync::OnceLock; + use regex::Regex; use crate::{Error, Result}; -lazy_static! { - static ref NAME_BLOCKLIST: Vec = [ - "^___rust_try", - "^__pthread", - "^__clone", - "^>(); - static ref FILE_BLOCKLIST: Vec = ["axum-.*$", "tower-.*$", "hyper-.*$", "tokio-.*$", "futures-.*$", "^/rustc"] +static NAME_BLOCKLIST: OnceLock> = OnceLock::new(); +static FILE_BLOCKLIST: OnceLock> = OnceLock::new(); + +fn get_name_blocklist() -> &'static Vec { + NAME_BLOCKLIST.get_or_init(|| { + [ + "^___rust_try", + "^__pthread", + "^__clone", + "^>() + }) +} + +fn get_file_blocklist() -> &'static Vec { + FILE_BLOCKLIST.get_or_init(|| { + [ + "axum-.*$", + "tower-.*$", + "hyper-.*$", + "tokio-.*$", + "futures-.*$", + "^/rustc", + ] .iter() .map(|s| Regex::new(s).unwrap()) - .collect::>(); + .collect::>() + }) } pub fn print_backtrace(bt: &std::backtrace::Backtrace) -> Result<()> { - backtrace_printer::print_backtrace(&mut std::io::stdout(), bt, &NAME_BLOCKLIST, &FILE_BLOCKLIST) - .map_err(Error::msg) + backtrace_printer::print_backtrace( + &mut std::io::stdout(), + bt, + get_name_blocklist(), + get_file_blocklist(), + ) + .map_err(Error::msg) } diff --git a/src/controller/describe.rs b/src/controller/describe.rs index 43aec5b29..dc168cf35 100644 --- a/src/controller/describe.rs +++ b/src/controller/describe.rs @@ -1,11 +1,14 @@ +use std::sync::OnceLock; + use axum::{http, routing::MethodRouter}; -use lazy_static::lazy_static; use regex::Regex; use crate::app::AppContext; -lazy_static! { - static ref DESCRIBE_METHOD_ACTION: Regex = Regex::new(r"\b(\w+):\s*BoxedHandler\b").unwrap(); +static DESCRIBE_METHOD_ACTION: OnceLock = OnceLock::new(); + +fn get_describe_method_action() -> &'static Regex { + DESCRIBE_METHOD_ACTION.get_or_init(|| Regex::new(r"\b(\w+):\s*BoxedHandler\b").unwrap()) } /// Extract the allow list method actions from [`MethodRouter`]. @@ -16,7 +19,7 @@ lazy_static! { pub fn method_action(method: &MethodRouter) -> Vec { let method_str = format!("{method:?}"); - DESCRIBE_METHOD_ACTION + get_describe_method_action() .captures(&method_str) .and_then(|captures| captures.get(1).map(|m| m.as_str().to_lowercase())) .and_then(|method_name| match method_name.as_str() { diff --git a/src/controller/middleware/powered_by.rs b/src/controller/middleware/powered_by.rs index 0656a93de..5cb2ce01f 100644 --- a/src/controller/middleware/powered_by.rs +++ b/src/controller/middleware/powered_by.rs @@ -6,6 +6,8 @@ //! custom identifier string or defaults to "loco.rs" if no identifier is //! provided. +use std::sync::OnceLock; + use axum::{ http::header::{HeaderName, HeaderValue}, Router as AXRouter, @@ -14,9 +16,10 @@ use tower_http::set_header::SetResponseHeaderLayer; use crate::{app::AppContext, controller::middleware::MiddlewareLayer, Result}; -lazy_static::lazy_static! { - static ref DEFAULT_IDENT_HEADER_VALUE: HeaderValue = - HeaderValue::from_static("loco.rs"); +static DEFAULT_IDENT_HEADER_VALUE: OnceLock = OnceLock::new(); + +fn get_default_ident_header_value() -> &'static HeaderValue { + DEFAULT_IDENT_HEADER_VALUE.get_or_init(|| HeaderValue::from_static("loco.rs")) } /// [`Middleware`] struct responsible for managing the identifier value for the @@ -31,7 +34,7 @@ pub struct Middleware { #[must_use] pub fn new(ident: Option<&str>) -> Middleware { let ident_value = ident.map_or_else( - || Some(DEFAULT_IDENT_HEADER_VALUE.clone()), + || Some(get_default_ident_header_value().clone()), |ident| { if ident.is_empty() { None @@ -44,7 +47,7 @@ pub fn new(ident: Option<&str>) -> Middleware { val = ident, "could not set custom ident header" ); - Some(DEFAULT_IDENT_HEADER_VALUE.clone()) + Some(get_default_ident_header_value().clone()) } } } @@ -79,7 +82,7 @@ impl MiddlewareLayer for Middleware { HeaderName::from_static("x-powered-by"), self.ident .clone() - .unwrap_or_else(|| DEFAULT_IDENT_HEADER_VALUE.clone()), + .unwrap_or_else(|| get_default_ident_header_value().clone()), ))) } } diff --git a/src/controller/middleware/remote_ip.rs b/src/controller/middleware/remote_ip.rs index a4a71bf95..ce2415918 100644 --- a/src/controller/middleware/remote_ip.rs +++ b/src/controller/middleware/remote_ip.rs @@ -13,6 +13,7 @@ use std::{ iter::Iterator, net::{IpAddr, SocketAddr}, str::FromStr, + sync::OnceLock, task::{Context, Poll}, }; @@ -27,28 +28,30 @@ use axum::{ use futures_util::future::BoxFuture; use hyper::HeaderMap; use ipnetwork::IpNetwork; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use tower::{Layer, Service}; use tracing::error; use crate::{app::AppContext, controller::middleware::MiddlewareLayer, Error, Result}; -lazy_static! { -// matching what Rails does is probably a smart idea: -// https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L40 -static ref LOCAL_TRUSTED_PROXIES: Vec = [ - "127.0.0.0/8", // localhost IPv4 range, per RFC-3330 - "::1", // localhost IPv6 - "fc00::/7", // private IPv6 range fc00::/7 - "10.0.0.0/8", // private IPv4 range 10.x.x.x - "172.16.0.0/12", // private IPv4 range 172.16.0.0 .. 172.31.255.255 - "192.168.0.0/16" - ] - .iter() - .map(|ip| IpNetwork::from_str(ip).unwrap()) - .collect(); +static LOCAL_TRUSTED_PROXIES: OnceLock> = OnceLock::new(); + +fn get_local_trusted_proxies() -> &'static Vec { + LOCAL_TRUSTED_PROXIES.get_or_init(|| { + [ + "127.0.0.0/8", // localhost IPv4 range, per RFC-3330 + "::1", // localhost IPv6 + "fc00::/7", // private IPv6 range fc00::/7 + "10.0.0.0/8", // private IPv4 range 10.x.x.x + "172.16.0.0/12", // private IPv4 range 172.16.0.0 .. 172.31.255.255 + "192.168.0.0/16", + ] + .iter() + .map(|ip| IpNetwork::from_str(ip).unwrap()) + .collect() + }) } + const X_FORWARDED_FOR: &str = "X-Forwarded-For"; /// @@ -160,7 +163,9 @@ fn maybe_get_forwarded( */ .filter(|ip| { // trusted proxies provided REPLACES our default local proxies - let proxies = trusted_proxies.as_ref().unwrap_or(&LOCAL_TRUSTED_PROXIES); + let proxies = trusted_proxies + .as_ref() + .unwrap_or_else(|| get_local_trusted_proxies()); !proxies .iter() .any(|trusted_proxy| trusted_proxy.contains(*ip)) diff --git a/src/controller/middleware/request_id.rs b/src/controller/middleware/request_id.rs index eb548d10f..d0f1740f5 100644 --- a/src/controller/middleware/request_id.rs +++ b/src/controller/middleware/request_id.rs @@ -9,7 +9,6 @@ use axum::{ extract::Request, http::HeaderValue, middleware::Next, response::Response, Router as AXRouter, }; -use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -19,10 +18,13 @@ use crate::{app::AppContext, controller::middleware::MiddlewareLayer, Result}; const X_REQUEST_ID: &str = "x-request-id"; const MAX_LEN: usize = 255; -lazy_static! { - static ref ID_CLEANUP: Regex = Regex::new(r"[^\w\-@]").unwrap(); -} +use std::sync::OnceLock; + +static ID_CLEANUP: OnceLock = OnceLock::new(); +fn get_id_cleanup() -> &'static Regex { + ID_CLEANUP.get_or_init(|| Regex::new(r"[^\w\-@]").unwrap()) +} #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RequestId { #[serde(default)] @@ -97,7 +99,7 @@ fn make_request_id(maybe_request_id: Option) -> String { .and_then(|hdr| { // see: https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/request_id.rb#L39 let id: Option = hdr.to_str().ok().map(|s| { - ID_CLEANUP + get_id_cleanup() .replace_all(s, "") .chars() .take(MAX_LEN) diff --git a/src/controller/middleware/secure_headers.rs b/src/controller/middleware/secure_headers.rs index 8fdda57bc..a899395d2 100644 --- a/src/controller/middleware/secure_headers.rs +++ b/src/controller/middleware/secure_headers.rs @@ -5,6 +5,7 @@ use std::{ collections::{BTreeMap, HashMap}, + sync::OnceLock, task::{Context, Poll}, }; @@ -15,19 +16,19 @@ use axum::{ Router as AXRouter, }; use futures_util::future::BoxFuture; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use serde_json::{self, json}; use tower::{Layer, Service}; use crate::{app::AppContext, controller::middleware::MiddlewareLayer, Error, Result}; -lazy_static! { - /// Predefined secure header presets loaded from `secure_headers.json` - static ref PRESETS: HashMap> = - serde_json::from_str(include_str!("secure_headers.json")).unwrap(); +static PRESETS: OnceLock>> = OnceLock::new(); +fn get_presets() -> &'static HashMap> { + PRESETS.get_or_init(|| { + let json_data = include_str!("secure_headers.json"); + serde_json::from_str(json_data).unwrap() + }) } - /// Sets a predefined or custom set of secure headers. /// /// We recommend our `github` preset. Presets values are derived @@ -123,7 +124,7 @@ impl SecureHeader { let mut headers = vec![]; let preset = &self.preset; - let p = PRESETS.get(preset).ok_or_else(|| { + let p = get_presets().get(preset).ok_or_else(|| { Error::Message(format!( "secure_headers: a preset named `{preset}` does not exist" )) diff --git a/src/db.rs b/src/db.rs index 2bf7ca6dc..a87fbc8da 100644 --- a/src/db.rs +++ b/src/db.rs @@ -3,11 +3,10 @@ //! This module defines functions and operations related to the application's //! database interactions. -use std::{collections::HashMap, fs::File, path::Path, time::Duration}; +use std::{collections::HashMap, fs::File, path::Path, sync::OnceLock, time::Duration}; use duct::cmd; use fs_err as fs; -use lazy_static::lazy_static; use regex::Regex; use sea_orm::{ ActiveModelTrait, ConnectOptions, ConnectionTrait, Database, DatabaseBackend, @@ -23,13 +22,10 @@ use crate::{ errors::Error, }; -lazy_static! { - // Getting the table name from the environment configuration. - // For example: - // postgres://loco:loco@localhost:5432/loco_app - // mysql://loco:loco@localhost:3306/loco_app - // the results will be loco_app - pub static ref EXTRACT_DB_NAME: Regex = Regex::new(r"/([^/]+)$").unwrap(); +pub static EXTRACT_DB_NAME: OnceLock = OnceLock::new(); + +fn get_extract_db_name() -> &'static Regex { + EXTRACT_DB_NAME.get_or_init(|| Regex::new(r"/([^/]+)$").unwrap()) } #[derive(Default, Clone, Debug)] @@ -175,7 +171,7 @@ pub async fn create(db_uri: &str) -> AppResult<()> { "Only Postgres databases are supported for table creation", )); } - let db_name = EXTRACT_DB_NAME + let db_name = get_extract_db_name() .captures(db_uri) .and_then(|cap| cap.get(1).map(|db| db.as_str())) .ok_or_else(|| { @@ -184,7 +180,9 @@ pub async fn create(db_uri: &str) -> AppResult<()> { ) })?; - let conn = EXTRACT_DB_NAME.replace(db_uri, "/postgres").to_string(); + let conn = get_extract_db_name() + .replace(db_uri, "/postgres") + .to_string(); let db = Database::connect(conn).await?; Ok(create_postgres_database(db_name, &db).await?) diff --git a/src/doctor.rs b/src/doctor.rs index c576cc922..67707992f 100644 --- a/src/doctor.rs +++ b/src/doctor.rs @@ -1,10 +1,10 @@ use std::{ collections::{BTreeMap, HashMap}, process::Command, + sync::OnceLock, }; use colored::Colorize; -use lazy_static::lazy_static; use regex::Regex; use semver::Version; @@ -26,8 +26,10 @@ const QUEUE_NOT_CONFIGURED: &str = "queue not configured?"; // versions health const MIN_SEAORMCLI_VER: &str = "1.1.0"; -lazy_static! { - static ref MIN_DEP_VERSIONS: HashMap<&'static str, &'static str> = { +static MIN_DEP_VERSIONS: OnceLock> = OnceLock::new(); + +fn get_min_dep_versions() -> &'static HashMap<&'static str, &'static str> { + MIN_DEP_VERSIONS.get_or_init(|| { let mut min_vers = HashMap::new(); min_vers.insert("tokio", "1.33.0"); @@ -36,7 +38,7 @@ lazy_static! { min_vers.insert("axum", "0.7.5"); min_vers - }; + }) } /// Represents different resources that can be checked. @@ -137,7 +139,8 @@ pub async fn run_all(config: &Config, production: bool) -> Result Result { let cargolock = fs_err::read_to_string("Cargo.lock")?; - let crate_statuses = depcheck::check_crate_versions(&cargolock, MIN_DEP_VERSIONS.clone())?; + let crate_statuses = + depcheck::check_crate_versions(&cargolock, get_min_dep_versions().clone())?; let mut report = String::new(); report.push_str("Dependencies\n"); let mut all_ok = true; diff --git a/src/scheduler.rs b/src/scheduler.rs index 25c9656c0..367e04b77 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -5,6 +5,7 @@ use std::{ collections::HashMap, fmt, io, path::{Path, PathBuf}, + sync::OnceLock, time::Instant, }; @@ -14,8 +15,10 @@ use tokio_cron_scheduler::{JobScheduler, JobSchedulerError}; use crate::{app::Hooks, environment::Environment, task::Tasks}; -lazy_static::lazy_static! { - static ref RE_IS_CRON_SYNTAX: Regex = Regex::new(r"^[\*\d]").unwrap(); +static RE_IS_CRON_SYNTAX: OnceLock = OnceLock::new(); + +fn get_re_is_cron_syntax() -> &'static Regex { + RE_IS_CRON_SYNTAX.get_or_init(|| Regex::new(r"^[\*\d]").unwrap()) } /// Errors that may occur while operating the scheduler. @@ -291,7 +294,7 @@ impl Scheduler { let job_description = job.prepare_command(&self.binary_path, &self.default_output, &self.environment); - let cron_syntax = if RE_IS_CRON_SYNTAX.is_match(&job.cron) { + let cron_syntax = if get_re_is_cron_syntax().is_match(&job.cron) { job.cron.clone() } else { english_to_cron::str_cron_syntax(&job.cron).map_err(|err| { diff --git a/src/testing.rs b/src/testing.rs index 6d41f815e..46dce0093 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -4,8 +4,9 @@ //! purposes, including cleaning up data patterns and bootstrapping the //! application for testing. +use std::sync::OnceLock; + use axum_test::{TestServer, TestServerConfig}; -use lazy_static::lazy_static; #[cfg(feature = "with-db")] use sea_orm::DatabaseConnection; @@ -16,38 +17,59 @@ use crate::{ Result, }; -// Lazy-static constants for data cleanup patterns -lazy_static! { - /// Constants for cleaning up user model data, replacing certain patterns with placeholders. - pub static ref CLEANUP_USER_MODEL: Vec<(&'static str, &'static str)> = vec![ - ( - r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", - "PID" - ), - (r"password: (.*{60}),", "password: \"PASSWORD\","), - (r"([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)","TOKEN") - ]; - - /// Constants for cleaning up date data, replacing date-time patterns with placeholders. - pub static ref CLEANUP_DATE: Vec<(&'static str, &'static str)> = +static CLEANUP_USER_MODEL: OnceLock> = OnceLock::new(); +static CLEANUP_DATE: OnceLock> = OnceLock::new(); +static CLEANUP_MODEL: OnceLock> = OnceLock::new(); +static CLEANUP_MAIL: OnceLock> = OnceLock::new(); + +pub fn get_cleanup_user_model() -> &'static Vec<(&'static str, &'static str)> { + CLEANUP_USER_MODEL.get_or_init(|| { + vec![ + ( + r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", + "PID", + ), + (r"password: (.*{60}),", "password: \"PASSWORD\","), + (r"([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)", "TOKEN"), + ] + }) +} + +pub fn get_cleanup_date() -> &'static Vec<(&'static str, &'static str)> { + CLEANUP_DATE.get_or_init(|| { + vec![ + ( + r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?\+\d{2}:\d{2}", + "DATE", + ), // with tz + (r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+", "DATE"), + (r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})", "DATE"), + ] + }) +} + +pub fn get_cleanup_model() -> &'static Vec<(&'static str, &'static str)> { + CLEANUP_MODEL.get_or_init(|| vec![(r"id: \d+,", "id: ID")]) +} + +pub fn get_cleanup_mail() -> &'static Vec<(&'static str, &'static str)> { + CLEANUP_MAIL.get_or_init(|| { vec![ - (r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?\+\d{2}:\d{2}", "DATE"), // with tz - (r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+", "DATE"), - (r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})", "DATE") - ]; - - /// Constants for cleaning up generals model data, replacing IDs with placeholders. - pub static ref CLEANUP_MODEL: Vec<(&'static str, &'static str)> = vec![(r"id: \d+,", "id: ID")]; - pub static ref CLEANUP_MAIL: Vec<(&'static str, &'static str)> = vec![ (r"[0-9A-Za-z]+{40}", "IDENTIFIER"), - (r"\w+, \d{1,2} \w+ \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}", "DATE"), - (r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})","RANDOM_ID"), - - // also handles line break in text-format emails, where they break into a new line and then use '=' as continuation symbol. - // #6c23875d-3523-4805-8527-f2=\r\n82d3aa7514 - // #6c23875d-3523-4805-8527-f282d3aa75=\r\n14 (note postfix after '=' can be short) - (r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4})-[0-9a-fA-F]{4}-.*[0-9a-fA-F]{2}", "RANDOM_ID") - ]; + ( + r"\w+, \d{1,2} \w+ \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}", + "DATE", + ), + ( + r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", + "RANDOM_ID", + ), + ( + r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4})-[0-9a-fA-F]{4}-.*[0-9a-fA-F]{2}", + "RANDOM_ID", + ), + ] + }) } /// Combines cleanup filters from various categories (user model, date, and @@ -83,17 +105,17 @@ lazy_static! { /// ``` #[must_use] pub fn cleanup_user_model() -> Vec<(&'static str, &'static str)> { - let mut combined_filters = CLEANUP_USER_MODEL.to_vec(); - combined_filters.extend(CLEANUP_DATE.iter().copied()); - combined_filters.extend(CLEANUP_MODEL.iter().copied()); + let mut combined_filters = get_cleanup_user_model().clone(); + combined_filters.extend(get_cleanup_date().iter().copied()); + combined_filters.extend(get_cleanup_model().iter().copied()); combined_filters } /// Combines cleanup filters from emails that can be dynamic #[must_use] pub fn cleanup_email() -> Vec<(&'static str, &'static str)> { - let mut combined_filters = CLEANUP_MAIL.to_vec(); - combined_filters.extend(CLEANUP_DATE.iter().copied()); + let mut combined_filters = get_cleanup_mail().clone(); + combined_filters.extend(get_cleanup_date().iter().copied()); combined_filters } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index b59f1859c..9040ff170 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -20,7 +20,6 @@ duct = "0.13.6" cargo_metadata = "0.18.1" requestty = "0.5.0" regex = { version = "1.10.2" } -lazy_static = "1.4.0" thiserror = "1" tabled = "0.14.0" colored = "2.1.0" diff --git a/xtask/src/bump_version.rs b/xtask/src/bump_version.rs index a4870941e..aed5e3777 100644 --- a/xtask/src/bump_version.rs +++ b/xtask/src/bump_version.rs @@ -2,11 +2,11 @@ use std::{ fs, io::{Read, Write}, path::{Path, PathBuf}, + sync::OnceLock, }; use cargo_metadata::semver::Version; use colored::Colorize; -use lazy_static::lazy_static; use regex::Regex; use crate::{ @@ -15,19 +15,22 @@ use crate::{ out, utils, }; -lazy_static! { - /// Regular expression for replacing the version in the root package's Cargo.toml file. - static ref REPLACE_LOCO_LIB_VERSION_: Regex = Regex::new( - r#"(?Pname\s*=\s*".+\s+version\s*=\s*")(?P[0-9]+\.[0-9]+\.[0-9]+)"# - ) - .unwrap(); - - /// Regular expression for updating the version in loco-rs package dependencies in Cargo.toml files. - static ref REPLACE_LOCO_PACKAGE_VERSION: Regex = - Regex::new(r#"loco-rs = \{ (version|path) = "[^"]+""#).unwrap(); +static REPLACE_LOCO_LIB_VERSION_: OnceLock = OnceLock::new(); +static REPLACE_LOCO_PACKAGE_VERSION: OnceLock = OnceLock::new(); +fn get_replace_loco_lib_version() -> &'static Regex { + REPLACE_LOCO_LIB_VERSION_.get_or_init(|| { + Regex::new( + r#"(?Pname\s*=\s*".+\s+version\s*=\s*")(?P[0-9]+\.[0-9]+\.[0-9]+)"#, + ) + .unwrap() + }) } +fn get_replace_loco_package_version() -> &'static Regex { + REPLACE_LOCO_PACKAGE_VERSION + .get_or_init(|| Regex::new(r#"loco-rs = \{ (version|path) = "[^"]+""#).unwrap()) +} pub struct BumpVersion { pub base_dir: PathBuf, pub version: Version, @@ -94,14 +97,14 @@ impl BumpVersion { let cargo_toml_file = self.base_dir.join(path).join("Cargo.toml"); fs::File::open(&cargo_toml_file)?.read_to_string(&mut content)?; - if !REPLACE_LOCO_LIB_VERSION_.is_match(&content) { + if !get_replace_loco_lib_version().is_match(&content) { return Err(Error::BumpVersion { path: cargo_toml_file, package: "root_package".to_string(), }); } - let content = REPLACE_LOCO_LIB_VERSION_ + let content = get_replace_loco_lib_version() .replace(&content, |captures: ®ex::Captures<'_>| { format!("{}{}", &captures["name"], self.version) }); @@ -177,13 +180,13 @@ impl BumpVersion { let cargo_toml_file = path.join("Cargo.toml"); fs::File::open(&cargo_toml_file)?.read_to_string(&mut content)?; - if !REPLACE_LOCO_PACKAGE_VERSION.is_match(&content) { + if !get_replace_loco_package_version().is_match(&content) { return Err(Error::BumpVersion { path: cargo_toml_file, package: "loco-rs".to_string(), }); } - content = REPLACE_LOCO_PACKAGE_VERSION + content = get_replace_loco_package_version() .replace_all(&content, |_captures: ®ex::Captures<'_>| { replace_with.to_string() })