diff --git a/Cargo.toml b/Cargo.toml index 8e1441fd..7f1cc215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ documentation = "https://docs.rs/tardis/" repository = "https://github.com/ideal-world/tardis" edition = "2021" license = "MIT/Apache-2.0" -rust-version = "1.64" +rust-version = "1.72" \ No newline at end of file diff --git a/examples/tracing-otlp/config/conf-default.toml b/examples/tracing-otlp/config/conf-default.toml index feafd37f..92f3e674 100644 --- a/examples/tracing-otlp/config/conf-default.toml +++ b/examples/tracing-otlp/config/conf-default.toml @@ -8,11 +8,16 @@ version = "1.0.0" port = 8089 doc_urls = [["test env", "http://localhost:8089/"]] -[fw.db] -url = "postgres://postgres:ENC(5892ae51dbeedacdf10ba4c0d7af42a7)@localhost:5432/test" +# [fw.db] +# url = "postgres://postgres:ENC(5892ae51dbeedacdf10ba4c0d7af42a7)@localhost:5432/test" [fw.log] -level = "info" +level = "debug" + +[fw.log.tracing] +# https://www.jaegertracing.io/docs/1.49/getting-started/ +# endpoint = "http://localhost:4318/" endpoint = "http://localhost:4317" +# protocol = "http/protobuf" protocol = "grpc" server_name = "tracing示例" diff --git a/examples/tracing-otlp/src/main.rs b/examples/tracing-otlp/src/main.rs index c9fa5bde..592e5475 100644 --- a/examples/tracing-otlp/src/main.rs +++ b/examples/tracing-otlp/src/main.rs @@ -13,13 +13,12 @@ mod route; /// #[tokio::main] async fn main() -> TardisResult<()> { - env::set_var("RUST_LOG", "debug"); env::set_var("PROFILE", "default"); // Initial configuration TardisFuns::init(Some("config")).await?; // Register the processor and start the web service TardisFuns::web_server().add_route(Api).await.start().await; - web_server.await; + TardisFuns::web_server().await; Ok(()) } diff --git a/examples/tracing-otlp/src/route.rs b/examples/tracing-otlp/src/route.rs index 7aa4af35..536bce56 100644 --- a/examples/tracing-otlp/src/route.rs +++ b/examples/tracing-otlp/src/route.rs @@ -6,7 +6,7 @@ use tardis::web::web_resp::{TardisApiResult, TardisResp}; use crate::processor::{self, TaskKind}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Api; #[poem_openapi::OpenApi] diff --git a/examples/websocket/src/processor.rs b/examples/websocket/src/processor.rs index 30810790..e1b65e6d 100644 --- a/examples/websocket/src/processor.rs +++ b/examples/websocket/src/processor.rs @@ -17,7 +17,7 @@ impl Page { #[oai(path = "/echo", method = "get")] async fn echo(&self) -> Html<&'static str> { Html( - r###" + r##"
Name: @@ -58,14 +58,14 @@ impl Page { msgInput.value = ""; }); - "###, + "##, ) } #[oai(path = "/broadcast", method = "get")] async fn broadcast(&self) -> Html<&'static str> { Html( - r###" + r##" Name: @@ -108,7 +108,7 @@ impl Page { msgInput.value = ""; }); - "###, + "##, ) } diff --git a/tardis/Cargo.toml b/tardis/Cargo.toml index f80c30f4..f91ed708 100644 --- a/tardis/Cargo.toml +++ b/tardis/Cargo.toml @@ -17,9 +17,12 @@ name = "tardis" path = "src/lib.rs" [features] -default = ["tardis-macros"] -conf-remote = ["web-client", "async-trait", "rust-crypto"] -crypto = ["rust-crypto", "rsa"] +default = ["tardis-macros", "async-trait"] +conf-remote = ["web-client", "async-trait", "crypto"] +digest = ["sha1", "sha2", "hmac", "md-5", "sm3", "dep:digest"] +aead = ["aes-gcm-siv", "aes-gcm", "aes-siv", "dep:aead"] +block_modes = ["cbc", "ecb", "aes", "cipher"] +crypto = ["rsa", "digest", "aead", "block_modes"] crypto-with-sm = ["crypto", "libsm", "num-bigint"] future = ["futures", "async-stream", "futures-util", "async-trait"] tls = ["native-tls"] @@ -48,8 +51,10 @@ fs = ["tokio/fs"] process = ["tokio/process"] test = ["testcontainers"] tracing = ["tracing-opentelemetry", "opentelemetry", "opentelemetry-otlp"] +tokio-console = ["console-subscriber"] +tracing-appender = ["dep:tracing-appender"] web-server-grpc = ["web-server", "dep:poem-grpc"] -cluster = ["web-server", "ws-client"] +cluster = ["web-server", "ws-client", "cache"] [dependencies] # Basic @@ -65,8 +70,10 @@ rand_core = { version = "0.6" } chrono = { version = "0.4", features = ["serde"] } config = { version = "0.13" } regex = { version = "1.5" } -url = { version = "2.2" } +url = { version = "2.2", features = ["serde"] } lru = { version = "0.11.0" } +typed-builder = { version = "0.16" } +paste = { version = "1.0" } # Tokio tokio = { version = "1", features = [ "macros", @@ -80,7 +87,8 @@ tardis-macros = { version = "0.1.0-beta.11", path = "../tardis-macros", optional # Log tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } - +tracing-appender = { version = "0.2", optional = true } +console-subscriber = { version = "0.1", optional = true } # Tracing tracing-opentelemetry = { version = "0.21", optional = true } opentelemetry = { version = "0.20", default-features = false, features = [ @@ -93,16 +101,37 @@ opentelemetry-otlp = { version = "0.13", features = [ "http-proto", "tls", ], optional = true } - # TLS native-tls = { version = "0.2", optional = true } # Crypto -rust-crypto = { version = "0.2", optional = true } +cipher = { version = "0.4", optional = true, features = ["block-padding", "alloc"]} +## Digest +## see https://github.com/RustCrypto/hashes +sha1 = { version = "0.10", optional = true } +sha2 = { version = "0.10", optional = true } +# md5 is no longer considered secure +md-5 = { version = "0.10", optional = true } +# this sm3 lib support digest trait +sm3 = { version = "0.4", optional = true } +hmac = { version = "0.12", optional = true } +digest = { version = "0.10", optional = true } +# RSA rsa = { version = "0.9", features = ["pem"], optional = true } +# AES +aead = { version = "0.5", optional = true } +aes ={ version = "0.8", optional = true } +aes-gcm-siv = { version = "0.11", optional = true } +aes-gcm = { version = "0.10", optional = true } +aes-siv = { version = "0.7", optional = true } +## block-mode +cbc = { version = "0.1", optional = true } +ecb = { version = "0.1", optional = true } +# libsm = { version = "0.5", optional = true } num-bigint = { version = "0.4.3", optional = true } + # Future futures = { version = "0.3", optional = true } async-stream = { version = "0.3", optional = true } @@ -180,7 +209,6 @@ testcontainers = { version = "0.14", optional = true } # Common tokio = { version = "1", features = ["time", "rt", "macros", "sync"] } criterion = { version = "0.5" } -console-subscriber = "0.1.10" poem-grpc-build = "0.2.21" prost = "0.11.9" strip-ansi-escapes = "0.2.0" diff --git a/tardis/benches/crypto_benchmark.rs b/tardis/benches/crypto_benchmark.rs index 3362fe9d..e9d677bb 100644 --- a/tardis/benches/crypto_benchmark.rs +++ b/tardis/benches/crypto_benchmark.rs @@ -1,3 +1,4 @@ +use aes::Aes128; use criterion::{criterion_group, criterion_main, Criterion}; use tardis::TardisFuns; @@ -24,17 +25,17 @@ Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, // AES - let key = TardisFuns::crypto.key.rand_16_hex().unwrap(); - let iv = TardisFuns::crypto.key.rand_16_hex().unwrap(); - let encrypted_data = TardisFuns::crypto.aes.encrypt_cbc(large_text, &key, &iv).unwrap(); + let key = TardisFuns::crypto.key.rand_16_bytes(); + let iv = TardisFuns::crypto.key.rand_16_bytes(); + let encrypted_data = TardisFuns::crypto.aead.encrypt_cbc::(large_text, &key, &iv).unwrap(); c.bench_function("CRYPTO: aes_encrypt_cbc", |b| { b.iter(|| { - TardisFuns::crypto.aes.encrypt_cbc(large_text, &key, &iv).unwrap(); + TardisFuns::crypto.aead.encrypt_cbc::(large_text, &key, &iv).unwrap(); }) }); c.bench_function("CRYPTO: aes_decrypt_cbc", |b| { b.iter(|| { - TardisFuns::crypto.aes.decrypt_cbc(&encrypted_data, &key, &iv).unwrap(); + TardisFuns::crypto.aead.decrypt_cbc::(&encrypted_data, &key, &iv).unwrap(); }) }); @@ -77,17 +78,17 @@ Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, }); // SM4 - let key = TardisFuns::crypto.key.rand_16_hex().unwrap(); - let iv = TardisFuns::crypto.key.rand_16_hex().unwrap(); - let encrypted_data = TardisFuns::crypto.sm4.encrypt_cbc(large_text, &key, &iv).unwrap(); + let key = TardisFuns::crypto.key.rand_16_bytes(); + let iv = TardisFuns::crypto.key.rand_16_bytes(); + let encrypted_data = TardisFuns::crypto.sm4.encrypt_cbc(large_text, key, iv).unwrap(); c.bench_function("CRYPTO: sm4_encrypt_cbc", |b| { b.iter(|| { - TardisFuns::crypto.sm4.encrypt_cbc(large_text, &key, &iv).unwrap(); + TardisFuns::crypto.sm4.encrypt_cbc(large_text, key, iv).unwrap(); }) }); c.bench_function("CRYPTO: sm4_decrypt_cbc", |b| { b.iter(|| { - TardisFuns::crypto.sm4.decrypt_cbc(&encrypted_data, &key, &iv).unwrap(); + TardisFuns::crypto.sm4.decrypt_cbc(&encrypted_data, key, iv).unwrap(); }) }); diff --git a/tardis/src/basic/dto.rs b/tardis/src/basic/dto.rs index 13e9ff51..cfd48f7c 100644 --- a/tardis/src/basic/dto.rs +++ b/tardis/src/basic/dto.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, fmt, pin::Pin, sync::Arc}; use tokio::sync::{Mutex, RwLock}; use tracing::error; -use crate::serde::{Deserialize, Serialize}; +use crate::{serde::{Deserialize, Serialize}, TardisFuns}; use super::result::TardisResult; @@ -96,6 +96,16 @@ impl Default for TardisContext { } impl TardisContext { + pub fn to_json(&self) -> TardisResult { + TardisFuns::json.obj_to_string(self) + } + + pub fn to_base64(&self) -> TardisResult { + let ctx = TardisContext::default(); + let ctx = TardisFuns::json.obj_to_string(&ctx)?; + Ok(TardisFuns::crypto.base64.encode(ctx)) + } + pub async fn add_ext(&self, key: &str, value: &str) -> TardisResult<()> { self.ext.write().await.insert(key.to_string(), value.to_string()); Ok(()) diff --git a/tardis/src/basic/error.rs b/tardis/src/basic/error.rs index 549b9a2c..1884cd46 100644 --- a/tardis/src/basic/error.rs +++ b/tardis/src/basic/error.rs @@ -88,6 +88,8 @@ impl TardisError { } } +impl std::error::Error for TardisError {} + pub struct TardisErrorWithExt { pub ext: String, /// https://www.andiamo.co.uk/resources/iso-language-codes/ @@ -147,6 +149,13 @@ impl TardisErrorWithExt { } } +// dynamic cast any error into TardisError +impl From<&dyn std::error::Error> for TardisError { + fn from(error: &dyn std::error::Error) -> Self { + TardisError::format_error(&format!("[Tardis.Basic] {error}"), "") + } +} + impl From for TardisError { fn from(error: std::io::Error) -> Self { TardisError::io_error(&format!("[Tardis.Basic] {error}"), "") diff --git a/tardis/src/basic/field.rs b/tardis/src/basic/field.rs index f12ba70e..2225100f 100644 --- a/tardis/src/basic/field.rs +++ b/tardis/src/basic/field.rs @@ -1,8 +1,8 @@ -use std::fmt::{Display, Formatter}; use std::ops::Deref; use regex::Regex; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::utils::mapper::{Mapped, Trim, Base64Encode, Base64Decode}; lazy_static! { static ref R_PHONE: Regex = Regex::new(r"^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$").expect("Regular parsing error"); @@ -204,105 +204,27 @@ impl TardisField { /// done: bool, /// } /// ``` -#[derive(Debug, Eq, PartialEq, Hash)] -pub struct TrimString(pub String); +pub type TrimString = Mapped; -impl From<&str> for TrimString { - fn from(str: &str) -> Self { - TrimString(str.to_string()) - } +// This function is `non_snake_case` for being compatible with the old version +#[allow(non_snake_case)] +#[deprecated(since="1.0.0", note="Please use `TrimString::new` instead")] +pub fn TrimString(string: String) -> TrimString { + TrimString::new(string) } -impl From for TrimString { - fn from(str: String) -> Self { - TrimString(str) - } -} - -impl Clone for TrimString { - fn clone(&self) -> Self { - TrimString(self.0.clone()) - } -} - -impl Display for TrimString { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0.trim(), f) - } -} - -impl Serialize for TrimString { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.0) - } -} - -impl<'de> Deserialize<'de> for TrimString { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer).map(TrimString) +impl From<&str> for TrimString { + fn from(str: &str) -> Self { + TrimString::new(str.to_string()) } } impl AsRef for TrimString { fn as_ref(&self) -> &str { - self.0.as_str() + self.deref() } } -impl Deref for TrimString { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0.trim() - } -} - -#[cfg(feature = "web-server")] -impl crate::web::poem_openapi::types::Type for TrimString { - const IS_REQUIRED: bool = true; - - type RawValueType = Self; - - type RawElementValueType = Self; - - fn name() -> std::borrow::Cow<'static, str> { - "trim_string".into() - } - - fn schema_ref() -> poem_openapi::registry::MetaSchemaRef { - String::schema_ref() - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } - - fn raw_element_iter<'a>(&'a self) -> Box + 'a> { - Box::new(self.as_raw_value().into_iter()) - } -} - -#[cfg(feature = "web-server")] -impl crate::web::poem_openapi::types::ToJSON for TrimString { - fn to_json(&self) -> Option { - self.0.to_json() - } -} - -#[cfg(feature = "web-server")] -impl crate::web::poem_openapi::types::ParseFromJSON for TrimString { - fn parse_from_json(value: Option) -> poem_openapi::types::ParseResult { - let value = value.unwrap_or_default(); - if let serde_json::Value::String(value) = value { - Ok(TrimString(value)) - } else { - Err(poem_openapi::types::ParseError::expected_type(value)) - } - } -} +pub type TrimStr<'a> = Mapped<&'a str, Trim>; +pub type Base64EncodedString = Mapped; +pub type Base64DecodedString = Mapped; diff --git a/tardis/src/basic/json.rs b/tardis/src/basic/json.rs index 17a4d2f7..6a56b2a4 100644 --- a/tardis/src/basic/json.rs +++ b/tardis/src/basic/json.rs @@ -1,6 +1,6 @@ use crate::basic::error::TardisError; use crate::basic::result::TardisResult; -use crate::serde::de::DeserializeOwned; +use crate::serde::de::Deserialize; use crate::serde::Serialize; use crate::serde_json::Value; use std::fs::File; @@ -54,7 +54,7 @@ impl TardisJson { /// use tardis::TardisFuns; /// TardisFuns::json.str_to_obj::>(&json_str); /// ``` - pub fn str_to_obj(&self, str: &str) -> TardisResult { + pub fn str_to_obj Deserialize<'de>>(&self, str: &str) -> TardisResult { let result = serde_json::from_str(str); match result { Ok(r) => Ok(r), @@ -76,7 +76,7 @@ impl TardisJson { /// let file = fs::File::open("text.json")? /// TardisFuns::json.reader_to_obj::(file); /// ``` - pub fn reader_to_obj(&self, rdr: R) -> TardisResult { + pub fn reader_to_obj Deserialize<'de>>(&self, rdr: R) -> TardisResult { let result = serde_json::from_reader::(rdr); match result { Ok(r) => Ok(r), @@ -96,7 +96,7 @@ impl TardisJson { /// /// TardisFuns::json.file_to_obj::("text.json")?; /// ``` - pub fn file_to_obj>(&self, path: P) -> TardisResult { + pub fn file_to_obj Deserialize<'de>, P: AsRef>(&self, path: P) -> TardisResult { let file = File::open(path); match file { Ok(f) => self.reader_to_obj::(f), @@ -134,7 +134,7 @@ impl TardisJson { /// use tardis::TardisFuns; /// TardisFuns::json.json_to_obj::>(json_value); /// ``` - pub fn json_to_obj(&self, value: Value) -> TardisResult { + pub fn json_to_obj Deserialize<'de>>(&self, value: Value) -> TardisResult { let result = serde_json::from_value::(value); match result { Ok(r) => Ok(r), @@ -199,7 +199,7 @@ impl TardisJson { } } - pub fn copy(&self, source: &F) -> TardisResult { + pub fn copy Deserialize<'de>>(&self, source: &F) -> TardisResult { let result = serde_json::to_value(source); match result { Ok(value) => { diff --git a/tardis/src/basic/tracing.rs b/tardis/src/basic/tracing.rs index 851f8e76..badb1e31 100644 --- a/tardis/src/basic/tracing.rs +++ b/tardis/src/basic/tracing.rs @@ -1,80 +1,188 @@ -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::OnceLock; +use std::sync::Once; use crate::basic::result::TardisResult; -use tracing_subscriber::fmt::Layer; +use crate::config::config_dto::LogConfig; + +use crate::TARDIS_INST; + +#[allow(unused_imports)] +use crate::consts::*; use tracing_subscriber::layer::Layered; +use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; -use tracing_subscriber::{prelude::*, reload::Handle, Registry}; +use tracing_subscriber::{fmt::Layer as FmtLayer, layer::SubscriberExt, prelude::*, reload::Layer as ReloadLayer, Registry}; +#[derive(Default)] +pub struct TardisTracing { + configer: Vec TardisResult<()> + Send + Sync>>, +} -use super::error::TardisError; -static INITIALIZED: AtomicBool = AtomicBool::new(false); +fn create_configurable_layer(layer: L, configer: impl Fn(&C) -> TardisResult + Send + Sync) -> TardisResult<(ReloadLayer, impl Fn(&C) -> TardisResult<()>)> { + let (reload_layer, reload_handle) = ReloadLayer::new(layer); + let config_layer_fn = move |conf: &C| -> TardisResult<()> { + let layer = configer(conf)?; + reload_handle.reload(layer)?; + Ok(()) + }; + Ok((reload_layer, config_layer_fn)) +} -pub struct TardisTracing; +/// Tardis tracing initializer +/// ```ignore +/// # use crate::basic::tracing::TardisTracingInitializer; +/// # use tracing_subscriber::{fmt, EnvFilter} +/// TardisTracing::init() +/// .with_layer(fmt::layer()) +/// .with_configurable_layer(EnvFilter::from_default_env(), |config| { +/// let env_filter = EnvFilter::from_default_env(); +/// // handle with config +/// Ok(env_filter) +/// }) +/// .init(); +/// ``` +pub struct TardisTracingInitializer { + /// 所有延迟配置函数 + configers: Vec TardisResult<()> + Send + Sync>>, + /// 外部创建层 + layered: L, +} -pub static GLOBAL_RELOAD_HANDLE: OnceLock, Registry>>> = OnceLock::new(); +impl Default for TardisTracingInitializer { + fn default() -> Self { + Self::new() + } +} -impl TardisTracing { - pub(crate) fn init_log() -> TardisResult<()> { - if INITIALIZED.swap(true, Ordering::SeqCst) { - return Ok(()); +impl TardisTracingInitializer { + pub fn new() -> Self { + Self { + configers: Vec::new(), + layered: Registry::default(), } - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "info"); + } +} + +impl TardisTracingInitializer +where + L0: SubscriberExt, +{ + pub fn with_configurable_layer( + mut self, + layer: L, + configer: impl Fn(&C) -> TardisResult + 'static + Send + Sync, + ) -> TardisResult, L0>, C>> + where + ReloadLayer: tracing_subscriber::Layer, + L: 'static + Send + Sync, + { + let (reload_layer, config_layer_fn) = create_configurable_layer::(layer, configer)?; + self.configers.push(Box::new(config_layer_fn)); + Ok(TardisTracingInitializer { + configers: self.configers, + layered: self.layered.with(reload_layer), + }) + } + + pub fn with_layer(self, layer: L) -> TardisTracingInitializer, C> + where + L: tracing_subscriber::Layer, + { + TardisTracingInitializer { + configers: self.configers, + layered: self.layered.with(layer), } + } +} - let builder = tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).with_filter_reloading(); - let reload_handle = builder.reload_handle(); - builder.finish().init(); - GLOBAL_RELOAD_HANDLE.get_or_init(|| reload_handle); - Ok(()) +impl TardisTracingInitializer +where + L: SubscriberInitExt + 'static, +{ + pub fn init(self) { + static INITIALIZED: Once = Once::new(); + let configer_list = self.configers; + if INITIALIZED.is_completed() { + tracing::error!("[Tardis.Tracing] Trying to initialize tardis tracing more than once, this initialization will be ignored. If you want to use new config for tracing, use update_config() instead."); + } else { + INITIALIZED.call_once(|| self.layered.init()); + TARDIS_INST.tracing.set(TardisTracing { configer: configer_list }); + } } +} - pub fn update_log_level(log_level: &str) -> TardisResult<()> { - std::env::set_var("RUST_LOG", log_level); - GLOBAL_RELOAD_HANDLE - .get() - .ok_or_else(|| TardisError::internal_error(&format!("{} is none, tracing may not be initialized", stringify!(GLOBAL_RELOAD_HANDLE)), ""))? - .reload(EnvFilter::from_default_env())?; - Ok(()) +impl TardisTracing { + /// Get an initializer for tardis tracing + pub fn init() -> TardisTracingInitializer { + TardisTracingInitializer::default() } - pub fn update_log_level_by_domain_code(domain_code: &str, log_level: &str) -> TardisResult<()> { - let env_filter = EnvFilter::from_default_env(); - let env_filter = env_filter - .add_directive(format!("{domain_code}={log_level}").parse().map_err(|e| TardisError::internal_error(&format!("update_log_level_by_domain_code failed: {e:?}"), ""))?); - std::env::set_var("RUST_LOG", env_filter.to_string()); - GLOBAL_RELOAD_HANDLE - .get() - .ok_or_else(|| TardisError::internal_error(&format!("{} is none, tracing may not be initialized", stringify!(GLOBAL_RELOAD_HANDLE)), ""))? - .reload(env_filter)?; + /// Update tardis tracing config, and this will reload all configurable layers + /// + pub fn update_config(&self, config: &LogConfig) -> TardisResult<()> { + for configer in &self.configer { + (configer)(config)? + } + tracing::debug!("[Tardis.Tracing] Config updated."); + tracing::trace!("[Tardis.Tracing] New config: {:?}", config); Ok(()) } - #[cfg(feature = "tracing")] - pub(crate) fn init_tracing(conf: &crate::config::config_dto::FrameworkConfig) -> TardisResult<()> { - if INITIALIZED.swap(true, Ordering::SeqCst) { - return Ok(()); - } - if let Some(tracing_config) = conf.log.as_ref() { - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", tracing_config.level.as_str()); - } - if std::env::var_os("OTEL_EXPORTER_OTLP_ENDPOINT").is_none() { - std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", tracing_config.endpoint.as_str()); - } - if std::env::var_os("OTEL_EXPORTER_OTLP_PROTOCOL").is_none() { - std::env::set_var("OTEL_EXPORTER_OTLP_PROTOCOL", tracing_config.protocol.as_str()); - } - if std::env::var_os("OTEL_SERVICE_NAME").is_none() { - std::env::set_var("OTEL_SERVICE_NAME", tracing_config.server_name.as_str()); + pub(crate) fn init_default() -> TardisResult<()> { + tracing::info!("[Tardis.Tracing] Initializing by defualt initializer."); + let initializer = TardisTracingInitializer::default(); + let initializer = initializer.with_layer(FmtLayer::default()); + let initializer = initializer.with_configurable_layer(EnvFilter::from_default_env(), |config: &LogConfig| { + let mut env_filter = EnvFilter::from_default_env(); + env_filter = env_filter.add_directive(config.level.clone()); + for directive in &config.directives { + env_filter = env_filter.add_directive(directive.clone()); } - } - let telemetry_layer = tracing_opentelemetry::layer().with_tracer(Self::create_otlp_tracer()?); - let builder = tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).with_filter_reloading(); - let reload_handle = builder.reload_handle(); - GLOBAL_RELOAD_HANDLE.get_or_init(|| reload_handle); - builder.finish().with(telemetry_layer).init(); + std::env::set_var("RUST_LOG", env_filter.to_string()); + Ok(env_filter) + })?; + tracing::debug!("[Tardis.Tracing] Added fmt layer and env filter."); + #[cfg(feature = "tracing")] + let initializer = { + let initializer = initializer.with_configurable_layer(tracing_opentelemetry::layer().with_tracer(Self::create_otlp_tracer()?), |conf: &LogConfig| { + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", conf.level.to_string()); + } + if std::env::var_os(OTEL_EXPORTER_OTLP_ENDPOINT).is_none() { + std::env::set_var(OTEL_EXPORTER_OTLP_ENDPOINT, conf.tracing.endpoint.as_str()); + } + if std::env::var_os(OTEL_EXPORTER_OTLP_PROTOCOL).is_none() { + std::env::set_var(OTEL_EXPORTER_OTLP_PROTOCOL, conf.tracing.protocol.to_string()); + } + if std::env::var_os(OTEL_SERVICE_NAME).is_none() { + std::env::set_var(OTEL_SERVICE_NAME, conf.tracing.server_name.as_str()); + } + Ok(tracing_opentelemetry::layer().with_tracer(Self::create_otlp_tracer()?)) + })?; + tracing::debug!("[Tardis.Tracing] Added fmt layer and env filter."); + initializer + }; + #[cfg(feature = "console-subscriber")] + let initializer = { + use console_subscriber::ConsoleLayer; + tracing::info!("[Tardis.Tracing] Initializing console subscriber. To make it work, you need to enable tokio and runtime tracing targets at **TRACE** level."); + let layer = ConsoleLayer::builder().with_default_env().spawn(); + initializer.with_layer(layer) + }; + #[cfg(feature = "tracing-appender")] + let initializer = { + use crate::config::config_dto::log::TracingAppenderConfig; + let config_file_layer = |cfg: Option<&TracingAppenderConfig>| { + let fmt_file_layer = if let Some(cfg) = &cfg { + let file_appender = tracing_appender::rolling::RollingFileAppender::new(cfg.rotation.into(), &cfg.dir, &cfg.filename); + FmtLayer::default().with_writer(file_appender).boxed() + } else { + FmtLayer::default().with_writer(std::io::sink).boxed() + }; + fmt_file_layer + }; + initializer.with_configurable_layer(config_file_layer(None), move |cfg| TardisResult::Ok(config_file_layer(cfg.tracing_appender.as_ref())))? + }; + tracing::info!("[Tardis.Tracing] Initialize finished."); + initializer.init(); Ok(()) } @@ -82,34 +190,38 @@ impl TardisTracing { fn create_otlp_tracer() -> TardisResult { use opentelemetry_otlp::WithExportConfig; - let protocol = std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL").unwrap_or("grpc".to_string()); + use crate::config::config_dto::OtlpProtocol; + tracing::debug!("[Tardis.Tracing] Initializing otlp tracer"); + let protocol = std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL).ok().map(|s| s.parse::()).transpose()?.unwrap_or_default(); let mut tracer = opentelemetry_otlp::new_pipeline().tracing(); - match protocol.as_str() { - "grpc" => { + match protocol { + OtlpProtocol::Grpc => { let mut exporter = opentelemetry_otlp::new_exporter().tonic().with_env(); // Check if we need TLS - if let Ok(endpoint) = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") { + if let Ok(endpoint) = std::env::var(OTEL_EXPORTER_OTLP_ENDPOINT) { if endpoint.to_lowercase().starts_with("https") { exporter = exporter.with_tls_config(Default::default()); } } tracer = tracer.with_exporter(exporter) } - "http/protobuf" => { + OtlpProtocol::HttpProtobuf => { let headers = Self::parse_otlp_headers_from_env(); let exporter = opentelemetry_otlp::new_exporter().http().with_headers(headers.into_iter().collect()).with_env(); tracer = tracer.with_exporter(exporter) } - p => return Err(TardisError::conflict(&format!("[Tracing] Unsupported protocol {p}"), "")), }; - Ok(tracer.install_batch(opentelemetry::runtime::Tokio)?) + tracing::debug!("[Tardis.Tracing] Batch installing tracer. If you are blocked here, try running tokio in multithread."); + let tracer = tracer.install_batch(opentelemetry::runtime::Tokio)?; + tracing::debug!("[Tardis.Tracing] Initialized otlp tracer"); + Ok(tracer) } #[cfg(feature = "tracing")] fn parse_otlp_headers_from_env() -> Vec<(String, String)> { let mut headers = Vec::new(); - if let Ok(hdrs) = std::env::var("OTEL_EXPORTER_OTLP_HEADERS") { + if let Ok(hdrs) = std::env::var(OTEL_EXPORTER_OTLP_HEADERS) { hdrs.split(',') .map(|header| header.split_once('=').expect("Header should contain '=' character")) .for_each(|(name, value)| headers.push((name.to_owned(), value.to_owned()))); diff --git a/tardis/src/basic/uri.rs b/tardis/src/basic/uri.rs index d209e1a8..09a98b01 100644 --- a/tardis/src/basic/uri.rs +++ b/tardis/src/basic/uri.rs @@ -1,3 +1,5 @@ +use url::Url; + use crate::basic::result::TardisResult; /// Uri handle / Uri处理 @@ -50,7 +52,8 @@ impl TardisUri { /// assert_eq!(TardisFuns::uri.format("api://a1.t1/e1?q2=2&q1=1&q3=3").unwrap(), "api://a1.t1/e1?q1=1&q2=2&q3=3"); /// ``` pub fn format(&self, uri_str: &str) -> TardisResult { - let uri = url::Url::parse(uri_str)?; + let mut uri = url::Url::parse(uri_str)?; + self.sort_url_query(&mut uri); let authority = if let Some(password) = uri.password() { format!("{}:{}@", uri.username(), password) } else if !uri.username().is_empty() { @@ -67,7 +70,7 @@ impl TardisUri { } }; let port = match uri.port() { - Some(port) => format!(":{port}"), + Some(port) => format!(":{}", port), None => "".to_string(), }; let path = if uri.path().is_empty() { @@ -77,9 +80,8 @@ impl TardisUri { } else { uri.path() }; - let query = self.sort_query(uri.query()); let query = match uri.query() { - Some(_) => format!("?{query}"), + Some(query) => format!("?{}", query), None => "".to_string(), }; let formatted_uri = format!("{}://{}{}{}{}{}", uri.scheme(), authority, host, port, path, query); @@ -108,14 +110,12 @@ impl TardisUri { Ok(format!("{path}{query}")) } - fn sort_query(&self, query: Option<&str>) -> String { - match query { - None => "".to_string(), - Some(query) => { - let mut query = query.split('&').collect::>(); - query.sort_by(|a, b| Ord::cmp(a.split('=').next().unwrap_or(""), b.split('=').next().unwrap_or(""))); - query.join("&") - } + /// Sort the Query parameters in the Uri / 对Uri中的Query参数进行排序 + pub fn sort_url_query(&self, uri: &mut Url) { + let mut query_pairs = uri.query_pairs().map(|(k, v)| (k.to_string(), v.to_string())).collect::>(); + if !query_pairs.is_empty() { + query_pairs.sort_by(|(ka, _), (kb, _)| Ord::cmp(ka, kb)); + uri.query_pairs_mut().clear().extend_pairs(query_pairs); } } } diff --git a/tardis/src/cache/cache_client.rs b/tardis/src/cache/cache_client.rs index 4545ae92..c35da7e6 100644 --- a/tardis/src/cache/cache_client.rs +++ b/tardis/src/cache/cache_client.rs @@ -3,11 +3,12 @@ use std::collections::HashMap; use deadpool_redis::{Config, Connection, Pool, Runtime}; use redis::{AsyncCommands, ErrorKind, RedisError, RedisResult}; use tracing::{error, info, trace}; -use url::Url; use crate::basic::error::TardisError; use crate::basic::result::TardisResult; -use crate::config::config_dto::FrameworkConfig; +use crate::config::config_dto::component::cache::CacheModuleConfig; + +use crate::utils::initializer::InitBy; /// Distributed cache handle / 分布式缓存操作 /// @@ -31,28 +32,23 @@ use crate::config::config_dto::FrameworkConfig; pub struct TardisCacheClient { pool: Pool, } - -impl TardisCacheClient { - /// Initialize configuration from the cache configuration object / 从缓存配置对象中初始化配置 - pub async fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert("".to_string(), TardisCacheClient::init(&conf.cache.url).await?); - for (k, v) in &conf.cache.modules { - clients.insert(k.to_string(), TardisCacheClient::init(&v.url).await?); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisCacheClient { + async fn init_by(config: &CacheModuleConfig) -> TardisResult { + Self::init(config).await } +} +impl TardisCacheClient { /// Initialize configuration / 初始化配置 - pub async fn init(str_url: &str) -> TardisResult { - let url = Url::parse(str_url).map_err(|_| TardisError::format_error(&format!("[Tardis.CacheClient] Invalid url {str_url}"), "406-tardis-cache-url-error"))?; + pub async fn init(CacheModuleConfig { url }: &CacheModuleConfig) -> TardisResult { info!( "[Tardis.CacheClient] Initializing, host:{}, port:{}, db:{}", url.host_str().unwrap_or(""), url.port().unwrap_or(0), if url.path().is_empty() { "" } else { &url.path()[1..] }, ); - let cfg = Config::from_url(str_url); + let cfg = Config::from_url(url.clone()); let pool = cfg .create_pool(Some(Runtime::Tokio1)) .map_err(|e| TardisError::format_error(&format!("[Tardis.CacheClient] Create pool error: {e}"), "500-tardis-cache-pool-error"))?; diff --git a/tardis/src/cluster/cluster_processor.rs b/tardis/src/cluster/cluster_processor.rs index 1b5df667..15841183 100644 --- a/tardis/src/cluster/cluster_processor.rs +++ b/tardis/src/cluster/cluster_processor.rs @@ -45,17 +45,18 @@ impl TardisClusterSubscriber for ClusterSubscriberWhoAmI { pub async fn init_by_conf(conf: &FrameworkConfig, cluster_server: &TardisWebServer) -> TardisResult<()> { if let Some(cluster_config) = &conf.cluster { + let web_server_config = conf.web_server.as_ref().expect("missing web server config"); info!("[Tardis.Cluster] Initializing cluster"); init_node(cluster_server).await?; match cluster_config.watch_kind.to_lowercase().as_str() { #[cfg(feature = "k8s")] "k8s" => { info!("[Tardis.Cluster] Initializing cluster by k8s watch"); - cluster_watch_by_k8s::init(cluster_config, &conf.web_server).await?; + cluster_watch_by_k8s::init(cluster_config, web_server_config).await?; } "cache" => { info!("[Tardis.Cluster] Initializing cluster by default watch"); - cluster_watch_by_cache::init(cluster_config, &conf.web_server).await?; + cluster_watch_by_cache::init(cluster_config, web_server_config).await?; } _ => panic!("[Tardis.Cluster] Unsupported cluster watch kind: {}", cluster_config.watch_kind), } diff --git a/tardis/src/cluster/cluster_watch_by_cache.rs b/tardis/src/cluster/cluster_watch_by_cache.rs index c9f30c10..93abc679 100644 --- a/tardis/src/cluster/cluster_watch_by_cache.rs +++ b/tardis/src/cluster/cluster_watch_by_cache.rs @@ -8,7 +8,7 @@ use crate::{ basic::result::TardisResult, cache::cache_client::TardisCacheClient, cluster::cluster_processor, - config::config_dto::{ClusterConfig, WebServerConfig}, + config::config_dto::{component::WebServerConfig, ClusterConfig}, TardisFuns, }; diff --git a/tardis/src/config.rs b/tardis/src/config.rs index 0010c04d..79db6c7e 100644 --- a/tardis/src/config.rs +++ b/tardis/src/config.rs @@ -3,3 +3,4 @@ pub mod config_dto; pub mod config_nacos; pub mod config_processor; pub(crate) mod config_utils; + diff --git a/tardis/src/config/config_dto.rs b/tardis/src/config/config_dto.rs index f9f282a2..3e9805d3 100644 --- a/tardis/src/config/config_dto.rs +++ b/tardis/src/config/config_dto.rs @@ -2,44 +2,53 @@ use crate::{ serde::{Deserialize, Serialize}, TardisFuns, }; -use serde_json::Value; use std::collections::HashMap; +use typed_builder::TypedBuilder; +pub(crate) mod component; +pub mod log; +pub use component::*; +pub use log::*; /// Configuration of Tardis / Tardis的配置 -#[derive(Default, Serialize, Deserialize, Clone)] -#[serde(default)] +#[derive(Serialize, Deserialize, Clone, TypedBuilder, Debug)] pub struct TardisConfig { + #[builder(default, setter(into))] /// Project custom configuration / 项目自定义的配置 - pub cs: HashMap, + pub cs: HashMap, + #[builder(default)] /// Tardis framework configuration / Tardis框架的各功能配置 pub fw: FrameworkConfig, } /// Configuration of each function of the Tardis framework / Tardis框架的各功能配置 -#[derive(Default, Debug, Serialize, Deserialize, Clone)] -#[serde(default)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, TypedBuilder)] // TODO Replace with options / enums +#[builder(field_defaults(default, setter(strip_option, into)))] +#[serde(default)] pub struct FrameworkConfig { + #[builder(setter(!strip_option))] /// Application configuration / 应用配置 pub app: AppConfig, + #[builder(setter(!strip_option))] + /// Advanced configuration / 高级配置 + pub adv: AdvConfig, /// Database configuration / 数据库配置 - pub db: DBConfig, + pub db: Option, /// Web service configuration / Web服务配置 - pub web_server: WebServerConfig, + pub web_server: Option, + #[builder(default = Some(WebClientConfig::default()))] /// Web client configuration / Web客户端配置 - pub web_client: WebClientConfig, + pub web_client: Option, /// Distributed cache configuration / 分布式缓存配置 - pub cache: CacheConfig, + pub cache: Option, /// Message queue configuration / 消息队列配置 - pub mq: MQConfig, + pub mq: Option, /// Search configuration / 搜索配置 - pub search: SearchConfig, + pub search: Option, /// Mail configuration / 邮件配置 - pub mail: MailConfig, + pub mail: Option, /// Object Storage configuration / 对象存储配置 - pub os: OSConfig, - /// Advanced configuration / 高级配置 - pub adv: AdvConfig, + pub os: Option, /// Config center configuration / 配置中心的配置 #[cfg(feature = "conf-remote")] pub conf_center: Option, @@ -49,6 +58,69 @@ pub struct FrameworkConfig { pub cluster: Option, } +impl FrameworkConfig { + /// Get db config + /// # Panic + /// If the config of db is none, this will be panic. + pub fn db(&self) -> &DBConfig { + self.db.as_ref().expect("missing component config of db") + } + /// Get web_server config + /// # Panic + /// If the config of web_server is none, this will be panic. + pub fn web_server(&self) -> &WebServerConfig { + self.web_server.as_ref().expect("missing component config of web_server") + } + /// Get web_client config + /// # Panic + /// If the config of web_client is none, this will be panic. + pub fn web_client(&self) -> &WebClientConfig { + self.web_client.as_ref().expect("missing component config of web_client") + } + /// Get cache config + /// # Panic + /// If the config of cache is none, this will be panic. + pub fn cache(&self) -> &CacheConfig { + self.cache.as_ref().expect("missing component config of cache") + } + /// Get mq config + /// # Panic + /// If the config of mq is none, this will be panic. + pub fn mq(&self) -> &MQConfig { + self.mq.as_ref().expect("missing component config of mq") + } + /// Get search config + /// # Panic + /// If the config of search is none, this will be panic. + pub fn search(&self) -> &SearchConfig { + self.search.as_ref().expect("missing component config of search") + } + /// Get mail config + /// # Panic + /// If the config of mail is none, this will be panic. + pub fn mail(&self) -> &MailConfig { + self.mail.as_ref().expect("missing component config of mail") + } + /// Get os config + /// # Panic + /// If the config of os is none, this will be panic. + pub fn os(&self) -> &OSConfig { + self.os.as_ref().expect("missing component config of os") + } + /// Get log config + /// # Panic + /// If the config of log is none, this will be panic. + pub fn log(&self) -> &LogConfig { + self.log.as_ref().expect("missing component config of log") + } + /// Get cluster config + /// # Panic + /// If the config of cluster is none, this will be panic. + pub fn cluster(&self) -> &ClusterConfig { + self.cluster.as_ref().expect("missing component config of cluster") + } +} + /// Application configuration / 应用配置 /// /// By application, it means the current service @@ -65,35 +137,43 @@ pub struct FrameworkConfig { /// ..Default::default() /// }; /// ``` -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] #[serde(default)] pub struct AppConfig { + #[builder(default)] /// Application identifier / 应用标识 /// /// Used to distinguish different services (applications) in a microservice environment. /// /// 在微服务环境下用于区别不同的服务(应用). pub id: String, + #[builder(default = String::from("Tardis Application"))] /// Application name / 应用名称 pub name: String, + #[builder(default = String::from("This is a Tardis Application"))] /// Application description / 应用描述 pub desc: String, + #[builder(default = String::from("0.0.1"))] /// Application version / 应用版本 pub version: String, + #[builder(default)] /// Application address / 应用地址 /// /// Can be either the access address or the documentation address. /// /// 可以是访问地址,也可以是文档地址. pub url: String, + #[builder(default)] /// Application contact email / 应用联系邮箱 pub email: String, + #[builder(default = format!("inst_{}", TardisFuns::field.nanoid()))] /// Application instance identification / 应用实例标识 /// /// An application can have multiple instances, each with its own identity, using the nanoid by default. /// /// 一个应用可以有多个实例,每个实例都有自己的标识,默认使用nanoid. pub inst: String, + #[builder(default, setter(strip_option))] /// Application default language / 应用默认语言 /// https://www.andiamo.co.uk/resources/iso-language-codes/ pub default_lang: Option, @@ -101,623 +181,11 @@ pub struct AppConfig { impl Default for AppConfig { fn default() -> Self { - AppConfig { - id: "".to_string(), - name: "Tardis Application".to_string(), - desc: "This is a Tardis Application".to_string(), - version: "0.0.1".to_string(), - url: "".to_string(), - email: "".to_string(), - inst: format!("inst_{}", TardisFuns::field.nanoid()), - default_lang: None, - } + AppConfig::builder().build() } } -/// Database configuration / 数据库配置 -/// -/// Database operations need to be enabled ```#[cfg(feature = "reldb")]``` . -/// -/// 数据库的操作需要启用 ```#[cfg(feature = "reldb")]``` . -/// -/// # Examples -/// ```ignore -/// use tardis::basic::config::DBConfig; -/// let config = DBConfig{ -/// url: "mysql://root:123456@localhost:3306/test".to_string(), -/// ..Default::default() -/// }; -/// ``` -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct DBConfig { - /// Whether to enable the database operation function / 是否启用数据库操作功能 - pub enabled: bool, - /// Database access Url, Url with permission information / 数据库访问Url,Url带权限信息 - pub url: String, - /// Maximum number of connections, default 20 / 最大连接数,默认 20 - pub max_connections: u32, - /// Minimum number of connections, default 5 / 最小连接数,默认 5 - pub min_connections: u32, - /// Connection timeout / 连接超时时间 - pub connect_timeout_sec: Option, - /// Idle connection timeout / 空闲连接超时时间 - pub idle_timeout_sec: Option, - /// Database module configuration / 数据库模块配置 - pub modules: HashMap, - /// Compatible database type / 兼容数据库类型 - pub compatible_type: CompatibleType, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct DBModuleConfig { - /// Database access Url, Url with permission information / 数据库访问Url,Url带权限信息 - pub url: String, - /// Maximum number of connections, default 20 / 最大连接数,默认 20 - pub max_connections: u32, - /// Minimum number of connections, default 5 / 最小连接数,默认 5 - pub min_connections: u32, - /// Connection timeout / 连接超时时间 - pub connect_timeout_sec: Option, - /// Idle connection timeout / 空闲连接超时时间 - pub idle_timeout_sec: Option, - /// Compatible database type / 兼容数据库类型 - pub compatible_type: CompatibleType, -} - -impl Default for DBConfig { - fn default() -> Self { - DBConfig { - enabled: true, - url: "".to_string(), - max_connections: 20, - min_connections: 5, - connect_timeout_sec: None, - idle_timeout_sec: None, - modules: Default::default(), - compatible_type: CompatibleType::None, - } - } -} - -impl Default for DBModuleConfig { - fn default() -> Self { - DBModuleConfig { - url: "".to_string(), - max_connections: 20, - min_connections: 5, - connect_timeout_sec: None, - idle_timeout_sec: None, - compatible_type: CompatibleType::None, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub enum CompatibleType { - None, - Oracle, -} - -/// Web service configuration / Web服务配置 -/// -/// Web service operations need to be enabled ```#[cfg(feature = "web-server")]``` . -/// -/// Web服务操作需要启用 ```#[cfg(feature = "web-server")]``` . -/// -/// # Examples -/// ```ignore -/// use tardis::basic::config::{WebServerConfig, WebServerModuleConfig}; -/// let config = WebServerConfig { -/// modules: vec![ -/// WebServerModuleConfig { -/// code: "todo".to_string(), -/// title: "todo app".to_string(), -/// doc_urls: [("test env".to_string(), web_url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())].iter().cloned().collect(), -/// ..Default::default() -/// }, -/// WebServerModuleConfig { -/// code: "other".to_string(), -/// title: "other app".to_string(), -/// ..Default::default() -/// }, -/// ], -/// tls_key: Some(TLS_KEY.to_string()), -/// tls_cert: Some(TLS_CERT.to_string()), -/// ..Default::default() -///}; -/// ``` -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct WebServerConfig { - /// Whether to enable the web service operation function / 是否启用Web服务操作功能 - pub enabled: bool, - /// Web service Host, default is `0.0.0.0` / Web服务Host,默认为 `0.0.0.0` - pub host: String, - /// Directly accessible host, same as host by default / 可直接访问的host,默认与host相同 - pub access_host: Option, - /// Web service port, default is `8080` / Web服务端口,默认为 `8080` - pub port: u16, - /// Directly accessible port, same as port by default / 可直接访问的端口,默认与port相同 - pub access_port: Option, - /// Allowed cross-domain sources, default is `*` / 允许的跨域来源,默认为 `*` - pub allowed_origin: String, - /// TLS Key, if this configuration is included then the protocol is HTTPS / TLS Key,如果包含此配置则协议为HTTPS - pub tls_key: Option, - /// TLS certificate / TLS 证书 - pub tls_cert: Option, - /// Whether to hide detailed error messages in the return message / 返回信息中是否隐藏详细错误信息 - pub security_hide_err_msg: bool, - /// Tardis context configuration / Tardis上下文配置 - pub context_conf: WebServerContextConfig, - /// API request path for ``OpenAPI`` / API请求路径,用于 ``OpenAPI`` - /// - /// Formatted as ``[(environment identifier, request path)]`` / 格式为 ``[(环境标识,请求路径)]`` - pub doc_urls: Vec<(String, String)>, - /// Common request headers for ``OpenAPI`` / 公共请求头信息,用于 ``OpenAPI`` - /// - /// Formatted as ``[(header name, header description)]`` / 格式为 ``[(请求头名称,请求头说明)]`` - pub req_headers: Vec<(String, String)>, - /// ``OpenAPI`` UI path / 模``OpenAPI`` UI路径 - pub ui_path: Option, - /// ``OpenAPI`` information path / ``OpenAPI`` 信息路径 - pub spec_path: Option, - /// Enable `UniformError` middleware / 启用 `UniformError` 中间件 - /// - /// It's enabled by default. In some case like running a mocker server, it may be supposed to be closed - pub uniform_error: bool, - /// Web module configuration / Web模块配置 - pub modules: HashMap, -} - -/// Tardis context configuration / Tardis上下文配置 -/// -/// `Tardis Context` [TardisContext](crate::basic::dto::TardisContext) is used to bring in some -/// authentication information when a web request is received. -/// -/// `Tardis上下文` [TardisContext](crate::basic::dto::TardisContext) 用于Web请求时带入一些认证信息. -/// -/// This configuration specifies the source of the [TardisContext](crate::basic::dto::TardisContext). -/// -/// 该配置用于指明 [TardisContext](crate::basic::dto::TardisContext) 的生成来源. -/// -/// First it will try to get [context_header_name](Self::context_header_name) from the request header, -/// and if it is not specified or has no value it will try to get it from the cache. -/// -/// 首先会尝试从请求头信息中获取 [context_header_name](Self::context_header_name) ,如果没指定或是没有值时会尝试从缓存中获取. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct WebServerContextConfig { - /// Tardis context identifier, used to specify the request header name, default is `Tardis-Context` - /// - /// Tardis上下文标识,用于指定请求头名,默认为 `Tardis-Context` - pub context_header_name: String, - /// Tardis context identifier, used to specify the `key` of the cache, default is `tardis::ident::token::` - /// - /// Tardis上下文标识,用于指定缓存的 `key`,默认为 `tardis::ident::token::` - pub token_cache_key: String, -} - -/// Web module configuration / Web模块配置 -/// -/// An application can contain multiple web modules, each of which can have its own independent -/// request root path and API documentation. -/// -/// 一个应用可以包含多个Web模块,每个模块可以有自己独立的请求根路径及API文档. -/// -/// # Examples -/// ```ignore -/// use tardis::basic::config::WebServerModuleConfig; -/// let config = WebServerModuleConfig { -/// code: "todo".to_string(), -/// title: "todo app".to_string(), -/// doc_urls: [ -/// ("test env".to_string(), "http://127.0.0.1:8081".to_string()), -/// ("prod env".to_string(), "http://127.0.0.1:8082".to_string()) -/// ].iter().cloned().collect(), -/// ..Default::default() -/// }; -/// ``` -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct WebServerModuleConfig { - /// Module name for ``OpenAPI`` / 模块名称,用于 ``OpenAPI`` - pub name: String, - /// Module version for ``OpenAPI`` / 模块版本,用于 ``OpenAPI`` - pub version: String, - /// Module API request path for ``OpenAPI`` / 模块API请求路径,用于 ``OpenAPI`` - /// - /// Formatted as ``[(environment identifier, request path)]`` / 格式为 ``[(环境标识,请求路径)]`` - pub doc_urls: Vec<(String, String)>, - /// Module common request headers for ``OpenAPI`` / 模块公共请求头信息,用于 ``OpenAPI`` - /// - /// Formatted as ``[(header name, header description)]`` / 格式为 ``[(请求头名称,请求头说明)]`` - pub req_headers: Vec<(String, String)>, - /// Module ``OpenAPI`` UI path / 模块 ``OpenAPI`` UI路径 - pub ui_path: Option, - /// Module ``OpenAPI`` information path / 模块 ``OpenAPI`` 信息路径 - pub spec_path: Option, - /// Enable `UniformError` middleware / 启用 `UniformError` 中间件 - /// - /// It's enabled by default. In some case like running a mocker server, it may be supposed to be closed - pub uniform_error: bool, -} - -impl Default for WebServerContextConfig { - fn default() -> Self { - WebServerContextConfig { - context_header_name: "Tardis-Context".to_string(), - token_cache_key: "tardis::ident::token::".to_string(), - } - } -} - -impl Default for WebServerConfig { - fn default() -> Self { - WebServerConfig { - enabled: true, - host: "0.0.0.0".to_string(), - port: 8080, - allowed_origin: "*".to_string(), - tls_key: None, - tls_cert: None, - security_hide_err_msg: false, - context_conf: WebServerContextConfig::default(), - doc_urls: [("test env".to_string(), "http://localhost:8080/".to_string())].to_vec(), - req_headers: vec![], - ui_path: Some("ui".to_string()), - spec_path: Some("spec".to_string()), - modules: Default::default(), - uniform_error: true, - access_host: None, - access_port: None, - } - } -} - -impl Default for WebServerModuleConfig { - fn default() -> Self { - WebServerModuleConfig { - name: "Tardis-based application".to_string(), - version: "1.0.0".to_string(), - doc_urls: [("test env".to_string(), "http://localhost:8080/".to_string())].to_vec(), - req_headers: vec![], - ui_path: Some("ui".to_string()), - spec_path: Some("spec".to_string()), - uniform_error: true, - } - } -} - -/// Web client configuration / Web客户端配置 -/// -/// Web client operation needs to be enabled ```#[cfg(feature = "web-client")]``` . -/// -/// Web客户端操作需要启用 ```#[cfg(feature = "web-client")]``` . -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct WebClientConfig { - /// Connection timeout / 连接超时时间 - pub connect_timeout_sec: u64, - /// Request timeout / 请求超时时间 - pub request_timeout_sec: u64, - /// Web client module configuration / Web客户端模块配置 - pub modules: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct WebClientModuleConfig { - /// Connection timeout / 连接超时时间 - pub connect_timeout_sec: u64, - /// Request timeout / 请求超时时间 - pub request_timeout_sec: u64, -} - -impl Default for WebClientConfig { - fn default() -> Self { - WebClientConfig { - connect_timeout_sec: 60, - request_timeout_sec: 60, - modules: Default::default(), - } - } -} - -impl Default for WebClientModuleConfig { - fn default() -> Self { - WebClientModuleConfig { - connect_timeout_sec: 60, - request_timeout_sec: 60, - } - } -} - -/// Distributed cache configuration / 分布式缓存配置 -/// -/// Distributed cache operations need to be enabled ```#[cfg(feature = "cache")]``` . -/// -/// 分布式缓存操作需要启用 ```#[cfg(feature = "cache")]``` . -/// -/// # Examples -/// ```ignore -/// use tardis::basic::config::CacheConfig; -/// let config = CacheConfig { -/// url: "redis://123456@127.0.0.1:6379".to_string(), -/// ..Default::default() -///}; -/// ``` -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct CacheConfig { - /// Whether to enable the distributed cache operation function / 是否启用分布式缓存操作功能 - pub enabled: bool, - /// Cache access Url, Url with permission information / 缓存访问Url,Url带权限信息 - pub url: String, - /// Distributed cache module configuration / 分布式缓存模块配置 - pub modules: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct CacheModuleConfig { - /// Cache access Url, Url with permission information / 缓存访问Url,Url带权限信息 - pub url: String, -} - -impl Default for CacheConfig { - fn default() -> Self { - CacheConfig { - enabled: true, - url: "".to_string(), - modules: Default::default(), - } - } -} - -impl Default for CacheModuleConfig { - fn default() -> Self { - CacheModuleConfig { url: "".to_string() } - } -} - -/// Message queue configuration / 消息队列配置 -/// -/// Message queue operation needs to be enabled ```#[cfg(feature = "mq")]``` . -/// -/// 消息队列操作需要启用 ```#[cfg(feature = "mq")]``` . -/// -/// # Examples -/// ```ignore -/// use tardis::basic::config::MQConfig; -/// let config = MQConfig { -/// url: "amqp://guest:guest@127.0.0.1:5672/%2f".to_string(), -/// ..Default::default() -///}; -/// ``` -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct MQConfig { - /// Whether to enable the message queue operation function / 是否启用消息队列操作功能 - pub enabled: bool, - /// Message queue access Url, Url with permission information / 消息队列访问Url,Url带权限信息 - pub url: String, - /// Message queue module configuration / MQ模块配置 - pub modules: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct MQModuleConfig { - /// Message queue access Url, Url with permission information / 消息队列访问Url,Url带权限信息 - pub url: String, -} - -impl Default for MQConfig { - fn default() -> Self { - MQConfig { - enabled: true, - url: "".to_string(), - modules: Default::default(), - } - } -} - -impl Default for MQModuleConfig { - fn default() -> Self { - MQModuleConfig { url: "".to_string() } - } -} - -/// Search configuration / 搜索配置 -/// -/// Search operation needs to be enabled ```#[cfg(feature = "web-client")]``` . -/// -/// 搜索操作需要启用 ```#[cfg(feature = "web-client")]``` . -/// -/// # Examples -/// ```ignore -/// use tardis::basic::config::ESConfig; -/// let config = ESConfig { -/// url: "https://elastic:123456@127.0.0.1:9200".to_string(), -/// ..Default::default() -///}; -/// ``` -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct SearchConfig { - /// Whether to enable the search function / 是否启用搜索操作功能 - pub enabled: bool, - /// Search access Url, Url with permission information / 搜索访问Url,Url带权限信息 - pub url: String, - /// Timeout / 操作超时时间 - pub timeout_sec: u64, - /// Search module configuration / 搜索模块配置 - pub modules: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct SearchModuleConfig { - /// Search access Url, Url with permission information / 搜索访问Url,Url带权限信息 - pub url: String, - /// Timeout / 操作超时时间 - pub timeout_sec: u64, -} - -impl Default for SearchConfig { - fn default() -> Self { - SearchConfig { - enabled: true, - url: "".to_string(), - timeout_sec: 60, - modules: Default::default(), - } - } -} - -impl Default for SearchModuleConfig { - fn default() -> Self { - SearchModuleConfig { - url: "".to_string(), - timeout_sec: 60, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct MailConfig { - /// Whether to enable the mail function / 是否启用邮件操作功能 - pub enabled: bool, - pub smtp_host: String, - pub smtp_port: u16, - pub smtp_username: String, - pub smtp_password: String, - pub default_from: String, - pub starttls: bool, - /// Mail module configuration / 邮件模块配置 - pub modules: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct MailModuleConfig { - pub smtp_host: String, - pub smtp_port: u16, - pub smtp_username: String, - pub smtp_password: String, - pub default_from: String, - pub starttls: bool, -} - -impl Default for MailConfig { - fn default() -> Self { - MailConfig { - enabled: true, - smtp_host: "".to_string(), - smtp_port: 0, - smtp_username: "".to_string(), - smtp_password: "".to_string(), - default_from: "".to_string(), - modules: Default::default(), - starttls: false, - } - } -} - -impl Default for MailModuleConfig { - fn default() -> Self { - MailModuleConfig { - smtp_host: "".to_string(), - smtp_port: 0, - smtp_username: "".to_string(), - smtp_password: "".to_string(), - default_from: "".to_string(), - starttls: false, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct OSConfig { - /// Whether to enable the object storage function / 是否启用对象存储操作功能 - pub enabled: bool, - /// s3/oss/obs, Support amazon s3 / aliyun oss / huaweicloud obs - pub kind: String, - pub endpoint: String, - pub ak: String, - pub sk: String, - pub region: String, - pub default_bucket: String, - /// Object Storage module configuration / 对象存储模块配置 - pub modules: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct OSModuleConfig { - /// s3/oss/obs, Support amazon s3 / aliyun oss / huaweicloud obs - pub kind: String, - pub endpoint: String, - pub ak: String, - pub sk: String, - pub region: String, - pub default_bucket: String, -} - -impl Default for OSConfig { - fn default() -> Self { - OSConfig { - enabled: true, - kind: "s3".to_string(), - endpoint: "".to_string(), - ak: "".to_string(), - sk: "".to_string(), - region: "".to_string(), - default_bucket: "".to_string(), - modules: Default::default(), - } - } -} - -impl Default for OSModuleConfig { - fn default() -> Self { - OSModuleConfig { - kind: "s3".to_string(), - endpoint: "".to_string(), - ak: "".to_string(), - sk: "".to_string(), - region: "".to_string(), - default_bucket: "".to_string(), - } - } -} - -/// Advanced configuration / 高级配置 -#[derive(Default, Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct AdvConfig { - /// Whether to capture the error stack / 是否捕捉错误堆栈 - /// - /// Enable it to locate errors easily, but it will affect performance. - /// - /// 启用后可方便定位错误,但会影响性能. - pub backtrace: bool, - - /// Configure field encryption salt value / 配置字段加密盐值 - /// - /// Using the aes-ecb algorithm, salt consists of 16-bit English or numeric characters. - /// - /// Usage: - /// . Open https://www.javainuse.com/aesgenerator and output the following: - /// `Enter Plain Text to Encrypt ` = `Value to be encrypted` , `Select Mode` = `ECB` , `Key Size in Bits` = `128` , `Enter Secret Key` = `Value of this field` , `Output Text Format` = `Hex` - /// . Click `Encrypt` to wrap the generated value in `ENC(xx)` to replace the original value - pub salt: String, -} - +/// ConfCenterConfig / 配置中心的配置 #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(default)] pub struct ConfCenterConfig { @@ -732,39 +200,6 @@ pub struct ConfCenterConfig { pub config_change_polling_interval: Option, } -#[cfg(feature = "conf-remote")] -impl ConfCenterConfig { - /// Reload configuration on remote configuration change / 远程配置变更时重新加载配置 - #[must_use] - pub fn reload_on_remote_config_change(&self, relative_path: Option<&str>) -> tokio::sync::mpsc::Sender<()> { - let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(1); - let relative_path = relative_path.map(str::to_string); - tokio::spawn(async move { - match rx.recv().await { - Some(_) => {} - None => { - tracing::debug!("[Tardis.config] Configuration update channel closed"); - return; - } - }; - if let Ok(config) = TardisConfig::init(relative_path.as_deref()).await { - match TardisFuns::hot_reload(config).await { - Ok(_) => { - tracing::info!("[Tardis.config] Tardis hot reloaded"); - } - Err(e) => { - tracing::error!("[Tardis.config] Tardis shutdown with error {}", e); - } - } - } else { - tracing::error!("[Tardis.config] Configuration update failed: Failed to load configuration"); - } - tracing::debug!("[Tardis.config] Configuration update listener closed") - }); - tx - } -} - impl Default for ConfCenterConfig { fn default() -> Self { ConfCenterConfig { @@ -780,39 +215,6 @@ impl Default for ConfCenterConfig { } } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct LogConfig { - pub level: String, - #[cfg(feature = "tracing")] - pub endpoint: String, - #[cfg(feature = "tracing")] - pub protocol: String, - #[cfg(feature = "tracing")] - pub server_name: String, - #[cfg(feature = "tracing")] - pub headers: Option, -} - -impl Default for LogConfig { - fn default() -> Self { - #[cfg(feature = "tracing")] - { - LogConfig { - level: "info".to_string(), - endpoint: "http://localhost:4317".to_string(), - protocol: "grpc".to_string(), - server_name: "tardis-tracing".to_string(), - headers: None, - } - } - #[cfg(not(feature = "tracing"))] - { - LogConfig { level: "info".to_string() } - } - } -} - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct ClusterConfig { diff --git a/tardis/src/config/config_dto/component.rs b/tardis/src/config/config_dto/component.rs new file mode 100644 index 00000000..b193f3f4 --- /dev/null +++ b/tardis/src/config/config_dto/component.rs @@ -0,0 +1,139 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use typed_builder::TypedBuilder; +use url::Url; + +pub(crate) mod db; +pub use db::*; +pub(crate) mod web_server; +pub use web_server::*; +pub(crate) mod web_client; +pub use web_client::*; +pub(crate) mod cache; +pub use cache::*; +pub(crate) mod mq; +pub use mq::*; +pub(crate) mod search; +pub use search::*; +pub(crate) mod mail; +pub use mail::*; +pub(crate) mod os; +pub use os::*; + +/// # Tardis Component Configuration +/// +/// common structure for components with one defualt module and many submodules +/// +/// - common: common config for all modules, default to `()` +/// - default: default module config +/// - modules: submodule configs +/// +/// Common config should have a default value. +/// +/// ## Construct from submodule config +/// For those common config has a default value, you can construct it from submodule config through `From` trait. +/// ```ignore +/// SomeConfig::from(submodule_config); +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +pub struct TardisComponentConfig { + #[serde(flatten)] + #[builder(default, setter(into))] + common: C, + #[serde(flatten)] + pub default: T, + #[builder(default, setter(into))] + #[serde(default = "Default::default")] + pub modules: HashMap, +} + +impl std::ops::Deref for TardisComponentConfig { + type Target = C; + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl Default for TardisComponentConfig +where + T: Default, + C: Default, +{ + fn default() -> Self { + Self { + common: Default::default(), + default: Default::default(), + modules: HashMap::new(), + } + } +} + +impl From for TardisComponentConfig { + fn from(value: T) -> Self { + Self { + common: Default::default(), + default: value, + modules: HashMap::new(), + } + } +} + +pub type DBConfig = TardisComponentConfig; + +pub type CacheConfig = TardisComponentConfig; + +pub type WebServerConfig = TardisComponentConfig; + +pub type WebClientConfig = TardisComponentConfig; + +pub type MQConfig = TardisComponentConfig; + +pub type SearchConfig = TardisComponentConfig; + +pub type MailConfig = TardisComponentConfig; + +pub type OSConfig = TardisComponentConfig; + +/// Advanced configuration / 高级配置 +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +#[serde(default)] +pub struct AdvConfig { + /// Whether to capture the error stack / 是否捕捉错误堆栈 + /// + /// Enable it to locate errors easily, but it will affect performance. + /// + /// 启用后可方便定位错误,但会影响性能. + #[builder(default = false)] + pub backtrace: bool, + + /// Configure field encryption salt value / 配置字段加密盐值 + /// + /// Using the aes-ecb algorithm, salt consists of 16-bit English or numeric characters. + /// + /// Usage: + /// . Open https://www.javainuse.com/aesgenerator and output the following: + /// `Enter Plain Text to Encrypt ` = `Value to be encrypted` , `Select Mode` = `ECB` , `Key Size in Bits` = `128` , `Enter Secret Key` = `Value of this field` , `Output Text Format` = `Hex` + /// . Click `Encrypt` to wrap the generated value in `ENC(xx)` to replace the original value + #[builder(default)] + pub salt: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] +pub struct ConfCenterConfig { + #[builder(default = "nacos".to_string())] + pub kind: String, + pub url: Url, + #[builder(default)] + pub username: String, + #[builder(default)] + pub password: String, + #[builder(default = Some("default".to_string()))] + pub group: Option, + #[builder(default = Some("toml".to_string()))] + pub format: Option, + #[builder(default)] + pub namespace: Option, + #[builder(default = Some(30000), setter(strip_option))] + /// config change polling interval, in milliseconds, default is 30000ms / 配置变更轮询间隔,单位毫秒, 默认30000ms + pub config_change_polling_interval: Option, +} diff --git a/tardis/src/config/config_dto/component/cache.rs b/tardis/src/config/config_dto/component/cache.rs new file mode 100644 index 00000000..39d88281 --- /dev/null +++ b/tardis/src/config/config_dto/component/cache.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; +use url::Url; +/// Distributed cache configuration / 分布式缓存配置 +/// +/// Distributed cache operations need to be enabled ```#[cfg(feature = "cache")]``` . +/// +/// 分布式缓存操作需要启用 ```#[cfg(feature = "cache")]``` . +/// +/// # Examples +/// ```ignore +/// use tardis::basic::config::CacheModuleConfig; +/// let config = CacheModuleConfig { +/// url: "redis://123456@127.0.0.1:6379".to_string(), +/// ..Default::default() +///}; +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] +pub struct CacheModuleConfig { + /// Cache access Url, Url with permission information / 缓存访问Url,Url带权限信息 + pub url: Url, +} diff --git a/tardis/src/config/config_dto/component/db.rs b/tardis/src/config/config_dto/component/db.rs new file mode 100644 index 00000000..e3b52081 --- /dev/null +++ b/tardis/src/config/config_dto/component/db.rs @@ -0,0 +1,53 @@ +use serde::{Deserialize, Serialize}; + +use typed_builder::TypedBuilder; + +/// Database module configuration / 数据库模块配置 +/// +/// Database operations need to be enabled ```#[cfg(feature = "reldb")]``` . +/// +/// 数据库的操作需要启用 ```#[cfg(feature = "reldb")]``` . +/// +/// # Examples +/// ```ignore +/// use tardis::basic::config::DBConfig; +/// let config = DBConfig{ +/// url: "mysql://root:123456@localhost:3306/test".to_string(), +/// ..Default::default() +/// }; +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +#[serde(default)] +pub struct DBModuleConfig { + #[builder(setter(into))] + /// Database access Url, Url with permission information / 数据库访问Url,Url带权限信息 + pub url: String, + /// Maximum number of connections, default 20 / 最大连接数,默认 20 + #[builder(default = 20)] + pub max_connections: u32, + /// Minimum number of connections, default 5 / 最小连接数,默认 5 + #[builder(default = 5)] + pub min_connections: u32, + /// Connection timeout / 连接超时时间 + #[builder(default, setter(strip_option))] + pub connect_timeout_sec: Option, + /// Idle connection timeout / 空闲连接超时时间 + #[builder(default, setter(strip_option))] + pub idle_timeout_sec: Option, + /// Compatible database type / 兼容数据库类型 + #[builder(default)] + pub compatible_type: CompatibleType, +} + +impl Default for DBModuleConfig { + fn default() -> Self { + DBModuleConfig::builder().url("").build() + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] +pub enum CompatibleType { + #[default] + None, + Oracle, +} diff --git a/tardis/src/config/config_dto/component/mail.rs b/tardis/src/config/config_dto/component/mail.rs new file mode 100644 index 00000000..a20bffa7 --- /dev/null +++ b/tardis/src/config/config_dto/component/mail.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use typed_builder::TypedBuilder; + +/// Mail module configuration / 邮件模块配置 +/// +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] +pub struct MailModuleConfig { + #[builder(setter(into))] + pub smtp_host: String, + #[builder(default = 587)] + pub smtp_port: u16, + #[builder(setter(into))] + pub smtp_username: String, + #[builder(setter(into))] + pub smtp_password: String, + #[builder(setter(into))] + pub default_from: String, + #[builder(default = false)] + pub starttls: bool, +} diff --git a/tardis/src/config/config_dto/component/mq.rs b/tardis/src/config/config_dto/component/mq.rs new file mode 100644 index 00000000..019406a3 --- /dev/null +++ b/tardis/src/config/config_dto/component/mq.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; +use url::Url; + +/// Message queue configuration / 消息队列配置 +/// +/// Message queue operation needs to be enabled ```#[cfg(feature = "mq")]``` . +/// +/// 消息队列操作需要启用 ```#[cfg(feature = "mq")]``` . +/// +/// # Examples +/// ```ignore +/// use tardis::basic::config::MQModuleConfig; +/// let config = MQModuleConfig { +/// url: "amqp://guest:guest@127.0.0.1:5672/%2f".parse().unwrap(), +/// ..Default::default() +///}; +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +pub struct MQModuleConfig { + /// Message queue access Url, Url with permission information / 消息队列访问Url,Url带权限信息 + pub url: Url, +} diff --git a/tardis/src/config/config_dto/component/os.rs b/tardis/src/config/config_dto/component/os.rs new file mode 100644 index 00000000..44bc56a5 --- /dev/null +++ b/tardis/src/config/config_dto/component/os.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use typed_builder::TypedBuilder; + +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] +#[serde(default)] +pub struct OSModuleConfig { + /// s3/oss/obs, Support amazon s3 / aliyun oss / huaweicloud obs + #[builder(default = "s3".to_string(), setter(into))] + pub kind: String, + #[builder(default, setter(into))] + pub endpoint: String, + #[builder(default, setter(into))] + pub ak: String, + #[builder(default, setter(into))] + pub sk: String, + #[builder(default, setter(into))] + pub region: String, + #[builder(default, setter(into))] + pub default_bucket: String, +} + +impl Default for OSModuleConfig { + fn default() -> Self { + Self::builder().build() + } +} diff --git a/tardis/src/config/config_dto/component/search.rs b/tardis/src/config/config_dto/component/search.rs new file mode 100644 index 00000000..4fa8c60e --- /dev/null +++ b/tardis/src/config/config_dto/component/search.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; +use url::Url; +/// Search configuration / 搜索配置 +/// +/// Search operation needs to be enabled ```#[cfg(feature = "web-client")]``` . +/// +/// 搜索操作需要启用 ```#[cfg(feature = "web-client")]``` . +/// +/// # Examples +/// ```ignore +/// use tardis::basic::config::SearchModuleConfig; +/// let config = SearchModuleConfig { +/// url: "https://elastic:123456@127.0.0.1:9200".parse().unwrap(), +/// ..Default::default() +///}; +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] +pub struct SearchModuleConfig { + /// Search access Url, Url with permission information / 搜索访问Url,Url带权限信息 + pub url: Url, + #[builder(default = 60)] + /// Timeout / 操作超时时间 + pub timeout_sec: u64, +} diff --git a/tardis/src/config/config_dto/component/web_client.rs b/tardis/src/config/config_dto/component/web_client.rs new file mode 100644 index 00000000..8cbffb7c --- /dev/null +++ b/tardis/src/config/config_dto/component/web_client.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +/// Web client configuration / Web客户端配置 +/// +/// Web client operation needs to be enabled ```#[cfg(feature = "web-client")]``` . +/// +/// Web客户端操作需要启用 ```#[cfg(feature = "web-client")]``` . +#[derive(Debug, Serialize, Deserialize, Clone, TypedBuilder)] +#[serde(default)] +pub struct WebClientModuleConfig { + #[builder(default = 60, setter(into))] + /// Connection timeout / 连接超时时间 + pub connect_timeout_sec: u64, + #[builder(default = 60, setter(into))] + /// Request timeout / 请求超时时间 + pub request_timeout_sec: u64, +} + +impl Default for WebClientModuleConfig { + fn default() -> Self { + Self::builder().build() + } +} diff --git a/tardis/src/config/config_dto/component/web_server.rs b/tardis/src/config/config_dto/component/web_server.rs new file mode 100644 index 00000000..59f61fe2 --- /dev/null +++ b/tardis/src/config/config_dto/component/web_server.rs @@ -0,0 +1,164 @@ +use std::net::{IpAddr, Ipv4Addr}; + +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +/// Web service configuration / Web服务配置 +/// +/// Web service operations need to be enabled ```#[cfg(feature = "web-server")]``` . +/// +/// Web服务操作需要启用 ```#[cfg(feature = "web-server")]``` . +/// +/// # Examples +/// ```ignore +/// use tardis::basic::config::{WebServerConfig, WebServerModuleConfig}; +/// let config = WebServerConfig { +/// modules: vec![ +/// WebServerModuleConfig { +/// code: "todo".to_string(), +/// title: "todo app".to_string(), +/// doc_urls: [("test env".to_string(), web_url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())].iter().cloned().collect(), +/// ..Default::default() +/// }, +/// WebServerModuleConfig { +/// code: "other".to_string(), +/// title: "other app".to_string(), +/// ..Default::default() +/// }, +/// ], +/// tls_key: Some(TLS_KEY.to_string()), +/// tls_cert: Some(TLS_CERT.to_string()), +/// ..Default::default() +///}; +/// ``` + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +#[serde(default)] +pub struct WebServerCommonConfig { + #[builder(default = IpAddr::V4(Ipv4Addr::UNSPECIFIED), setter(into))] + /// Web service Host, default is `0.0.0.0` / Web服务Host,默认为 `0.0.0.0` + pub host: IpAddr, + #[builder(default, setter(strip_option, into))] + /// Directly accessible host, same as host by default / 可直接访问的host,默认与host相同 + pub access_host: Option, + #[builder(default = 8080)] + /// Web service port, default is `8080` / Web服务端口,默认为 `8080` + pub port: u16, + #[builder(default, setter(strip_option))] + /// Directly accessible port, same as port by default / 可直接访问的端口,默认与port相同 + pub access_port: Option, + #[builder(default = String::from("*"), setter(into))] + /// Allowed cross-domain sources, default is `*` / 允许的跨域来源,默认为 `*` + pub allowed_origin: String, + #[builder(default, setter(strip_option, into))] + /// TLS Key, if this configuration is included then the protocol is HTTPS / TLS Key,如果包含此配置则协议为HTTPS + pub tls_key: Option, + #[builder(default, setter(strip_option, into))] + /// TLS certificate / TLS 证书 + pub tls_cert: Option, + #[builder(default)] + /// Tardis context configuration / Tardis上下文配置 + pub context_conf: WebServerContextConfig, + #[builder(default = false)] + pub security_hide_err_msg: bool, +} + +/// Tardis context configuration / Tardis上下文配置 +/// +/// `Tardis Context` [TardisContext](crate::basic::dto::TardisContext) is used to bring in some +/// authentication information when a web request is received. +/// +/// `Tardis上下文` [TardisContext](crate::basic::dto::TardisContext) 用于Web请求时带入一些认证信息. +/// +/// This configuration specifies the source of the [TardisContext](crate::basic::dto::TardisContext). +/// +/// 该配置用于指明 [TardisContext](crate::basic::dto::TardisContext) 的生成来源. +/// +/// First it will try to get [context_header_name](Self::context_header_name) from the request header, +/// and if it is not specified or has no value it will try to get it from the cache. +/// +/// 首先会尝试从请求头信息中获取 [context_header_name](Self::context_header_name) ,如果没指定或是没有值时会尝试从缓存中获取. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +#[serde(default)] +pub struct WebServerContextConfig { + /// Tardis context identifier, used to specify the request header name, default is `Tardis-Context` + /// + /// Tardis上下文标识,用于指定请求头名,默认为 `Tardis-Context` + #[builder(default = String::from("Tardis-Context"), setter(into))] + pub context_header_name: String, + #[builder(default = String::from("tardis::ident::token::"), setter(into))] + /// Tardis context identifier, used to specify the `key` of the cache, default is `tardis::ident::token::` + /// + /// Tardis上下文标识,用于指定缓存的 `key`,默认为 `tardis::ident::token::` + pub token_cache_key: String, +} + +/// Web module configuration / Web模块配置 +/// +/// An application can contain multiple web modules, each of which can have its own independent +/// request root path and API documentation. +/// +/// 一个应用可以包含多个Web模块,每个模块可以有自己独立的请求根路径及API文档. +/// +/// # Examples +/// ```ignore +/// use tardis::basic::config::WebServerModuleConfig; +/// let config = WebServerModuleConfig { +/// code: "todo".to_string(), +/// title: "todo app".to_string(), +/// doc_urls: [ +/// ("test env".to_string(), "http://127.0.0.1:8081".to_string()), +/// ("prod env".to_string(), "http://127.0.0.1:8082".to_string()) +/// ].iter().cloned().collect(), +/// ..Default::default() +/// }; +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +#[serde(default)] +pub struct WebServerModuleConfig { + #[builder(default = "Tardis-based application".to_string(), setter(into))] + /// Module name for ``OpenAPI`` / 模块名称,用于 ``OpenAPI`` + pub name: String, + #[builder(default = "1.0.0".to_string(), setter(into))] + /// Module version for ``OpenAPI`` / 模块版本,用于 ``OpenAPI`` + pub version: String, + #[builder(default = vec![("test env".to_string(), "http://localhost:8080/".to_string())], setter(into))] + /// API request path for ``OpenAPI`` / API请求路径,用于 ``OpenAPI`` + /// + /// Formatted as ``[(environment identifier, request path)]`` / 格式为 ``[(环境标识,请求路径)]`` + pub doc_urls: Vec<(String, String)>, + #[builder(default, setter(into))] + /// Common request headers for ``OpenAPI`` / 公共请求头信息,用于 ``OpenAPI`` + /// + /// Formatted as ``[(header name, header description)]`` / 格式为 ``[(请求头名称,请求头说明)]`` + pub req_headers: Vec<(String, String)>, + #[builder(default = Some(String::from("ui")), setter(strip_option, into))] + /// ``OpenAPI`` UI path / 模``OpenAPI`` UI路径 + pub ui_path: Option, + #[builder(default = Some(String::from("spec")), setter(strip_option, into))] + /// ``OpenAPI`` information path / ``OpenAPI`` 信息路径 + pub spec_path: Option, + #[builder(default = true)] + /// Enable `UniformError` middleware / 启用 `UniformError` 中间件 + /// + /// It's enabled by default. In some cases like running a mocker server, this may be supposed to be closed + pub uniform_error: bool, +} + +impl Default for WebServerContextConfig { + fn default() -> Self { + Self::builder().build() + } +} + +impl Default for WebServerModuleConfig { + fn default() -> Self { + Self::builder().build() + } +} + +impl Default for WebServerCommonConfig { + fn default() -> Self { + Self::builder().build() + } +} diff --git a/tardis/src/config/config_dto/log.rs b/tardis/src/config/config_dto/log.rs new file mode 100644 index 00000000..7e479784 --- /dev/null +++ b/tardis/src/config/config_dto/log.rs @@ -0,0 +1,76 @@ +use serde::{Deserialize, Serialize}; +use tracing_subscriber::filter::Directive; +use typed_builder::TypedBuilder; + +#[cfg(feature = "tracing")] +mod tracing; +#[cfg(feature = "tracing")] +pub use tracing::*; +#[cfg(feature = "tracing-appender")] +mod tracing_appender; +#[cfg(feature = "tracing-appender")] +pub use tracing_appender::*; + +/// # Log configure +/// +/// - level: global log level, default to `info` +/// - directives: log level with targets and modules, e.g. `tardis=debug,sqlx=info` +/// ## Example +/// ```toml +/// [fw.log] +/// level = "info" +/// directives = ["tardis=debug", "sqlx=info"] +/// ``` +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TypedBuilder)] +#[serde(default)] +pub struct LogConfig { + #[builder(default = "info".parse::().expect(""), setter(into))] + #[serde(deserialize_with = "deserialize_directive", serialize_with = "serialize_directive")] + pub level: Directive, + #[builder(default, setter(into))] + #[serde(deserialize_with = "deserialize_directives", serialize_with = "serialize_directives")] + pub directives: Vec, + #[cfg(feature = "tracing")] + #[builder(default)] + pub tracing: TracingConfig, + #[cfg(feature = "tracing-appender")] + #[builder(default)] + /// tracing appender config + /// a `None` value means no file output + pub tracing_appender: Option, +} + +fn serialize_directive(value: &Directive, serializer: S) -> Result +where + S: serde::Serializer, +{ + value.to_string().serialize(serializer) +} + +fn deserialize_directive<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + String::deserialize(deserializer)?.parse::().map_err(serde::de::Error::custom) +} +fn serialize_directives(value: &[Directive], serializer: S) -> Result +where + S: serde::Serializer, +{ + let directives: Vec = value.iter().map(|d| d.to_string()).collect(); + directives.serialize(serializer) +} + +fn deserialize_directives<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let directives: Vec = Vec::deserialize(deserializer)?; + directives.into_iter().map(|d| d.parse::().map_err(serde::de::Error::custom)).collect() +} + +impl Default for LogConfig { + fn default() -> Self { + LogConfig::builder().build() + } +} diff --git a/tardis/src/config/config_dto/log/tracing.rs b/tardis/src/config/config_dto/log/tracing.rs new file mode 100644 index 00000000..52d99c7f --- /dev/null +++ b/tardis/src/config/config_dto/log/tracing.rs @@ -0,0 +1,70 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use typed_builder::TypedBuilder; + +use crate::basic::error::TardisError; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum OtlpProtocol { + #[default] + Grpc, + HttpProtobuf, +} + +impl ToString for OtlpProtocol { + fn to_string(&self) -> String { + match self { + OtlpProtocol::Grpc => "grpc".to_string(), + OtlpProtocol::HttpProtobuf => "http/protobuf".to_string(), + } + } +} + +impl FromStr for OtlpProtocol { + type Err = TardisError; + + fn from_str(s: &str) -> Result { + match s { + "grpc" => Ok(OtlpProtocol::Grpc), + "http/protobuf" => Ok(OtlpProtocol::HttpProtobuf), + _ => Err(TardisError::conflict(&format!("[Tracing] Unsupported protocol {s}"), "")), + } + } +} + +impl Serialize for OtlpProtocol { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(self.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for OtlpProtocol { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + OtlpProtocol::from_str(s.as_str()).map_err(serde::de::Error::custom) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, TypedBuilder)] +pub struct TracingConfig { + #[cfg(feature = "tracing")] + #[builder(default = "http://localhost:4317".to_string(), setter(into))] + pub endpoint: String, + #[cfg(feature = "tracing")] + #[builder(default)] + pub protocol: OtlpProtocol, + #[cfg(feature = "tracing")] + #[builder(default = "tardis-tracing".to_string(), setter(into))] + pub server_name: String, + #[cfg(feature = "tracing")] + #[builder(default, setter(into, strip_option))] + pub headers: Option, +} + +impl Default for TracingConfig { + fn default() -> Self { + Self::builder().build() + } +} diff --git a/tardis/src/config/config_dto/log/tracing_appender.rs b/tardis/src/config/config_dto/log/tracing_appender.rs new file mode 100644 index 00000000..56cc130c --- /dev/null +++ b/tardis/src/config/config_dto/log/tracing_appender.rs @@ -0,0 +1,33 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use tracing_appender::rolling::Rotation; +use typed_builder::TypedBuilder; +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, TypedBuilder, Default)] +pub struct TracingAppenderConfig { + #[builder(default, setter(into))] + pub rotation: TracingAppenderRotation, + pub dir: PathBuf, + pub filename: PathBuf, +} + +#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TracingAppenderRotation { + #[default] + Never, + Minutely, + Hourly, + Daily, +} + +impl From for Rotation { + fn from(val: TracingAppenderRotation) -> Self { + match val { + TracingAppenderRotation::Never => Rotation::NEVER, + TracingAppenderRotation::Minutely => Rotation::MINUTELY, + TracingAppenderRotation::Hourly => Rotation::HOURLY, + TracingAppenderRotation::Daily => Rotation::DAILY, + } + } +} diff --git a/tardis/src/config/config_nacos/nacos_client.rs b/tardis/src/config/config_nacos/nacos_client.rs index 207b2541..9a820e48 100644 --- a/tardis/src/config/config_nacos/nacos_client.rs +++ b/tardis/src/config/config_nacos/nacos_client.rs @@ -2,11 +2,12 @@ use std::{collections::HashMap, fmt::Display, io::Read, sync::Arc}; use derive_more::Display; // use futures::TryFutureExt; -use crypto::{digest::Digest, md5::Md5}; use reqwest::Error as ReqwestError; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; -use tracing::debug; +use tracing::{debug, trace}; + +use crate::TardisFuns; const ACCESS_TOKEN_FIELD: &str = "accessToken"; @@ -208,11 +209,11 @@ impl NacosClient { } let mut params = HashMap::new(); params.insert("Listening-Configs", descriptor.as_listening_configs().await); - debug!("[Tardis.Config] listen_config Listening-Configs: {:?}", params.get("Listening-Configs")); + trace!("[Tardis.Config] listen_config Listening-Configs: {:?}", params.get("Listening-Configs")); let mut resp = self.listen_config_inner(¶ms).await?; - debug!("[Tardis.Config] listen_config resp: {:?}", resp); + trace!("[Tardis.Config] listen_config resp: {:?}", resp); // case of token expired if resp.status() == reqwest::StatusCode::FORBIDDEN { self.relogin().await?; @@ -257,10 +258,8 @@ impl<'a> NacosConfigDescriptor<'a> { /// update md5 value by content pub async fn update_by_content(&self, content: &str) { - let mut encoder = Md5::new(); - encoder.input_str(content); - let result = encoder.result_str(); - self.md5.lock().await.replace(result); + let md5 = TardisFuns::crypto.digest.md5(content).expect("fail to calculate md5"); + self.md5.lock().await.replace(md5); } /// data format: `dataId%02Group%02contentMD5%02tenant%01` or `dataId%02Group%02contentMD5%01` diff --git a/tardis/src/config/config_processor.rs b/tardis/src/config/config_processor.rs index ebd58e3b..e6ef0996 100644 --- a/tardis/src/config/config_processor.rs +++ b/tardis/src/config/config_processor.rs @@ -12,6 +12,7 @@ use crate::basic::fetch_profile; use crate::basic::locale::TardisLocale; use crate::basic::result::TardisResult; use crate::config::config_dto::FrameworkConfig; +use crate::TardisFuns; use tracing::{debug, info}; use super::config_dto::{ConfCenterConfig, TardisConfig}; @@ -144,7 +145,7 @@ impl TardisConfig { _ => return Err(error.into()), }, } - let mut framework_config = match conf.get::("fw") { + let framework_config = match conf.get::("fw") { Ok(fw) => fw, Err(error) => match error { ConfigError::NotFound(_) => { @@ -156,16 +157,6 @@ impl TardisConfig { }; env::set_var("RUST_BACKTRACE", if framework_config.adv.backtrace { "1" } else { "0" }); - // If set log level,reload trace filter - if let Some(log_config) = framework_config.log.as_mut() { - if let Some(log_level) = std::env::var_os("RUST_LOG") { - log_config.level = log_level.into_string().unwrap_or_default(); - } - if crate::basic::tracing::GLOBAL_RELOAD_HANDLE.get().is_some() { - let log_level = log_config.level.as_str(); - crate::basic::tracing::TardisTracing::update_log_level(log_level)?; - } - } let config = if framework_config.adv.salt.is_empty() { TardisConfig { @@ -203,8 +194,42 @@ pub(crate) trait ConfCenterProcess: Sync + Send + std::fmt::Debug { fn register_to_config(&self, conf: ConfigBuilder) -> ConfigBuilder; } +#[cfg(feature = "conf-remote")] +impl ConfCenterConfig { + /// Reload configuration on remote configuration change / 远程配置变更时重新加载配置 + #[must_use] + pub fn reload_on_remote_config_change(&self, relative_path: Option<&str>) -> tokio::sync::mpsc::Sender<()> { + let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(1); + let relative_path = relative_path.map(str::to_string); + tokio::spawn(async move { + match rx.recv().await { + Some(_) => {} + None => { + tracing::debug!("[Tardis.config] Configuration update channel closed"); + return; + } + }; + if let Ok(config) = TardisConfig::init(relative_path.as_deref()).await { + match TardisFuns::hot_reload(config).await { + Ok(_) => { + tracing::info!("[Tardis.config] Tardis hot reloaded"); + } + Err(e) => { + tracing::error!("[Tardis.config] Tardis shutdown with error {}", e); + } + } + } else { + tracing::error!("[Tardis.config] Configuration update failed: Failed to load configuration"); + } + tracing::debug!("[Tardis.config] Configuration update listener closed") + }); + tx + } +} + #[cfg(feature = "crypto")] fn decryption(text: &str, salt: &str) -> TardisResult { + use crate::crypto::crypto_aead::algorithm::Aes128; if salt.len() != 16 { return Err(TardisError::format_error("[Tardis.Config] [salt] Length must be 16", "")); } @@ -213,7 +238,13 @@ fn decryption(text: &str, salt: &str) -> TardisResult { .replace_all(text, |captures: ®ex::Captures| { let data = captures.get(1).map_or("", |m| m.as_str()).to_string(); let data = &data[4..data.len() - 1]; - crate::TardisFuns::crypto.aes.decrypt_ecb(data, salt).expect("[Tardis.Config] Decryption error") + let data = hex::decode(data).expect("[Tardis.Config] Decryption error: invalid hex"); + crate::TardisFuns::crypto + .aead + .decrypt_ecb::(data, salt) + .map(String::from_utf8) + .expect("[Tardis.Config] Decryption error") + .expect("[Tardis.Config] Uft8 decode error") }) .to_string(); Ok(text) diff --git a/tardis/src/consts.rs b/tardis/src/consts.rs new file mode 100644 index 00000000..018bbf64 --- /dev/null +++ b/tardis/src/consts.rs @@ -0,0 +1,17 @@ +use std::net::IpAddr; + +// IP addresses +pub const IP_LOCALHOST: IpAddr = IpAddr::V4(std::net::Ipv4Addr::LOCALHOST); +pub const IP_BROADCAST: IpAddr = IpAddr::V4(std::net::Ipv4Addr::BROADCAST); +pub const IP_UNSPECIFIED: IpAddr = IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED); + +pub const IPV6_LOCALHOST: IpAddr = IpAddr::V6(std::net::Ipv6Addr::LOCALHOST); +pub const IPV6_UNSPECIFIED: IpAddr = IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED); + +// env var keys + +// opentelemetry +pub const OTEL_EXPORTER_OTLP_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_ENDPOINT"; +pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL"; +pub const OTEL_EXPORTER_OTLP_HEADERS: &str = "OTEL_EXPORTER_OTLP_HEADERS"; +pub const OTEL_SERVICE_NAME: &str = "OTEL_SERVICE_NAME"; diff --git a/tardis/src/crypto.rs b/tardis/src/crypto.rs index 442f6866..7a3b8d0e 100644 --- a/tardis/src/crypto.rs +++ b/tardis/src/crypto.rs @@ -1,4 +1,4 @@ -pub mod crypto_aes; +pub mod crypto_aead; pub mod crypto_base64; pub mod crypto_digest; pub mod crypto_hex; @@ -8,4 +8,4 @@ pub mod crypto_rsa; #[cfg(feature = "crypto-with-sm")] pub mod crypto_sm2_4; -pub use crypto as rust_crypto; +// pub use crypto as rust_crypto; diff --git a/tardis/src/crypto/crypto_aead.rs b/tardis/src/crypto/crypto_aead.rs new file mode 100644 index 00000000..a7279870 --- /dev/null +++ b/tardis/src/crypto/crypto_aead.rs @@ -0,0 +1,119 @@ +use aead::generic_array::GenericArray; +use aead::{Aead, AeadCore, KeyInit, Payload}; +use cipher::BlockDecryptMut; +use cipher::{block_padding::Pkcs7, BlockCipher, BlockEncryptMut, KeyIvInit}; + +use crate::basic::error::TardisError; +use crate::basic::result::TardisResult; + +use rand::rngs::ThreadRng; + +/// # TardisCryptoAead +/// Aead (Authenticated Encryption with Associated Data) +/// +/// This part includes aead encryption and decryption, and cbc/ecb encryption and decryption. +pub struct TardisCryptoAead {} + +impl TardisCryptoAead { + /// Encrypy with cbc, + /// `A` could be any algorithem implemented `BlockEncryptMut + BlockCipher`, a typical one would be `Aes128`. + /// + /// # **Warning** + /// cbc mode is not recommended, it is not safe enough. + pub fn encrypt_cbc(&self, message: impl AsRef<[u8]>, iv: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult> + where + cbc::Encryptor: KeyIvInit + BlockEncryptMut, + { + let iv = GenericArray::from_slice(iv.as_ref()); + let message = message.as_ref(); + let key = GenericArray::from_slice(key.as_ref()); + let encryptor = as KeyIvInit>::new(key, iv); + let ct = as cipher::BlockEncryptMut>::encrypt_padded_vec_mut::(encryptor, message); + // let ct = encryptor.encrypt_padded_vec_mut::(message); + Ok(ct) + } + + /// Encrypy with ecb, + /// `A` could be any algorithem implemented `BlockEncryptMut + BlockCipher`, a typical one would be `Aes128`. + /// # **Warning** + /// cbc mode is not recommended, it is not safe enough. + pub fn encrypt_ecb(&self, message: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult> + where + ecb::Encryptor: KeyInit + BlockEncryptMut, + { + let message = message.as_ref(); + let key = GenericArray::from_slice(key.as_ref()); + let encryptor = as KeyInit>::new(key); + let ct = encryptor.encrypt_padded_vec_mut::(message); + Ok(ct) + } + + /// Decrypy with cbc, + /// `A` could be any algorithem implemented `BlockEncryptMut + BlockCipher`, a typical one would be `Aes128`. + pub fn decrypt_cbc(&self, message: impl AsRef<[u8]>, iv: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult> + where + cbc::Decryptor: KeyIvInit + BlockDecryptMut, + { + let iv = GenericArray::from_slice(iv.as_ref()); + let message = message.as_ref(); + let key = GenericArray::from_slice(key.as_ref()); + let decryptor = as KeyIvInit>::new(key, iv); + let pt = decryptor.decrypt_padded_vec_mut::(message).map_err(|e| TardisError::internal_error(&e.to_string(), "406-tardis-crypto-aead-decrypt-failed"))?; + Ok(pt) + } + + /// Decrypy with ecb, + /// `A` could be any algorithem implemented `BlockEncryptMut + BlockCipher`, a typical one would be `Aes128`. + pub fn decrypt_ecb(&self, message: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult> + where + ecb::Decryptor: KeyInit + BlockDecryptMut, + { + let key = GenericArray::from_slice(key.as_ref()); + let decryptor = as KeyInit>::new(key); + let pt = decryptor.decrypt_padded_vec_mut::(message.as_ref()).map_err(|e| TardisError::internal_error(&e.to_string(), "406-tardis-crypto-aead-decrypt-failed"))?; + Ok(pt) + } + + /// Encrypt with aead algorithm, + /// `A` could be any algorithem implemented `Aead + KeyInit`, a typical one would be `Aes256Gcm`. + pub fn encrypt(&self, key: impl AsRef<[u8]>, aad: impl AsRef<[u8]>, nonce: impl AsRef<[u8]>, message: impl AsRef<[u8]>) -> TardisResult<(Vec, Vec)> { + let key = GenericArray::from_slice(key.as_ref()); + let nonce = GenericArray::from_slice(nonce.as_ref()); + let payload = Payload { + msg: message.as_ref(), + aad: aad.as_ref(), + }; + let cipher = A::new(key); + let ciphertext = cipher.encrypt(nonce, payload).map_err(|e| TardisError::internal_error(&e.to_string(), "406-tardis-crypto-aead-encrypt-failed"))?; + Ok((ciphertext, nonce.to_vec())) + } + + /// Decrypt with aead algorithm, + pub fn decrypt(&self, key: impl AsRef<[u8]>, aad: impl AsRef<[u8]>, nonce: impl AsRef<[u8]>, message: impl AsRef<[u8]>) -> TardisResult> { + let key = GenericArray::from_slice(key.as_ref()); + let nonce = GenericArray::from_slice(nonce.as_ref()); + let payload = Payload { + msg: message.as_ref(), + aad: aad.as_ref(), + }; + let cipher = A::new(key); + let plaintext = cipher.decrypt(nonce, payload).map_err(|e| TardisError::internal_error(&e.to_string(), "406-tardis-crypto-aead-encrypt-failed"))?; + Ok(plaintext) + } + + /// Generate a random nonce for aead algorithm. + pub fn random_nonce(&self) -> Vec + where + A: AeadCore, + { + let nonce = A::generate_nonce(ThreadRng::default()); + nonce.to_vec() + } +} + +pub mod algorithm { + pub use aes::{Aes128, Aes192, Aes256}; + pub use aes_gcm::{Aes128Gcm, Aes256Gcm}; + pub use aes_gcm_siv::{Aes128GcmSiv, Aes256GcmSiv}; + pub use aes_siv::{Aes128SivAead, Aes256SivAead}; +} diff --git a/tardis/src/crypto/crypto_aes.rs b/tardis/src/crypto/crypto_aes.rs deleted file mode 100644 index 4d4cccfd..00000000 --- a/tardis/src/crypto/crypto_aes.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crypto::buffer::{ReadBuffer, WriteBuffer}; - -use crate::basic::error::TardisError; -use crate::basic::result::TardisResult; - -pub struct TardisCryptoAes; - -/// AES handle / AES处理 -/// -/// # Examples -/// ```ignore -/// use tardis::TardisFuns; -/// let key = TardisFuns::crypto.key.rand_16_hex().unwrap(); -/// let iv = TardisFuns::crypto.key.rand_16_hex().unwrap(); -/// let text = "为什么选择 Rust?"; -/// let encrypted_data = TardisFuns::crypto.aes.encrypt_cbc(text, &key, &iv).unwrap(); -/// let data = TardisFuns::crypto.aes.decrypt_cbc(&encrypted_data, &key, &iv).unwrap(); -/// ``` -impl TardisCryptoAes { - pub fn encrypt_ecb(&self, data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>) -> TardisResult { - self.encrypt(data, hex_key, "", false) - } - - pub fn encrypt_cbc(&self, data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>, hex_iv: impl AsRef<[u8]>) -> TardisResult { - self.encrypt(data, hex_key, hex_iv, true) - } - - fn encrypt(&self, data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>, hex_iv: impl AsRef<[u8]>, cbc_mode: bool) -> TardisResult { - let key_size = match hex_key.as_ref().len() { - 16 => crypto::aes::KeySize::KeySize128, - 24 => crypto::aes::KeySize::KeySize192, - 32 => crypto::aes::KeySize::KeySize256, - _ => { - return Err(TardisError::format_error( - "[Tardis.Crypto] AES error, invalid key size", - "406-tardis-crypto-aes-key-invalid", - )) - } - }; - - let mut encryptor = if cbc_mode { - crypto::aes::cbc_encryptor(key_size, hex_key.as_ref(), hex_iv.as_ref(), crypto::blockmodes::PkcsPadding) - } else { - crypto::aes::ecb_encryptor(key_size, hex_key.as_ref(), crypto::blockmodes::PkcsPadding) - }; - - let mut final_result = Vec::::new(); - let mut read_buffer = crypto::buffer::RefReadBuffer::new(data.as_ref()); - let mut buffer = [0; 4096]; - let mut write_buffer = crypto::buffer::RefWriteBuffer::new(&mut buffer); - - loop { - let result = encryptor.encrypt(&mut read_buffer, &mut write_buffer, true)?; - final_result.extend(write_buffer.take_read_buffer().take_remaining().iter().copied()); - match result { - crypto::buffer::BufferResult::BufferUnderflow => break, - crypto::buffer::BufferResult::BufferOverflow => {} - } - } - Ok(hex::encode(final_result)) - } - - pub fn decrypt_ecb(&self, encrypted_data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>) -> TardisResult { - self.decrypt(encrypted_data, hex_key, "", false) - } - - pub fn decrypt_cbc(&self, encrypted_data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>, hex_iv: impl AsRef<[u8]>) -> TardisResult { - self.decrypt(encrypted_data, hex_key, hex_iv, true) - } - - fn decrypt(&self, encrypted_data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>, hex_iv: impl AsRef<[u8]>, cbc_mode: bool) -> TardisResult { - let key_size = match hex_key.as_ref().len() { - 16 => crypto::aes::KeySize::KeySize128, - 24 => crypto::aes::KeySize::KeySize192, - 32 => crypto::aes::KeySize::KeySize256, - _ => { - return Err(TardisError::format_error( - "[Tardis.Crypto] AES error, invalid key size", - "406-tardis-crypto-aes-key-invalid", - )) - } - }; - - let encrypted_data = hex::decode(encrypted_data)?; - - let mut decryptor = if cbc_mode { - crypto::aes::cbc_decryptor(key_size, hex_key.as_ref(), hex_iv.as_ref(), crypto::blockmodes::PkcsPadding) - } else { - crypto::aes::ecb_decryptor(key_size, hex_key.as_ref(), crypto::blockmodes::PkcsPadding) - }; - - let mut final_result = Vec::::new(); - let mut read_buffer = crypto::buffer::RefReadBuffer::new(encrypted_data.as_slice()); - let mut buffer = [0; 4096]; - let mut write_buffer = crypto::buffer::RefWriteBuffer::new(&mut buffer); - - loop { - let result = decryptor.decrypt(&mut read_buffer, &mut write_buffer, true)?; - final_result.extend(write_buffer.take_read_buffer().take_remaining().iter().copied()); - match result { - crypto::buffer::BufferResult::BufferUnderflow => break, - crypto::buffer::BufferResult::BufferOverflow => {} - } - } - - Ok(String::from_utf8(final_result)?) - } -} - -impl From for TardisError { - fn from(error: crypto::symmetriccipher::SymmetricCipherError) -> Self { - TardisError::format_error(&format!("[Tardis.Crypto] AES crypto error, {error:?}"), "406-tardis-crypto-aes-error") - } -} diff --git a/tardis/src/crypto/crypto_digest.rs b/tardis/src/crypto/crypto_digest.rs index 2b797d2f..079a4173 100644 --- a/tardis/src/crypto/crypto_digest.rs +++ b/tardis/src/crypto/crypto_digest.rs @@ -1,10 +1,48 @@ -use std::iter::repeat; +// use crypto::mac::Mac; +use crate::{ + basic::{error::TardisError, result::TardisResult}, + utils::mapper::Mapper, +}; +use algorithm::*; +use digest::KeyInit; -use crypto::mac::Mac; - -use crate::basic::result::TardisResult; +use output::*; pub struct TardisCryptoDigest; +pub mod algorithm { + pub use digest::Digest; + pub use hmac::{Hmac, Mac}; + pub use md5::Md5; + pub use sha1::Sha1; + pub use sha2::{OidSha224, OidSha256, OidSha384, OidSha512, Sha224, Sha256, Sha384, Sha512}; + pub use sm3::Sm3; + pub type HmacSha1 = Hmac; + pub type HmacSha256 = Hmac; + pub type HmacSha512 = Hmac; +} + +pub mod output { + use std::marker::PhantomData; + + pub use digest::Output; + + use crate::utils::mapper::Mapper; + pub struct HexCodeMapper(PhantomData); + impl Mapper> for HexCodeMapper { + type Output = String; + fn map(raw_output: digest::Output) -> Self::Output { + hex::encode(raw_output) + } + } + + pub struct BytesMapper(PhantomData); + impl Mapper> for BytesMapper { + type Output = Vec; + fn map(raw_output: digest::Output) -> Self::Output { + raw_output.to_vec() + } + } +} /// Digest handle / 摘要处理 /// /// # Examples @@ -23,55 +61,81 @@ pub struct TardisCryptoDigest; /// ``` impl TardisCryptoDigest { pub fn sha1(&self, data: impl AsRef<[u8]>) -> TardisResult { - self.digest(data, crypto::sha1::Sha1::new()) + self.digest::(data) } pub fn sha256(&self, data: impl AsRef<[u8]>) -> TardisResult { - self.digest(data, crypto::sha2::Sha256::new()) + self.digest::(data) } pub fn sha512(&self, data: impl AsRef<[u8]>) -> TardisResult { - self.digest(data, crypto::sha2::Sha512::new()) + self.digest::(data) } pub fn md5(&self, data: impl AsRef<[u8]>) -> TardisResult { - self.digest(data, crypto::md5::Md5::new()) + self.digest::(data) + } + + pub fn sm3(&self, data: impl AsRef<[u8]>) -> TardisResult { + self.digest::(data) } pub fn hmac_sha1(&self, data: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult { - self.digest_hmac(data, key, crypto::sha1::Sha1::new()) + self.digest_hmac::(data, key) } pub fn hmac_sha256(&self, data: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult { - self.digest_hmac(data, key, crypto::sha2::Sha256::new()) + self.digest_hmac::(data, key) } pub fn hmac_sha512(&self, data: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult { - self.digest_hmac(data, key, crypto::sha2::Sha512::new()) + self.digest_hmac::(data, key) } - #[cfg(feature = "crypto-with-sm")] - pub fn sm3(&self, data: impl AsRef<[u8]>) -> TardisResult { - use libsm::sm3::hash::Sm3Hash; + /// Digest the data, and map the output into hexcode by default. + pub fn digest(&self, data: impl AsRef<[u8]>) -> TardisResult { + self.digest_hex::(data) + } + + /// Digest the data, and map the output into hexcode. + pub fn digest_hex(&self, data: impl AsRef<[u8]>) -> TardisResult { + self.digest_as::>(data) + } + + /// Digest the data, and map the output into `Vec`. + pub fn digest_bytes(&self, data: impl AsRef<[u8]>) -> TardisResult> { + self.digest_as::>(data) + } + + /// Digest the data, and map the output into a specific type which determined by `M`. + pub fn digest_as>>(&self, data: impl AsRef<[u8]>) -> TardisResult { + self.digest_iter_as::(Some(data)) + } - Ok(hex::encode(Sm3Hash::new(data.as_ref()).get_hash())) + /// Digest a sequence of data, and map the output into a specific type which determined by `M`. + pub fn digest_iter_as>, T: AsRef<[u8]>>(&self, data_iter: impl IntoIterator) -> TardisResult { + self.digest_iter_raw::(data_iter).map(M::map) } - pub fn digest(&self, data: impl AsRef<[u8]>, mut algorithm: A) -> TardisResult { - algorithm.input(data.as_ref()); - Ok(algorithm.result_str()) + /// Digest a sequence of data. + /// + /// Get the raw digest output from Digest trait, the type is determined by althogrim itself (most time it's a GenericArray). + pub fn digest_iter_raw>(&self, data_iter: impl IntoIterator) -> TardisResult> { + let mut hasher = A::new(); + for data in data_iter { + hasher.update(data); + } + let out = hasher.finalize(); + Ok(out) } - pub fn digest_raw(&self, data: &[u8], mut algorithm: A) -> TardisResult> { - algorithm.input(data); - let mut buf: Vec = repeat(0).take((algorithm.output_bits() + 7) / 8).collect(); - algorithm.result(&mut buf); - Ok(buf) + pub fn digest_hmac(&self, data: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult { + self.digest_hmac_raw::(data, key).map(hex::encode) } - pub fn digest_hmac(&self, data: impl AsRef<[u8]>, key: impl AsRef<[u8]>, algorithm: A) -> TardisResult { - let mut hmac = crypto::hmac::Hmac::new(algorithm, key.as_ref()); - hmac.input(data.as_ref()); - Ok(hex::encode(hmac.result().code())) + pub fn digest_hmac_raw(&self, data: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> TardisResult> { + let mut hmac = ::new_from_slice(key.as_ref()).map_err(|_| TardisError::internal_error("hmac key with invalid length", "406-tardis-crypto-hmac-key-invalid"))?; + hmac.update(data.as_ref()); + Ok(hmac.finalize().into_bytes().to_vec()) } } diff --git a/tardis/src/crypto/crypto_key.rs b/tardis/src/crypto/crypto_key.rs index fb9f2ba0..ca8b9703 100644 --- a/tardis/src/crypto/crypto_key.rs +++ b/tardis/src/crypto/crypto_key.rs @@ -1,46 +1,46 @@ use rand::RngCore; use crate::{basic::result::TardisResult, TardisFuns}; - +use paste::paste; pub struct TardisCryptoKey; +macro_rules! gen_rand_n_hex { + {$($N: literal),*} => { + $( + paste! { + #[inline] + pub fn [](&self) -> String { + self.rand_n_hex::<$N>() + } + } + )* + }; +} +macro_rules! gen_rand_n_bytes { + {$($N: literal),*} => { + $( + paste! { + #[inline] + pub fn [](&self) -> [u8; $N] { + self.rand_n_bytes::<$N>() + } + } + )* + }; +} impl TardisCryptoKey { - pub fn rand_8_hex(&self) -> TardisResult { - let mut key: [u8; 4] = [0; 4]; + pub fn rand_n_hex(&self) -> String { + let mut key = vec![0; N / 2]; rand::rngs::OsRng.fill_bytes(&mut key); - Ok(hex::encode(key)) + hex::encode(key) } - - pub fn rand_16_hex(&self) -> TardisResult { - let mut key: [u8; 8] = [0; 8]; + pub fn rand_n_bytes(&self) -> [u8; N] { + let mut key = [0; N]; rand::rngs::OsRng.fill_bytes(&mut key); - Ok(hex::encode(key)) + key } - - pub fn rand_32_hex(&self) -> TardisResult { - let mut key: [u8; 16] = [0; 16]; - rand::rngs::OsRng.fill_bytes(&mut key); - Ok(hex::encode(key)) - } - - pub fn rand_64_hex(&self) -> TardisResult { - let mut key: [u8; 32] = [0; 32]; - rand::rngs::OsRng.fill_bytes(&mut key); - Ok(hex::encode(key)) - } - - pub fn rand_128_hex(&self) -> TardisResult { - let mut key: [u8; 64] = [0; 64]; - rand::rngs::OsRng.fill_bytes(&mut key); - Ok(hex::encode(key)) - } - - pub fn rand_256_hex(&self) -> TardisResult { - let mut key: [u8; 128] = [0; 128]; - rand::rngs::OsRng.fill_bytes(&mut key); - Ok(hex::encode(key)) - } - + gen_rand_n_hex! {8, 16, 32, 64, 128, 256} + gen_rand_n_bytes! {8, 16, 32, 64, 128, 256} pub fn generate_token(&self) -> TardisResult { Ok(format!("tk{}", TardisFuns::field.nanoid())) } diff --git a/tardis/src/crypto/crypto_main.rs b/tardis/src/crypto/crypto_main.rs index 8acd4ad9..d8f1bcb8 100644 --- a/tardis/src/crypto/crypto_main.rs +++ b/tardis/src/crypto/crypto_main.rs @@ -1,5 +1,5 @@ use super::{ - crypto_aes::TardisCryptoAes, crypto_base64::TardisCryptoBase64, crypto_digest::TardisCryptoDigest, crypto_hex::TardisCryptoHex, crypto_key::TardisCryptoKey, + crypto_aead::TardisCryptoAead, crypto_base64::TardisCryptoBase64, crypto_digest::TardisCryptoDigest, crypto_hex::TardisCryptoHex, crypto_key::TardisCryptoKey, crypto_rsa::TardisCryptoRsa, }; @@ -15,7 +15,7 @@ pub struct TardisCrypto { pub key: TardisCryptoKey, pub hex: TardisCryptoHex, pub base64: TardisCryptoBase64, - pub aes: TardisCryptoAes, + pub aead: TardisCryptoAead, pub rsa: TardisCryptoRsa, pub digest: TardisCryptoDigest, #[cfg(feature = "crypto-with-sm")] diff --git a/tardis/src/crypto/crypto_sm2_4.rs b/tardis/src/crypto/crypto_sm2_4.rs index c2a05a14..2714f84b 100644 --- a/tardis/src/crypto/crypto_sm2_4.rs +++ b/tardis/src/crypto/crypto_sm2_4.rs @@ -164,21 +164,21 @@ impl TardisCryptoSm2PublicKey { /// ``` #[cfg(feature = "crypto-with-sm")] impl TardisCryptoSm4 { - pub fn encrypt_cbc(&self, data: &str, hex_key: &str, hex_iv: &str) -> TardisResult { - let cipher = Cipher::new(hex_key.as_bytes(), Mode::Cbc) + pub fn encrypt_cbc(&self, data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>, hex_iv: impl AsRef<[u8]>) -> TardisResult { + let cipher = Cipher::new(hex_key.as_ref(), Mode::Cbc) .map_err(|error| TardisError::format_error(&format!("[Tardis.Crypto] SM4 new cipher error:{error}"), "406-tardis-crypto-sm4-cipher-error"))?; let encrypted_data = cipher - .encrypt(data.as_bytes(), hex_iv.as_bytes()) + .encrypt(data.as_ref(), hex_iv.as_ref()) .map_err(|error| TardisError::format_error(&format!("[Tardis.Crypto] SM4 encrypt error:{error}"), "406-tardis-crypto-sm4-encrypt-error"))?; Ok(hex::encode(encrypted_data)) } - pub fn decrypt_cbc(&self, encrypted_data: &str, hex_key: &str, hex_iv: &str) -> TardisResult { - let cipher = Cipher::new(hex_key.as_bytes(), Mode::Cbc) + pub fn decrypt_cbc(&self, encrypted_data: impl AsRef<[u8]>, hex_key: impl AsRef<[u8]>, hex_iv: impl AsRef<[u8]>) -> TardisResult { + let cipher = Cipher::new(hex_key.as_ref(), Mode::Cbc) .map_err(|error| TardisError::format_error(&format!("[Tardis.Crypto] SM4 new cipher error:{error}"), "406-tardis-crypto-sm4-cipher-error"))?; - let encrypted_data = hex::decode(encrypted_data)?; + let encrypted_data = hex::decode(encrypted_data.as_ref())?; let data = cipher - .decrypt(encrypted_data.as_slice(), hex_iv.as_bytes()) + .decrypt(encrypted_data.as_slice(), hex_iv.as_ref()) .map_err(|error| TardisError::format_error(&format!("[Tardis.Crypto] SM4 decrypt error:{error}"), "406-tardis-crypto-sm4-decrypt-error"))?; Ok(String::from_utf8(data)?) } diff --git a/tardis/src/db/reldb_client.rs b/tardis/src/db/reldb_client.rs index 9188be6c..34e8cbb5 100644 --- a/tardis/src/db/reldb_client.rs +++ b/tardis/src/db/reldb_client.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; @@ -20,9 +19,11 @@ use url::Url; use crate::basic::dto::TardisContext; use crate::basic::error::TardisError; use crate::basic::result::TardisResult; -use crate::config::config_dto::{CompatibleType, FrameworkConfig}; +use crate::config::config_dto::component::db::CompatibleType; +use crate::config::config_dto::component::db::DBModuleConfig; use crate::db::domain::{tardis_db_config, tardis_db_del_record}; use crate::serde::{Deserialize, Serialize}; +use crate::utils::initializer::InitBy; use crate::TardisFuns; /// Relational database handle / 关系型数据库操作 @@ -132,47 +133,24 @@ pub struct TardisRelDBClient { compatible_type: CompatibleType, } -impl TardisRelDBClient { - /// Initialize configuration from the database configuration object / 从数据库配置对象中初始化配置 - pub async fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert( - "".to_string(), - TardisRelDBClient::init( - &conf.db.url, - conf.db.max_connections, - conf.db.min_connections, - conf.db.connect_timeout_sec, - conf.db.idle_timeout_sec, - conf.db.compatible_type.clone(), - ) - .await?, - ); - for (k, v) in &conf.db.modules { - clients.insert( - k.to_string(), - TardisRelDBClient::init( - &v.url, - v.max_connections, - v.min_connections, - v.connect_timeout_sec, - v.idle_timeout_sec, - v.compatible_type.clone(), - ) - .await?, - ); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisRelDBClient { + async fn init_by(config: &DBModuleConfig) -> TardisResult { + Self::init(config).await } +} +impl TardisRelDBClient { /// Initialize configuration / 初始化配置 pub async fn init( - str_url: &str, - max_connections: u32, - min_connections: u32, - connect_timeout_sec: Option, - idle_timeout_sec: Option, - compatible_type: CompatibleType, + DBModuleConfig { + url: str_url, + max_connections, + min_connections, + connect_timeout_sec, + idle_timeout_sec, + compatible_type, + }: &DBModuleConfig, ) -> TardisResult { let url = Url::parse(str_url).map_err(|_| TardisError::format_error(&format!("[Tardis.RelDBClient] Invalid url {str_url}"), "406-tardis-reldb-url-error"))?; info!( @@ -181,13 +159,13 @@ impl TardisRelDBClient { url.port().unwrap_or(0), max_connections ); - let mut opt = ConnectOptions::new(str_url.to_string()); - opt.max_connections(max_connections).min_connections(min_connections).sqlx_logging(true); + let mut opt = ConnectOptions::new(url.to_string()); + opt.max_connections(*max_connections).min_connections(*min_connections).sqlx_logging(true); if let Some(connect_timeout_sec) = connect_timeout_sec { - opt.connect_timeout(Duration::from_secs(connect_timeout_sec)); + opt.connect_timeout(Duration::from_secs(*connect_timeout_sec)); } if let Some(idle_timeout_sec) = idle_timeout_sec { - opt.idle_timeout(Duration::from_secs(idle_timeout_sec)); + opt.idle_timeout(Duration::from_secs(*idle_timeout_sec)); } let con = if let Some(timezone) = url.query_pairs().find(|x| x.0.to_lowercase() == "timezone").map(|x| x.1.to_string()) { match url.scheme().to_lowercase().as_str() { @@ -265,7 +243,7 @@ impl TardisRelDBClient { ); Ok(TardisRelDBClient { con: Arc::new(con), - compatible_type, + compatible_type: *compatible_type, }) } @@ -277,7 +255,7 @@ impl TardisRelDBClient { /// Get database compatible type / 获取数据库兼容类型 /// eg. porlardb is compatible with Oracle pub fn compatible_type(&self) -> CompatibleType { - self.compatible_type.clone() + self.compatible_type } /// Get database connection @@ -291,13 +269,13 @@ impl TardisRelDBClient { pub async fn init_basic_tables(&self) -> TardisResult<()> { trace!("[Tardis.RelDBClient] Initializing basic tables"); let tx = self.con.begin().await?; - let create_all = tardis_db_config::ActiveModel::init(self.con.get_database_backend(), Some("update_time"), self.compatible_type.clone()); + let create_all = tardis_db_config::ActiveModel::init(self.con.get_database_backend(), Some("update_time"), self.compatible_type); TardisRelDBClient::create_table_inner(&create_all.0, &tx).await?; TardisRelDBClient::create_index_inner(&create_all.1, &tx).await?; for function_sql in create_all.2 { TardisRelDBClient::execute_one_inner(&function_sql, Vec::new(), &tx).await?; } - let create_all = tardis_db_del_record::ActiveModel::init(self.con.get_database_backend(), None, self.compatible_type.clone()); + let create_all = tardis_db_del_record::ActiveModel::init(self.con.get_database_backend(), None, self.compatible_type); TardisRelDBClient::create_table_inner(&create_all.0, &tx).await?; TardisRelDBClient::create_index_inner(&create_all.1, &tx).await?; tx.commit().await?; @@ -1486,7 +1464,7 @@ pub trait TardisActiveModel: ActiveModelBehavior { fn create_function_postgresql_auto_update_time(table_name: &str, update_time_field: &str, compatible_type: CompatibleType) -> Vec { match compatible_type { - crate::config::config_dto::CompatibleType::None => { + CompatibleType::None => { vec![ format!( r###"CREATE OR REPLACE FUNCTION TARDIS_AUTO_UPDATE_TIME_{}() @@ -1511,7 +1489,7 @@ pub trait TardisActiveModel: ActiveModelBehavior { ), ] } - crate::config::config_dto::CompatibleType::Oracle => vec![format!( + CompatibleType::Oracle => vec![format!( r###"CREATE OR REPLACE TRIGGER TARDIS_AUTO_UPDATE_TIME_ON BEFORE UPDATE ON diff --git a/tardis/src/lib.rs b/tardis/src/lib.rs index 73c2036f..69403de9 100644 --- a/tardis/src/lib.rs +++ b/tardis/src/lib.rs @@ -280,6 +280,7 @@ use crate::web::web_server::TardisWebServer; pub struct TardisFuns { custom_config: TardisComponentMap, framework_config: TardisComponent, + pub(crate) tracing: TardisComponent, #[cfg(feature = "reldb-core")] reldb: TardisComponentMap, #[cfg(feature = "web-server")] @@ -301,6 +302,7 @@ pub struct TardisFuns { static TARDIS_INST: TardisFuns = TardisFuns { custom_config: TardisComponentMap::new(), framework_config: TardisComponent::new(), + tracing: TardisComponent::new(), #[cfg(feature = "reldb-core")] reldb: TardisComponentMap::new(), #[cfg(feature = "web-server")] @@ -337,8 +339,7 @@ impl TardisFuns { /// TardisFuns::init("proj/config").await; /// ``` pub async fn init(relative_path: Option<&str>) -> TardisResult<()> { - #[cfg(not(feature = "tracing"))] - TardisTracing::init_log()?; + TardisTracing::init_default()?; let config = TardisConfig::init(relative_path).await?; TardisFuns::init_conf(config).await } @@ -349,7 +350,7 @@ impl TardisFuns { /// /// [init](Self::init) 函数时会自动调用此函数 pub fn init_log() -> TardisResult<()> { - TardisTracing::init_log()?; + TardisTracing::init_default()?; Ok(()) } @@ -405,23 +406,23 @@ impl TardisFuns { /// .await; /// ``` pub async fn init_conf(conf: TardisConfig) -> TardisResult<()> { - #[cfg(feature = "tracing")] - TardisTracing::init_tracing(&conf.fw)?; let custom_config = conf.cs.iter().map(|(k, v)| (k.clone(), CachedJsonValue::new(v.clone()))).collect::>(); TARDIS_INST.custom_config.replace_inner(custom_config); TARDIS_INST.framework_config.set(conf.fw); #[allow(unused_variables)] let fw_conf = TardisFuns::fw_config(); + if let Some(log_config) = &fw_conf.log { + TARDIS_INST.tracing.get().update_config(log_config)?; + } #[cfg(feature = "reldb-core")] { - if TardisFuns::fw_config().db.enabled { - let reldb_clients = TardisRelDBClient::init_by_conf(&fw_conf).await?; - TARDIS_INST.reldb.replace_inner(reldb_clients); + if let Some(db_config) = &fw_conf.db { + TARDIS_INST.reldb.init_by(db_config).await?; } } #[cfg(feature = "web-server")] { - if TardisFuns::fw_config().web_server.enabled { + if let Some(_web_server_config) = &fw_conf.web_server { let web_server = TardisWebServer::init_by_conf(&fw_conf)?; // take out previous webserver first, because TARDIS_INST is not send and can't live cross an `await` point let inherit = TARDIS_INST.web_server.get(); @@ -438,42 +439,38 @@ impl TardisFuns { } #[cfg(feature = "web-client")] { - let web_clients = TardisWebClient::init_by_conf(&fw_conf)?; - TARDIS_INST.web_client.replace_inner(web_clients); + if let Some(web_client_config) = &fw_conf.web_client { + TARDIS_INST.web_client.init_by(web_client_config).await?; + } } #[cfg(feature = "cache")] { - if TardisFuns::fw_config().cache.enabled { - let cache_clients = TardisCacheClient::init_by_conf(&fw_conf).await?; - TARDIS_INST.cache.replace_inner(cache_clients); + if let Some(cache_config) = &fw_conf.cache { + TARDIS_INST.cache.init_by(cache_config).await?; } } #[cfg(feature = "mq")] { - if TardisFuns::fw_config().mq.enabled { - let mq_clients = TardisMQClient::init_by_conf(&fw_conf).await?; - TARDIS_INST.mq.replace_inner(mq_clients); + if let Some(mq_config) = &fw_conf.mq { + TARDIS_INST.mq.init_by(mq_config).await?; } } #[cfg(feature = "web-client")] { - if TardisFuns::fw_config().search.enabled && !TardisFuns::fw_config().search.url.is_empty() { - let search_clients = TardisSearchClient::init_by_conf(&fw_conf)?; - TARDIS_INST.search.replace_inner(search_clients); + if let Some(search_config) = &fw_conf.search { + TARDIS_INST.search.init_by(search_config).await?; } } #[cfg(feature = "mail")] { - if TardisFuns::fw_config().mail.enabled { - let mail_clients = TardisMailClient::init_by_conf(&fw_conf)?; - TARDIS_INST.mail.replace_inner(mail_clients); + if let Some(mail_config) = &fw_conf.mail { + TARDIS_INST.mail.init_by(mail_config).await?; } } #[cfg(feature = "os")] { - if TardisFuns::fw_config().os.enabled { - let os_clients = TardisOSClient::init_by_conf(&fw_conf)?; - TARDIS_INST.os.replace_inner(os_clients); + if let Some(os_config) = &fw_conf.os { + TARDIS_INST.os.init_by(os_config).await?; } } Ok(()) @@ -610,7 +607,7 @@ impl TardisFuns { key: crypto::crypto_key::TardisCryptoKey {}, hex: crypto::crypto_hex::TardisCryptoHex {}, base64: crypto::crypto_base64::TardisCryptoBase64 {}, - aes: crypto::crypto_aes::TardisCryptoAes {}, + aead: crypto::crypto_aead::TardisCryptoAead {}, rsa: crypto::crypto_rsa::TardisCryptoRsa {}, #[cfg(feature = "crypto-with-sm")] sm4: crypto::crypto_sm2_4::TardisCryptoSm4 {}, @@ -619,6 +616,10 @@ impl TardisFuns { digest: crypto::crypto_digest::TardisCryptoDigest {}, }; + pub fn tracing() -> Arc { + TARDIS_INST.tracing.get() + } + /// Use the relational database feature / 使用关系型数据库功能 /// /// This feature needs to be enabled #[cfg(feature = "reldb/reldb-postgres/reldb-mysql/reldb-sqlite")] . @@ -1040,23 +1041,24 @@ impl TardisFuns { #[allow(unused_variables)] let fw_config = TardisFuns::fw_config(); - #[cfg(feature = "tracing")] - { - if fw_config.log.is_some() && fw_config.log != old_framework_config.log { - TardisTracing::init_tracing(&fw_config)?; + + if fw_config.log != old_framework_config.log { + if let Some(log_config) = &fw_config.log { + TARDIS_INST.tracing.get().update_config(log_config)?; } } #[cfg(feature = "reldb-core")] { - if fw_config.db.enabled && fw_config.db != old_framework_config.db { - let reldb_clients = TardisRelDBClient::init_by_conf(&fw_config).await?; - TARDIS_INST.reldb.replace_inner(reldb_clients); + if fw_config.db != old_framework_config.db { + if let Some(db_config) = &fw_config.db { + TARDIS_INST.reldb.init_by(db_config).await?; + } } } #[cfg(feature = "web-server")] { - if fw_config.web_server.enabled && old_framework_config.web_server != fw_config.web_server { + if fw_config.web_server.is_some() && old_framework_config.web_server != fw_config.web_server { let web_server = TardisWebServer::init_by_conf(&fw_config)?; let old_server = TARDIS_INST.web_server.get(); // if there's some inherit webserver @@ -1073,40 +1075,39 @@ impl TardisFuns { } #[cfg(feature = "web-client")] { - let web_clients = TardisWebClient::init_by_conf(&fw_config)?; - TARDIS_INST.web_client.replace_inner(web_clients); + if let Some(web_client_config) = &fw_config.web_client { + TARDIS_INST.web_client.init_by(web_client_config).await?; + } } #[cfg(feature = "cache")] { - if fw_config.cache.enabled { - let cache_clients = TardisCacheClient::init_by_conf(&fw_config).await?; - TARDIS_INST.cache.replace_inner(cache_clients); + if let Some(cache_config) = &fw_config.cache { + TARDIS_INST.cache.init_by(cache_config).await?; } } #[cfg(feature = "mq")] { - if fw_config.mq.enabled && fw_config.mq != old_framework_config.mq { - let mq_clients = TardisMQClient::init_by_conf(&fw_config).await?; - let mut old_mq_clients = TARDIS_INST.mq.replace_inner(mq_clients); - for (code, client) in old_mq_clients.drain() { - if let Err(e) = client.close().await { - tracing::error!("[Tardis] Encounter an error while shutting down MQClient [{code}]: {}", e); + if fw_config.mq != old_framework_config.mq { + if let Some(mq_config) = &fw_config.mq { + let mut old_mq_clients = TARDIS_INST.mq.init_by(mq_config).await?; + for (code, client) in old_mq_clients.drain() { + if let Err(e) = client.close().await { + tracing::error!("[Tardis] Encounter an error while shutting down MQClient [{code}]: {}", e); + } } } } } #[cfg(feature = "mail")] { - if fw_config.mail.enabled { - let mail_clients = TardisMailClient::init_by_conf(&fw_config)?; - TARDIS_INST.mail.replace_inner(mail_clients); + if let Some(mail_config) = &fw_config.mail { + TARDIS_INST.mail.init_by(mail_config).await?; } } #[cfg(feature = "os")] { - if fw_config.os.enabled { - let os_clients = TardisOSClient::init_by_conf(&fw_config)?; - TARDIS_INST.os.replace_inner(os_clients); + if let Some(os_config) = &fw_config.os { + TARDIS_INST.os.init_by(os_config).await?; } } Ok(()) @@ -1264,4 +1265,5 @@ pub mod search; pub mod test; pub mod web; +pub mod consts; pub mod utils; diff --git a/tardis/src/mail/mail_client.rs b/tardis/src/mail/mail_client.rs index cc1c35e2..06cbdb91 100644 --- a/tardis/src/mail/mail_client.rs +++ b/tardis/src/mail/mail_client.rs @@ -1,42 +1,37 @@ -use std::collections::HashMap; - use lettre::message::{header, MultiPart, SinglePart}; use lettre::transport::smtp::client::{Tls, TlsParametersBuilder, TlsVersion}; use lettre::{address, error, transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; use tracing::{error, info, trace, warn}; +use typed_builder::TypedBuilder; use crate::basic::error::TardisError; -use crate::{FrameworkConfig, TardisFuns, TardisResult}; +use crate::config::config_dto::component::mail::MailModuleConfig; +use crate::utils::initializer::InitBy; +use crate::{TardisFuns, TardisResult}; pub struct TardisMailClient { client: AsyncSmtpTransport, default_from: String, } -impl TardisMailClient { - pub fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert( - "".to_string(), - TardisMailClient::init( - &conf.mail.smtp_host, - conf.mail.smtp_port, - &conf.mail.smtp_username, - &conf.mail.smtp_password, - &conf.mail.default_from, - conf.mail.starttls, - )?, - ); - for (k, v) in &conf.mail.modules { - clients.insert( - k.to_string(), - TardisMailClient::init(&v.smtp_host, v.smtp_port, &v.smtp_username, &v.smtp_password, &v.default_from, v.starttls)?, - ); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisMailClient { + async fn init_by(config: &MailModuleConfig) -> TardisResult { + Self::init(config) } +} - pub fn init(smtp_host: &str, smtp_port: u16, smtp_username: &str, smtp_password: &str, default_from: &str, starttls: bool) -> TardisResult { +impl TardisMailClient { + pub fn init( + MailModuleConfig { + smtp_host, + smtp_port, + smtp_username, + smtp_password, + default_from, + starttls, + }: &MailModuleConfig, + ) -> TardisResult { info!("[Tardis.MailClient] Initializing"); let creds = Credentials::new(smtp_username.to_string(), smtp_password.to_string()); let tls = TlsParametersBuilder::new(smtp_host.to_string()) @@ -45,7 +40,7 @@ impl TardisMailClient { .set_min_tls_version(TlsVersion::Tlsv10) .build() .map_err(|error| TardisError::internal_error(&format!("[Tardis.MailClient] Tls build error: {error}"), "500-tardis-mail-init-error"))?; - let (client, tls) = if starttls { + let (client, tls) = if *starttls { info!("[Tardis.MailClient] Using STARTTLS"); (AsyncSmtpTransport::::starttls_relay(smtp_host), Tls::Opportunistic(tls)) } else { @@ -55,7 +50,7 @@ impl TardisMailClient { .map_err(|_| TardisError::internal_error(&format!("[Tardis.MailClient] Failed to create SMTP client: {smtp_host}"), "500-tardis-mail-init-error"))? .credentials(creds) .tls(tls) - .port(smtp_port) + .port(*smtp_port) .build(); info!("[Tardis.MailClient] Initialized"); TardisResult::Ok(TardisMailClient { @@ -74,21 +69,15 @@ impl TardisMailClient { for to in &req.to { email = email.to(to.parse()?) } - if let Some(reply_to) = &req.reply_to { - for t in reply_to { - email = email.reply_to(t.parse()?) - } - }; - if let Some(cc) = &req.cc { - for t in cc { - email = email.cc(t.parse()?) - } - }; - if let Some(bcc) = &req.bcc { - for t in bcc { - email = email.bcc(t.parse()?) - } - }; + for t in &req.reply_to { + email = email.reply_to(t.parse()?) + } + for t in &req.cc { + email = email.cc(t.parse()?) + } + for t in &req.bcc { + email = email.bcc(t.parse()?) + } email = email.subject(&req.subject); let email = if let Some(html_body) = &req.html_body { email.multipart( @@ -126,15 +115,23 @@ impl TardisMailClient { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypedBuilder)] pub struct TardisMailSendReq { + #[builder(default, setter(into))] pub subject: String, + #[builder(default, setter(into))] pub txt_body: String, + #[builder(default, setter(into, strip_option))] pub html_body: Option, + #[builder(default, setter(into))] pub to: Vec, - pub reply_to: Option>, - pub cc: Option>, - pub bcc: Option>, + #[builder(default, setter(into))] + pub reply_to: Vec, + #[builder(default, setter(into))] + pub cc: Vec, + #[builder(default, setter(into))] + pub bcc: Vec, + #[builder(default, setter(into, strip_option))] pub from: Option, } diff --git a/tardis/src/mq/mq_client.rs b/tardis/src/mq/mq_client.rs index 2d900f7e..4b2f9725 100644 --- a/tardis/src/mq/mq_client.rs +++ b/tardis/src/mq/mq_client.rs @@ -5,11 +5,11 @@ use amq_protocol_types::{AMQPValue, LongString, ShortString}; use futures_util::lock::Mutex; use futures_util::stream::StreamExt; use lapin::{options::*, types::FieldTable, BasicProperties, Channel, Connection, ConnectionProperties, Consumer, ExchangeKind}; -use url::Url; -use crate::basic::error::TardisError; use crate::basic::result::TardisResult; -use crate::config::config_dto::FrameworkConfig; +use crate::config::config_dto::component::mq::MQModuleConfig; + +use crate::{basic::error::TardisError, utils::initializer::InitBy}; use tracing::{error, info, trace}; pub struct TardisMQClient { @@ -17,20 +17,17 @@ pub struct TardisMQClient { channels: Mutex>, } -impl TardisMQClient { - pub async fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert("".to_string(), TardisMQClient::init(&conf.mq.url).await?); - for (k, v) in &conf.mq.modules { - clients.insert(k.to_string(), TardisMQClient::init(&v.url).await?); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisMQClient { + async fn init_by(config: &MQModuleConfig) -> TardisResult { + TardisMQClient::init(config).await } +} - pub async fn init(str_url: &str) -> TardisResult { - let url = Url::parse(str_url).map_err(|_| TardisError::format_error(&format!("[Tardis.MQClient] Invalid url {str_url}"), "406-tardis-mq-url-error"))?; +impl TardisMQClient { + pub async fn init(MQModuleConfig { url }: &MQModuleConfig) -> TardisResult { info!("[Tardis.MQClient] Initializing, host:{}, port:{}", url.host_str().unwrap_or(""), url.port().unwrap_or(0)); - let con = Connection::connect(str_url, ConnectionProperties::default().with_connection_name("tardis".into())).await?; + let con = Connection::connect(url.as_str(), ConnectionProperties::default().with_connection_name("tardis".into())).await?; info!("[Tardis.MQClient] Initialized, host:{}, port:{}", url.host_str().unwrap_or(""), url.port().unwrap_or(0)); Ok(TardisMQClient { con, diff --git a/tardis/src/os/os_client.rs b/tardis/src/os/os_client.rs index 8772c582..4811070e 100644 --- a/tardis/src/os/os_client.rs +++ b/tardis/src/os/os_client.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::ops::Deref; use async_trait::async_trait; @@ -7,7 +6,9 @@ use s3::{Bucket, BucketConfiguration, Region}; use tracing::{error, info, trace}; use crate::basic::error::{TardisError, ERROR_DEFAULT_CODE}; -use crate::{FrameworkConfig, TardisResult}; +use crate::config::config_dto::component::os::OSModuleConfig; +use crate::utils::initializer::InitBy; +use crate::TardisResult; pub struct TardisOSClient { client: Box, @@ -19,22 +20,26 @@ struct TardisOSS3Client { default_bucket: Option, } -impl TardisOSClient { - pub fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert( - "".to_string(), - TardisOSClient::init(&conf.os.kind, &conf.os.endpoint, &conf.os.ak, &conf.os.sk, &conf.os.region, &conf.os.default_bucket)?, - ); - for (k, v) in &conf.os.modules { - clients.insert(k.to_string(), TardisOSClient::init(&v.kind, &v.endpoint, &v.ak, &v.sk, &v.region, &v.default_bucket)?); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisOSClient { + async fn init_by(config: &OSModuleConfig) -> TardisResult { + Self::init(config) } +} - pub fn init(kind: &str, endpoint: &str, ak: &str, sk: &str, region: &str, default_bucket: &str) -> TardisResult { +impl TardisOSClient { + pub fn init( + OSModuleConfig { + kind, + endpoint, + ak, + sk, + region, + default_bucket, + }: &OSModuleConfig, + ) -> TardisResult { info!("[Tardis.OSClient] Initializing for {}", kind); - match kind { + match kind.as_str() { "s3" => { let region = Region::Custom { region: region.to_string(), diff --git a/tardis/src/search/search_client.rs b/tardis/src/search/search_client.rs index 1729b4b7..a34b0b5b 100644 --- a/tardis/src/search/search_client.rs +++ b/tardis/src/search/search_client.rs @@ -3,10 +3,14 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use serde_json::Value; use tracing::{debug, info, trace}; +use url::Url; use crate::basic::error::TardisError; use crate::basic::result::TardisResult; -use crate::config::config_dto::FrameworkConfig; +use crate::config::config_dto::component::search::SearchModuleConfig; +use crate::config::config_dto::component::web_client::WebClientModuleConfig; + +use crate::utils::initializer::InitBy; use crate::{TardisFuns, TardisWebClient}; /// Distributed search handle / 分布式搜索操作 @@ -29,7 +33,7 @@ use crate::{TardisFuns, TardisWebClient}; /// ``` pub struct TardisSearchClient { pub client: TardisWebClient, - pub server_url: String, + pub server_url: Url, } #[derive(Serialize, Deserialize, Debug)] @@ -68,27 +72,21 @@ pub struct TardisRawSearchShards { pub total: i32, } -impl TardisSearchClient { - /// Initialize configuration from the search configuration object / 从搜索配置对象中初始化配置 - pub fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert("".to_string(), TardisSearchClient::init(&conf.search.url, conf.search.timeout_sec)?); - for (k, v) in &conf.search.modules { - clients.insert(k.to_string(), TardisSearchClient::init(&v.url, v.timeout_sec)?); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisSearchClient { + async fn init_by(config: &SearchModuleConfig) -> TardisResult { + Self::init(config) } +} +impl TardisSearchClient { /// Initialize configuration / 初始化配置 - pub fn init(str_url: &str, timeout_sec: u64) -> TardisResult { + pub fn init(SearchModuleConfig { url, timeout_sec }: &SearchModuleConfig) -> TardisResult { info!("[Tardis.SearchClient] Initializing"); - let mut client = TardisWebClient::init(timeout_sec)?; + let mut client = TardisWebClient::init(&WebClientModuleConfig::builder().request_timeout_sec(*timeout_sec).build())?; client.set_default_header("Content-Type", "application/json"); info!("[Tardis.SearchClient] Initialized"); - TardisResult::Ok(TardisSearchClient { - client, - server_url: str_url.to_string(), - }) + TardisResult::Ok(TardisSearchClient { client, server_url: url.clone() }) } /// Create index / 创建索引 diff --git a/tardis/src/utils.rs b/tardis/src/utils.rs index d90e87d9..defdf7ad 100644 --- a/tardis/src/utils.rs +++ b/tardis/src/utils.rs @@ -1,4 +1,8 @@ pub mod cached_json_value; -pub use cached_json_value::*; +pub(crate) use cached_json_value::*; pub mod tardis_component; -pub use tardis_component::*; +pub(crate) use tardis_component::*; +// pub mod hot; +pub mod initializer; + +pub mod mapper; \ No newline at end of file diff --git a/tardis/src/utils/cached_json_value.rs b/tardis/src/utils/cached_json_value.rs index bc6c73b3..a707aa96 100644 --- a/tardis/src/utils/cached_json_value.rs +++ b/tardis/src/utils/cached_json_value.rs @@ -1,13 +1,14 @@ use std::{ - any::Any, - sync::{Arc, OnceLock}, + any::{Any, TypeId}, + collections::HashMap, + sync::{Arc, OnceLock, RwLock}, }; use serde::Deserialize; pub struct CachedJsonValue { json_value: serde_json::Value, - cache: OnceLock>, + cache: OnceLock>>>, } impl CachedJsonValue { @@ -18,12 +19,21 @@ impl CachedJsonValue { } } pub fn get Deserialize<'a> + Any + 'static + Send + Sync>(&self) -> serde_json::Result> { - if let Some(v) = self.cache.get() { - return Ok(v.clone().downcast::().expect("invalid type downcasted")); + let lock = self.cache.get_or_init(Default::default); + { + let rg = lock.read().expect("poisoned map"); + if let Some(v) = rg.get(&TypeId::of::()) { + return Ok(v.clone().downcast::().expect("invalid type downcasted")); + } } - let rust_value: Arc = Arc::new(serde_json::from_value(self.json_value.clone())?); - let cached = rust_value.clone(); - self.cache.get_or_init(|| cached); - Ok(rust_value) + { + let rust_value: Arc = Arc::new(serde_json::from_value(self.json_value.clone())?); + let mut wg = lock.write().expect("poisoned map"); + wg.insert(TypeId::of::(), rust_value.clone()); + Ok(rust_value) + } + } + pub fn raw(&self) -> &serde_json::Value { + &self.json_value } } diff --git a/tardis/src/utils/initializer.rs b/tardis/src/utils/initializer.rs new file mode 100644 index 00000000..383ee670 --- /dev/null +++ b/tardis/src/utils/initializer.rs @@ -0,0 +1,44 @@ +use std::{collections::HashMap, sync::Arc}; + +use crate::{basic::result::TardisResult, config::config_dto::component::TardisComponentConfig}; + +#[async_trait::async_trait] +pub trait InitBy: Sized { + async fn init_by(initializer: &Initializer) -> TardisResult; +} + +pub trait InitBySync: Sized +where + Initializer: Sync, +{ + fn init_by(initializer: &Initializer) -> TardisResult; +} + +#[async_trait::async_trait] +impl> InitBy for T +where + Initializer: Sync, +{ + async fn init_by(initializer: &Initializer) -> TardisResult { + >::init_by(initializer) + } +} + +#[async_trait::async_trait] +impl InitBy> for HashMap> +where + Component: InitBy + Sync + Send, + CommonConfig: Sync + Send + 'static, + ModuleConfig: Sync + Send + 'static, +{ + async fn init_by(config: &TardisComponentConfig) -> TardisResult { + let mut map = HashMap::new(); + let default_component = Component::init_by(&config.default).await?; + map.insert(String::default(), Arc::new(default_component)); + for (code, module_config) in &config.modules { + let module_component = Component::init_by(module_config).await?; + map.insert(code.clone(), Arc::new(module_component)); + } + Ok(map) + } +} diff --git a/tardis/src/utils/mapper.rs b/tardis/src/utils/mapper.rs new file mode 100644 index 00000000..025b4863 --- /dev/null +++ b/tardis/src/utils/mapper.rs @@ -0,0 +1,312 @@ +mod trim; +mod endecode; +use std::fmt::Debug; +use std::{ + fmt::{Display, Formatter}, + marker::PhantomData, + ops::Deref, +}; +pub use trim::*; +pub use endecode::*; +use serde::{Deserialize, Serialize}; + +pub trait Mapper { + type Output; + fn map(value: T) -> Self::Output; +} + +#[derive(Copy)] +#[repr(transparent)] +pub struct Mapped +where + M: Mapper, +{ + pub(crate) inner: M::Output, + _modifier_marker: PhantomData, +} + +impl Clone for Mapped +where + M: Mapper, + M::Output: Clone, +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + _modifier_marker: PhantomData, + } + } +} + +impl PartialEq for Mapped +where + M: Mapper, + M::Output: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.inner.eq(&other.inner) + } +} + +impl Eq for Mapped +where + M: Mapper, + M::Output: Eq, +{ +} + +impl std::hash::Hash for Mapped +where + M: Mapper, + M::Output: std::hash::Hash, +{ + fn hash(&self, state: &mut H) { + self.inner.hash(state) + } +} + +impl Default for Mapped +where + M: Mapper, + T: Default, +{ + fn default() -> Self { + Mapped::new(T::default()) + } +} + +impl Mapped +where + M: Mapper, +{ + pub fn new(value: T) -> Self { + Mapped { + inner: M::map(value), + _modifier_marker: PhantomData, + } + } + pub fn into_inner(self) -> M::Output { + self.inner + } +} + +impl Deref for Mapped +where + M: Mapper, + M::Output: Deref, +{ + type Target = ::Target; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl AsRef for Mapped +where + M: Mapper, +{ + fn as_ref(&self) -> &M::Output { + &self.inner + } +} + +impl Mapper for () { + type Output = T; + fn map(value: T) -> Self::Output { + value + } +} + +impl Serialize for Mapped +where + M: Mapper, + M::Output: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.inner.serialize(serializer) + } +} + +impl<'de, T, M> Deserialize<'de> for Mapped +where + M: Mapper, + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Mapped::new(T::deserialize(deserializer)?)) + } +} + +impl From for Mapped +where + M: Mapper, +{ + fn from(value: T) -> Self { + Mapped::new(value) + } +} + +impl Debug for Mapped +where + M: Mapper, + M::Output: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.inner, f) + } +} +impl Display for Mapped +where + M: Mapper, + M::Output: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.inner, f) + } +} + +#[cfg(feature = "web-server")] + +mod web_server_ext { + use poem_openapi::types::{ParseFromJSON, ToJSON}; + + use super::*; + use crate::web::poem_openapi::types::{ParseResult, Type}; + impl Type for Mapped + where + M: Mapper + Sync + Send, + M::Output: crate::web::poem_openapi::types::Type, + { + const IS_REQUIRED: bool = true; + + type RawValueType = ::RawValueType; + + type RawElementValueType = ::RawElementValueType; + + fn name() -> std::borrow::Cow<'static, str> { + M::Output::name() + } + + fn schema_ref() -> poem_openapi::registry::MetaSchemaRef { + M::Output::schema_ref() + } + + fn as_raw_value(&self) -> Option<&Self::RawValueType> { + self.inner.as_raw_value() + } + + fn raw_element_iter<'a>(&'a self) -> Box + 'a> { + self.inner.raw_element_iter() + } + } + + impl ToJSON for Mapped + where + M: Mapper + Sync + Send, + M::Output: ToJSON, + { + fn to_json(&self) -> Option { + self.inner.to_json() + } + } + + impl ParseFromJSON for Mapped + where + M: Mapper + Sync + Send, + M::Output: ParseFromJSON, + T: for<'de> Deserialize<'de>, + { + fn parse_from_json(value: Option) -> ParseResult { + let value = value.unwrap_or_default(); + match serde_json::from_value(value) { + Ok(value) => Ok(Mapped::new(value)), + Err(e) => Err(poem_openapi::types::ParseError::custom(e)), + } + } + } +} + +macro_rules! impl_mapper_for_tuple { + {@$call:ident #rev $(#$argfmt:ident)* $first:literal, $($index:literal,)*; $($rev: literal,)*} => { + impl_mapper_for_tuple!(@$call #rev $(#$argfmt)* $($index,)*; $first, $($rev,)* ); + }; + {@$call:ident #rev $(#$argfmt:ident)* ; $($rev: literal,)*} => { + impl_mapper_for_tuple!(@$call $(#$argfmt)* $($rev,)*); + }; + {@$call:ident #window2 $(#$argfmt:ident)* $first:literal, } => {}; + {@$call:ident #window2 $(#$argfmt:ident)* $first:literal, $second:literal, } => { + impl_mapper_for_tuple!( + @$call + $(#$argfmt)* + $first; + $first,; + $second, ; + $second + ); + }; + {@$call:ident #window2 $(#$argfmt:ident)* $first:literal, $second:literal, $($rest:literal,)*} => { + impl_mapper_for_tuple!( + @$call + #window2 $(#$argfmt)* + $first; + $first, $second, ; + $second, ; + $($rest,)* + ); + }; + {@$call:ident #window2 $(#$argfmt:ident)* $first:literal; $($a:literal,)*; $($b:literal,)*; $last:literal, } => { + impl_mapper_for_tuple!( + @$call + $(#$argfmt)* + $first; + $($a,)*; + $($b,)* $last,; + $last + ); + }; + {@$call:ident #window2 $(#$argfmt:ident)* $first:literal; $($a:literal,)*; $($b:literal,)*; $next:literal, $($rest:literal,)+ } => { + impl_mapper_for_tuple!( + @$call + #window2 $(#$argfmt)* + $first; + $($a,)* $next, ; + $($b,)* $next, ; + $($rest,)+ + ); + }; + {@gen $last:literal, $($index:literal,)*} => { + impl_mapper_for_tuple!(@gen $($index,)*); + impl_mapper_for_tuple!(@item #rev #window2 $last, $($index,)*; ); + }; + {@gen } => {}; + {@item $first:literal; $($from:literal,)* ; $($to:literal,)*; $last:literal } => { + paste::paste! { + #[allow(clippy::unused_unit, unused_variables)] + impl< + [], + $([],)* + $([]: Mapper<[], Output = []>),* + > Mapper<[]> for ($([],)*) { + type Output = []; + fn map(value: T0) -> [] { + $( + let value = []::map(value); + )* + value + } + } + } + }; + {$($tt:literal),* $(,)?} => { + impl_mapper_for_tuple!(@gen #rev $($tt,)*;); + }; +} + +impl_mapper_for_tuple! { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } diff --git a/tardis/src/utils/mapper/endecode.rs b/tardis/src/utils/mapper/endecode.rs new file mode 100644 index 00000000..24f11c57 --- /dev/null +++ b/tardis/src/utils/mapper/endecode.rs @@ -0,0 +1,34 @@ +use crate::{TardisFuns, basic::result::TardisResult}; + +use super::Mapper; +pub struct Base64Encode; +pub struct Base64Decode; + +impl Mapper for Base64Encode { + type Output = String; + fn map(value: String) -> String { + TardisFuns::crypto.base64.encode(value) + } +} + +impl<'a> Mapper<&'a str> for Base64Encode { + type Output = String; + fn map(value: &'a str) -> String { + TardisFuns::crypto.base64.encode(value) + } +} + + +impl Mapper for Base64Decode { + type Output = TardisResult; + fn map(value: String) -> TardisResult { + TardisFuns::crypto.base64.decode(value) + } +} + +impl<'a> Mapper<&'a str> for Base64Decode { + type Output = TardisResult; + fn map(value: &'a str) -> TardisResult { + TardisFuns::crypto.base64.decode(value) + } +} \ No newline at end of file diff --git a/tardis/src/utils/mapper/trim.rs b/tardis/src/utils/mapper/trim.rs new file mode 100644 index 00000000..5018aa1d --- /dev/null +++ b/tardis/src/utils/mapper/trim.rs @@ -0,0 +1,19 @@ +pub struct Trim; + +use super::Mapper; + +impl Mapper for Trim { + type Output = String; + fn map(value: String) -> String { + value.trim().to_string() + } +} + +impl<'a> Mapper<&'a str> for Trim { + type Output = &'a str; + fn map(value: &'a str) -> &'a str { + value.trim() + } +} + + diff --git a/tardis/src/utils/tardis_component.rs b/tardis/src/utils/tardis_component.rs index 8787472c..c31a2ece 100644 --- a/tardis/src/utils/tardis_component.rs +++ b/tardis/src/utils/tardis_component.rs @@ -4,8 +4,13 @@ use std::{ sync::{Arc, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, }; +use crate::basic::result::TardisResult; + +use super::initializer::InitBy; + type ModuleCode = String; +/// A once-rwlock-arc wrapper, used to store multi-thread shared and once-initialized data #[repr(transparent)] #[derive(Default)] pub struct TardisComponent(OnceLock>); @@ -63,6 +68,7 @@ impl TardisComponentInner { } } +/// A once-rwlock-hashmap-arc wrapper, used to store multi-thread shared and once-initialized map #[repr(transparent)] #[derive(Default)] pub struct TardisComponentMap(OnceLock>); @@ -79,9 +85,10 @@ impl Deref for TardisComponentMap { } } +type ArcMap = HashMap>; #[repr(transparent)] pub struct TardisComponentMapInner { - inner: RwLock>>, + pub(crate) inner: RwLock>, } impl Default for TardisComponentMapInner { fn default() -> Self { @@ -100,15 +107,24 @@ impl TardisComponentMapInner { wg.insert(code.into(), value.into()) } - pub fn replace_inner(&self, iter: impl IntoIterator) -> HashMap> { + pub fn replace_inner(&self, iter: impl IntoIterator) -> ArcMap { let mut wg = self.inner.write().expect(Self::LOCK_EXPECT); let new_inner = iter.into_iter().map(|(k, v)| (k, Arc::new(v))).collect::>(); std::mem::replace(&mut wg, new_inner) } - pub fn drain(&self) -> HashMap> { + pub fn drain(&self) -> ArcMap { self.replace_inner(std::iter::empty()) } + + pub async fn init_by(&self, initializer: &I) -> TardisResult> + where + ArcMap: InitBy, + { + let new_inner = HashMap::>::init_by(initializer).await?; + let wg = &mut *self.inner.write().expect(Self::LOCK_EXPECT); + Ok(std::mem::replace(wg, new_inner)) + } } impl TardisComponentMapInner { @@ -152,13 +168,13 @@ impl TardisComponentMapInner { /// # Panic /// Panic if the lock is poisoned - pub fn read(&self) -> RwLockReadGuard<'_, HashMap>> { + pub fn read(&self) -> RwLockReadGuard<'_, ArcMap> { self.inner.read().expect(Self::LOCK_EXPECT) } /// # Panic /// Panic if the lock is poisoned - pub fn write(&self) -> RwLockWriteGuard<'_, HashMap>> { + pub fn write(&self) -> RwLockWriteGuard<'_, ArcMap> { self.inner.write().expect(Self::LOCK_EXPECT) } } diff --git a/tardis/src/web/context_extractor.rs b/tardis/src/web/context_extractor.rs index eb8c2111..62ddff8f 100644 --- a/tardis/src/web/context_extractor.rs +++ b/tardis/src/web/context_extractor.rs @@ -25,7 +25,9 @@ async fn context_checker(req: &Request, _: ApiKey) -> Option { } async fn extract_context(req: &Request) -> TardisResult { - let context_header_name = &TardisFuns::fw_config().web_server.context_conf.context_header_name; + let fw_config = TardisFuns::fw_config(); + let web_server_config = fw_config.web_server.as_ref().expect("missing web server config"); + let context_header_name = &web_server_config.context_conf.context_header_name; let context = req .headers() .get(context_header_name) @@ -53,7 +55,7 @@ async fn extract_context(req: &Request) -> TardisResult { .split(TOKEN_FLAG) .nth(1) .ok_or_else(|| TardisError::bad_request("[Tardis.WebServer] Context header is invalid", "400-tardis-webserver-context-not-valid"))?; - let context = TardisFuns::cache().get(format!("{}{}", TardisFuns::fw_config().web_server.context_conf.token_cache_key, token).as_str()).await?; + let context = TardisFuns::cache().get(format!("{}{}", web_server_config.context_conf.token_cache_key, token).as_str()).await?; let context = context.ok_or_else(|| TardisError::bad_request("[Tardis.WebServer] Token is not in cache", "400-tardis-webserver-context-not-in-cache"))?; let context = TardisFuns::json .str_to_obj(&context) diff --git a/tardis/src/web/uniform_error_mw.rs b/tardis/src/web/uniform_error_mw.rs index 6ee09952..6812a4be 100644 --- a/tardis/src/web/uniform_error_mw.rs +++ b/tardis/src/web/uniform_error_mw.rs @@ -97,9 +97,10 @@ impl Endpoint for UniformErrorImpl { } fn process_err_msg(code: &str, msg: String) -> String { - match TardisFuns::fw_config_opt() { + let fw_config = TardisFuns::fw_config(); + match fw_config.web_server.as_ref() { Some(config) => { - if config.web_server.security_hide_err_msg { + if config.security_hide_err_msg { warn!("[Tardis.WebServer] Response error,code:{},msg:{}", code, msg); "[Tardis.WebServer] Security is enabled, detailed errors are hidden, please check the server logs".to_string() } else { diff --git a/tardis/src/web/web_client.rs b/tardis/src/web/web_client.rs index d8d8bb2e..eb4867f5 100644 --- a/tardis/src/web/web_client.rs +++ b/tardis/src/web/web_client.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; use std::time::Duration; -use reqwest::{Client, Method, Response}; +use reqwest::{Client, IntoUrl, Method, RequestBuilder, Response}; +use serde::Deserialize; use tracing::{error, info, trace}; use crate::basic::error::TardisError; use crate::basic::result::TardisResult; -use crate::config::config_dto::FrameworkConfig; -use crate::serde::de::DeserializeOwned; +use crate::config::config_dto::component::web_client::WebClientModuleConfig; use crate::serde::Serialize; +use crate::utils::initializer::InitBy; use crate::TardisFuns; pub struct TardisWebClient { @@ -16,19 +17,40 @@ pub struct TardisWebClient { client: Client, } -impl TardisWebClient { - pub fn init_by_conf(conf: &FrameworkConfig) -> TardisResult> { - let mut clients = HashMap::new(); - clients.insert("".to_string(), TardisWebClient::init(conf.web_client.connect_timeout_sec)?); - for (k, v) in &conf.web_client.modules { - clients.insert(k.to_string(), TardisWebClient::init(v.connect_timeout_sec)?); - } - Ok(clients) +#[async_trait::async_trait] +impl InitBy for TardisWebClient { + async fn init_by(config: &WebClientModuleConfig) -> TardisResult { + Self::init(config) + } +} +trait TardisRequestBody { + fn apply_on(self, builder: RequestBuilder) -> RequestBuilder; +} + +impl TardisRequestBody for () { + fn apply_on(self, builder: RequestBuilder) -> RequestBuilder { + builder } +} +struct PlainText(T); +struct Json<'a, T>(&'a T); + +impl> TardisRequestBody for PlainText { + fn apply_on(self, builder: RequestBuilder) -> RequestBuilder { + builder.body(self.0.into()) + } +} - pub fn init(connect_timeout_sec: u64) -> TardisResult { +impl TardisRequestBody for Json<'_, T> { + fn apply_on(self, builder: RequestBuilder) -> RequestBuilder { + builder.json(&self.0) + } +} + +impl TardisWebClient { + pub fn init(WebClientModuleConfig { connect_timeout_sec, .. }: &WebClientModuleConfig) -> TardisResult { info!("[Tardis.WebClient] Initializing"); - let client = reqwest::Client::builder().danger_accept_invalid_certs(true).connect_timeout(Duration::from_secs(connect_timeout_sec)).https_only(false).build()?; + let client = reqwest::Client::builder().danger_accept_invalid_certs(true).connect_timeout(Duration::from_secs(*connect_timeout_sec)).https_only(false).build()?; info!("[Tardis.WebClient] Initialized"); TardisResult::Ok(TardisWebClient { client, @@ -46,122 +68,145 @@ impl TardisWebClient { self.default_headers.retain(|(k, _)| k != key); } - pub async fn get_to_str(&self, url: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::GET, url, headers, None, None).await?; + pub async fn get_to_str(&self, url: impl IntoUrl, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::GET, url, headers, ()).await?; self.to_text(code, headers, response).await } - pub async fn get(&self, url: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::GET, url, headers, None, None).await?; + pub async fn get Deserialize<'de>>(&self, url: impl IntoUrl, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::GET, url, headers, ()).await?; self.to_json::(code, headers, response).await } - pub async fn head_to_void(&self, url: &str, headers: Option>) -> TardisResult> { - let (code, headers, _) = self.request::<()>(Method::HEAD, url, headers, None, None).await?; + pub async fn head_to_void(&self, url: impl IntoUrl, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, _) = self.request(Method::HEAD, url, headers, ()).await?; Ok(TardisHttpResponse { code, headers, body: None }) } - pub async fn head(&self, url: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::HEAD, url, headers, None, None).await?; + pub async fn head Deserialize<'de>>(&self, url: impl IntoUrl, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::HEAD, url, headers, ()).await?; self.to_json::(code, headers, response).await } - pub async fn delete_to_void(&self, url: &str, headers: Option>) -> TardisResult> { - let (code, headers, _) = self.request::<()>(Method::DELETE, url, headers, None, None).await?; + pub async fn delete_to_void(&self, url: impl IntoUrl, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, _) = self.request(Method::DELETE, url, headers, ()).await?; Ok(TardisHttpResponse { code, headers, body: None }) } - pub async fn delete(&self, url: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::DELETE, url, headers, None, None).await?; + pub async fn delete Deserialize<'de>>(&self, url: impl IntoUrl, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::DELETE, url, headers, ()).await?; self.to_json::(code, headers, response).await } - pub async fn post_str_to_str(&self, url: &str, body: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::POST, url, headers, None, Some(body)).await?; + pub async fn post_str_to_str(&self, url: impl IntoUrl, body: impl Into, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::POST, url, headers, PlainText(body)).await?; self.to_text(code, headers, response).await } - pub async fn post_obj_to_str(&self, url: &str, body: &B, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::(Method::POST, url, headers, Some(body), None).await?; + pub async fn post_obj_to_str(&self, url: impl IntoUrl, body: &B, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::POST, url, headers, Json(body)).await?; self.to_text(code, headers, response).await } - pub async fn post_to_obj(&self, url: &str, body: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::POST, url, headers, None, Some(body)).await?; + pub async fn post_to_obj Deserialize<'de>>( + &self, + url: impl IntoUrl, + body: impl Into, + headers: impl IntoIterator, + ) -> TardisResult> { + let (code, headers, response) = self.request(Method::POST, url, headers, PlainText(body)).await?; self.to_json::(code, headers, response).await } - pub async fn post(&self, url: &str, body: &B, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request(Method::POST, url, headers, Some(body), None).await?; + pub async fn post Deserialize<'de>>( + &self, + url: impl IntoUrl, + body: &B, + headers: impl IntoIterator, + ) -> TardisResult> { + let (code, headers, response) = self.request(Method::POST, url, headers, Json(body)).await?; self.to_json::(code, headers, response).await } - pub async fn put_str_to_str(&self, url: &str, body: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::PUT, url, headers, None, Some(body)).await?; + pub async fn put_str_to_str(&self, url: impl IntoUrl, body: impl Into, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::PUT, url, headers, PlainText(body)).await?; self.to_text(code, headers, response).await } - pub async fn put_obj_to_str(&self, url: &str, body: &B, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::(Method::PUT, url, headers, Some(body), None).await?; + pub async fn put_obj_to_str(&self, url: impl IntoUrl, body: &B, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::PUT, url, headers, Json(body)).await?; self.to_text(code, headers, response).await } - pub async fn put_to_obj(&self, url: &str, body: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::PUT, url, headers, None, Some(body)).await?; + pub async fn put_to_obj Deserialize<'de>>( + &self, + url: impl IntoUrl, + body: impl Into, + headers: impl IntoIterator, + ) -> TardisResult> { + let (code, headers, response) = self.request(Method::PUT, url, headers, PlainText(body)).await?; self.to_json::(code, headers, response).await } - pub async fn put(&self, url: &str, body: &B, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request(Method::PUT, url, headers, Some(body), None).await?; + pub async fn put Deserialize<'de>>( + &self, + url: impl IntoUrl, + body: &B, + headers: impl IntoIterator, + ) -> TardisResult> { + let (code, headers, response) = self.request(Method::PUT, url, headers, Json(body)).await?; self.to_json::(code, headers, response).await } - pub async fn patch_str_to_str(&self, url: &str, body: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::PATCH, url, headers, None, Some(body)).await?; + pub async fn patch_str_to_str(&self, url: impl IntoUrl, body: impl Into, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::PATCH, url, headers, PlainText(body)).await?; self.to_text(code, headers, response).await } - pub async fn patch_obj_to_str(&self, url: &str, body: &B, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::(Method::PATCH, url, headers, Some(body), None).await?; + pub async fn patch_obj_to_str(&self, url: impl IntoUrl, body: &B, headers: impl IntoIterator) -> TardisResult> { + let (code, headers, response) = self.request(Method::PATCH, url, headers, Json(body)).await?; self.to_text(code, headers, response).await } - pub async fn patch_to_obj(&self, url: &str, body: &str, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request::<()>(Method::PATCH, url, headers, None, Some(body)).await?; + pub async fn patch_to_obj Deserialize<'de>>( + &self, + url: impl IntoUrl, + body: impl Into, + headers: impl IntoIterator, + ) -> TardisResult> { + let (code, headers, response) = self.request(Method::PATCH, url, headers, PlainText(body)).await?; self.to_json::(code, headers, response).await } - pub async fn patch(&self, url: &str, body: &B, headers: Option>) -> TardisResult> { - let (code, headers, response) = self.request(Method::PATCH, url, headers, Some(body), None).await?; + pub async fn patch Deserialize<'de>>( + &self, + url: impl IntoUrl, + body: &B, + headers: impl IntoIterator, + ) -> TardisResult> { + let (code, headers, response) = self.request(Method::PATCH, url, headers, Json(body)).await?; self.to_json::(code, headers, response).await } - async fn request( + async fn request( &self, method: Method, - url: &str, - headers: Option>, - body: Option<&B>, - str_body: Option<&str>, + url: impl IntoUrl, + headers: impl IntoIterator, + body: impl TardisRequestBody, ) -> TardisResult<(u16, HashMap, Response)> { - let formatted_url = TardisFuns::uri.format(url)?; + let mut url = url.into_url()?; + TardisFuns::uri.sort_url_query(&mut url); let method_str = method.to_string(); - trace!("[Tardis.WebClient] Request {}:{}", method_str, &formatted_url); - let mut result = self.client.request(method, formatted_url.clone()); + trace!("[Tardis.WebClient] Request {}:{}", method_str, &url); + let mut result = self.client.request(method, url.clone()); for (key, value) in &self.default_headers { result = result.header(key, value); } - if let Some(headers) = headers { - for (key, value) in headers { - result = result.header(key, value); - } - } - if let Some(body) = body { - result = result.json(body); - } - if let Some(body) = str_body { - result = result.body(body.to_string()); + for (key, value) in headers { + result = result.header(key, value); } + result = body.apply_on(result); let response = result.send().await?; let code = response.status().as_u16(); let headers = response @@ -174,7 +219,7 @@ impl TardisWebClient { ) }) .collect(); - trace!("[Tardis.WebClient] Request {}:{}, Response {}", method_str, formatted_url, code); + trace!("[Tardis.WebClient] Request {}:{}, Response {}", method_str, url, code); Ok((code, headers, response)) } @@ -185,7 +230,7 @@ impl TardisWebClient { } } - async fn to_json(&self, code: u16, headers: HashMap, response: Response) -> TardisResult> { + async fn to_json Deserialize<'de>>(&self, code: u16, headers: HashMap, response: Response) -> TardisResult> { match response.json().await { Ok(body) => Ok(TardisHttpResponse { code, headers, body: Some(body) }), Err(error) => Err(TardisError::format_error(&format!("[Tardis.WebClient] {error:?}"), "406-tardis-webclient-json-error")), diff --git a/tardis/src/web/web_server.rs b/tardis/src/web/web_server.rs index 87af7332..d4525b06 100644 --- a/tardis/src/web/web_server.rs +++ b/tardis/src/web/web_server.rs @@ -1,5 +1,6 @@ use std::fmt::Debug; +use std::net::IpAddr; use std::sync::Arc; use futures_util::lock::Mutex; @@ -16,7 +17,12 @@ use tokio::time::Duration; use tracing::{debug, error, info, warn}; use crate::basic::result::TardisResult; -use crate::config::config_dto::{FrameworkConfig, WebServerConfig, WebServerModuleConfig}; +use crate::config::config_dto::component::web_server::WebServerCommonConfig; +use crate::config::config_dto::{ + component::{web_server::WebServerModuleConfig, WebServerConfig}, + FrameworkConfig, +}; +use crate::utils::initializer::InitBy; use crate::web::uniform_error_mw::UniformError; mod initializer; use initializer::*; @@ -93,7 +99,7 @@ impl Default for ServerState { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] #[repr(transparent)] pub struct ArcTardisWebServer(pub Arc); @@ -116,7 +122,7 @@ impl From> for ArcTardisWebServer { } } -#[derive(Default, Debug)] +#[derive(Debug)] pub struct TardisWebServer { app_name: String, version: String, @@ -125,32 +131,53 @@ pub struct TardisWebServer { /// /// Don't manually add initializer into here if you wan't `Initializer::init()` to be called, /// use `load_initializer` or `load_boxed_initializer` instead - pub(self) initializers: Mutex>>, + pub(self) initializers: Mutex>>, state: Mutex, } +impl Default for TardisWebServer { + fn default() -> Self { + TardisWebServer { + app_name: "".to_string(), + version: "".to_string(), + config: WebServerConfig::default(), + state: Mutex::new(ServerState::default()), + initializers: Mutex::new(Vec::new()), + } + } +} + +#[async_trait::async_trait] +impl InitBy for TardisWebServer { + async fn init_by(conf: &FrameworkConfig) -> TardisResult { + let route = poem::Route::new(); + TardisResult::Ok(TardisWebServer { + app_name: conf.app.name.clone(), + version: conf.app.version.clone(), + config: conf.web_server.clone().expect("missing web server config"), + state: Mutex::new(ServerState::Halted(route)), + initializers: Mutex::new(Vec::new()), + }) + } +} impl TardisWebServer { pub fn init_by_conf(conf: &FrameworkConfig) -> TardisResult { let route = poem::Route::new(); TardisResult::Ok(TardisWebServer { app_name: conf.app.name.clone(), version: conf.app.version.clone(), - config: conf.web_server.clone(), + config: conf.web_server.clone().expect("missing web server config"), state: Mutex::new(ServerState::Halted(route)), initializers: Mutex::new(Vec::new()), }) } - pub fn init_simple(host: &str, port: u16) -> TardisResult { + pub fn init_simple(host: IpAddr, port: u16) -> TardisResult { let route = poem::Route::new(); TardisResult::Ok(TardisWebServer { app_name: "".to_string(), version: "".to_string(), - config: WebServerConfig { - host: host.to_string(), - port, - ..Default::default() - }, + config: WebServerConfig::builder().common(WebServerCommonConfig::builder().host(host).port(port).build()).default(WebServerModuleConfig::builder().build()).build(), state: Mutex::new(ServerState::Halted(route)), initializers: Mutex::new(Vec::new()), }) @@ -160,11 +187,7 @@ impl TardisWebServer { WebServerModuleConfig { name: self.app_name.clone(), version: self.version.clone(), - doc_urls: self.config.doc_urls.clone(), - req_headers: self.config.req_headers.clone(), - ui_path: self.config.ui_path.clone(), - spec_path: self.config.spec_path.clone(), - uniform_error: self.config.uniform_error, + ..self.config.default.clone() } } /// add route @@ -233,6 +256,7 @@ impl TardisWebServer { self } + #[allow(unused_variables, unused_mut)] async fn do_add_module_with_data(&self, code: &str, module_config: &WebServerModuleConfig, module: WebServerModule) -> &Self where T: OpenApi + 'static, @@ -256,6 +280,7 @@ impl TardisWebServer { } let mut route = Route::new(); if let Some(ui_path) = &module_config.ui_path { + #[allow(unused_assignments)] let mut has_doc = false; #[cfg(feature = "openapi-redoc")] { diff --git a/tardis/src/web/web_server/initializer.rs b/tardis/src/web/web_server/initializer.rs index bbef16c9..d522aec7 100644 --- a/tardis/src/web/web_server/initializer.rs +++ b/tardis/src/web/web_server/initializer.rs @@ -3,13 +3,13 @@ use std::sync::Arc; use super::*; #[async_trait::async_trait] -pub(crate) trait Initializer { +pub(crate) trait WebServerInitializer { async fn init(&self, target: &TardisWebServer); } /// a tuple of (Code, WebServerModule) can be an initializer #[async_trait::async_trait] -impl Initializer for (String, WebServerModule) +impl WebServerInitializer for (String, WebServerModule) where T: Clone + OpenApi + 'static + Send + Sync, MW: Clone + Middleware> + 'static + Send + Sync, @@ -17,14 +17,18 @@ where { async fn init(&self, target: &TardisWebServer) { let (code, ref module) = self; - let module_config = target.config.modules.get(code).unwrap_or_else(|| panic!("[Tardis.WebServer] Module {code} not found")).clone(); - target.do_add_module_with_data(code, &module_config, module.clone()).await; + if let Some(module_config) = target.config.modules.get(code) { + target.do_add_module_with_data(code, module_config, module.clone()).await; + } else { + crate::log::debug!("[Tardis.WebServer] Module {code} not found, using a default config.", code = code); + target.do_add_module_with_data(code, &WebServerModuleConfig::default(), module.clone()).await; + } } } /// a tuple of (Code, WebServerModule, Config) can be an initializer, in this case we don't load config manually #[async_trait::async_trait] -impl Initializer for (String, WebServerModule, WebServerModuleConfig) +impl WebServerInitializer for (String, WebServerModule, WebServerModuleConfig) where T: Clone + OpenApi + 'static + Send + Sync, MW: Clone + Middleware> + 'static + Send + Sync, @@ -39,7 +43,7 @@ where /// `TardisWebServer` itself can serve as an `Initializer`, it applies all of it's initializer to another /// it will consume all initializer of stored previous webserver #[async_trait::async_trait] -impl Initializer for TardisWebServer { +impl WebServerInitializer for TardisWebServer { #[inline] async fn init(&self, target: &TardisWebServer) { let mut target_initializers = target.initializers.lock().await; @@ -53,7 +57,7 @@ impl Initializer for TardisWebServer { /// `TardisWebServer` itself can serve as an `Initializer`, it applies all of it's initializer to another /// it will consume all initializer of stored previous webserver #[async_trait::async_trait] -impl Initializer for Arc { +impl WebServerInitializer for Arc { #[inline] async fn init(&self, target: &TardisWebServer) { let mut target_initializers = target.initializers.lock().await; @@ -70,7 +74,7 @@ impl Initializer for Arc { /// a tuple of (Code, WebServerModule) can be an initializer #[cfg(feature = "web-server-grpc")] #[async_trait::async_trait] -impl Initializer for (String, WebServerGrpcModule) +impl WebServerInitializer for (String, WebServerGrpcModule) where D: Clone + Send + Sync + 'static, MW: Clone + Middleware> + Send + Sync + 'static, @@ -84,7 +88,7 @@ where #[cfg(feature = "web-server-grpc")] #[async_trait::async_trait] -impl Initializer for (String, WebServerGrpcModule, WebServerModuleConfig) +impl WebServerInitializer for (String, WebServerGrpcModule, WebServerModuleConfig) where D: Clone + Send + Sync + 'static, MW: Clone + Middleware> + Send + Sync + 'static, @@ -102,12 +106,12 @@ where impl TardisWebServer { /// Load an single initializer #[inline] - pub(crate) async fn load_initializer(&self, initializer: impl Initializer + Send + Sync + 'static) { + pub(crate) async fn load_initializer(&self, initializer: impl WebServerInitializer + Send + Sync + 'static) { self.load_boxed_initializer(Box::new(initializer)).await; } /// Load an single boxed initializer - pub(crate) async fn load_boxed_initializer(&self, initializer: Box) { + pub(crate) async fn load_boxed_initializer(&self, initializer: Box) { initializer.init(self).await; self.initializers.lock().await.push(initializer); } diff --git a/tardis/src/web/web_server/module.rs b/tardis/src/web/web_server/module.rs index 8ccf924f..5475b0ea 100644 --- a/tardis/src/web/web_server/module.rs +++ b/tardis/src/web/web_server/module.rs @@ -5,8 +5,11 @@ use poem_openapi::OpenApi; use std::sync::Arc; use tokio::sync::broadcast; +/// Options for web server module +/// - uniform_error: whether to use uniform error response #[derive(Clone)] pub struct WebServerModuleOption { + /// whether to use uniform error response pub uniform_error: bool, } @@ -23,11 +26,16 @@ impl Default for WebServerModuleOption { } } +/// A module of web server #[derive(Clone)] pub struct WebServerModule { + /// A poem `Openapi` data structure pub apis: T, + /// Shared data for this module pub data: Option, + /// Middleware for this module pub middleware: MW, + /// Custom options for this module pub options: WebServerModuleOption, } diff --git a/tardis/src/web/ws_client.rs b/tardis/src/web/ws_client.rs index 0821e615..9faa75af 100644 --- a/tardis/src/web/ws_client.rs +++ b/tardis/src/web/ws_client.rs @@ -6,7 +6,7 @@ use futures::stream::SplitSink; #[cfg(feature = "future")] use futures::{Future, SinkExt, StreamExt}; use native_tls::TlsConnector; -use serde::de::DeserializeOwned; +use serde::de::Deserialize; use serde::Serialize; use serde_json::Value; use tokio::{net::TcpStream, sync::Mutex}; @@ -142,12 +142,12 @@ impl TardisWSClient { } pub trait TardisWebSocketMessageExt { - fn str_to_obj(&self) -> TardisResult; + fn str_to_obj Deserialize<'de>>(&self) -> TardisResult; fn str_to_json(&self) -> TardisResult; } impl TardisWebSocketMessageExt for Message { - fn str_to_obj(&self) -> TardisResult { + fn str_to_obj Deserialize<'de>>(&self) -> TardisResult { if let Message::Text(msg) = self { TardisFuns::json.str_to_obj(msg).map_err(|_| { TardisError::format_error( diff --git a/tardis/tests/.gitignore b/tardis/tests/.gitignore new file mode 100644 index 00000000..ba475f68 --- /dev/null +++ b/tardis/tests/.gitignore @@ -0,0 +1 @@ +/log \ No newline at end of file diff --git a/tardis/tests/config/conf-default.toml b/tardis/tests/config/conf-default.toml index c4a082d1..9053966d 100644 --- a/tardis/tests/config/conf-default.toml +++ b/tardis/tests/config/conf-default.toml @@ -5,15 +5,11 @@ level_num = 2 [fw] [fw.app] default_lang = "zh-CN" -[fw.db] -enabled = false -port = 8089 -[fw.web_server] -enabled = false +[fw.web_client] -[fw.cache] -enabled = false - -[fw.mq] -enabled = false +[fw.log] +level = "debug" +# directives = ["tokio=trace", "runtime=trace"] +tracing_appender = { rotation = "minutely", dir = "./tests/log", filename = "app.log" } +tracing = { endpoint = "http://localhost:4317", protocol = "grpc", server_name = "tardis-test" } diff --git a/tardis/tests/config/conf-test.toml b/tardis/tests/config/conf-test.toml index afcb77b9..93f9515d 100644 --- a/tardis/tests/config/conf-test.toml +++ b/tardis/tests/config/conf-test.toml @@ -7,4 +7,6 @@ url = "postgres://postgres@test.proj" url = "postgres://postgres@test" [fw.app] -name = "APP1" \ No newline at end of file +name = "APP1" + + diff --git a/tardis/tests/config/remote-config/conf-remote-v2.toml b/tardis/tests/config/remote-config/conf-remote-v2.toml index 8bc74b5c..74690adc 100644 --- a/tardis/tests/config/remote-config/conf-remote-v2.toml +++ b/tardis/tests/config/remote-config/conf-remote-v2.toml @@ -4,7 +4,7 @@ id = "test-app" [fw.web_server] enabled = true -host="localhost" +host="127.0.0.1" port=8080 allowed_origin="*" spec_path="spec" diff --git a/tardis/tests/test_basic_field.rs b/tardis/tests/test_basic_field.rs index 84959b0d..d5e86178 100644 --- a/tardis/tests/test_basic_field.rs +++ b/tardis/tests/test_basic_field.rs @@ -32,8 +32,8 @@ async fn test_basic_field() -> TardisResult<()> { assert_eq!(TardisFuns::field.nanoid().len(), 21); assert_eq!(TardisFuns::field.nanoid_len(4).len(), 4); - let ts = TrimString(" a ".to_string()); - assert_eq!(ts.0, " a "); + let ts = TrimString::new(" a ".to_string()); + assert_eq!(&*ts, " a "); let s: &str = &ts; assert_eq!(s, "a"); let ts: TrimString = " a ".into(); diff --git a/tardis/tests/test_basic_json.rs b/tardis/tests/test_basic_json.rs index 3ca0c406..e20483f1 100644 --- a/tardis/tests/test_basic_json.rs +++ b/tardis/tests/test_basic_json.rs @@ -65,21 +65,21 @@ async fn test_basic_json() -> TardisResult<()> { assert_eq!(ctx, r#"{"own_paths":"ss/","ak":"","owner":"","roles":[],"groups":[]}"#); let req_dto = UserAddReq { - name: TrimString("星航大大".to_lowercase()), + name: TrimString::new("星航大大".to_lowercase()), pwd: "123456".to_string(), age: 10, roles: vec![ UserRoleReq { - code: TrimString("admin".to_lowercase()), + code: TrimString::new("admin".to_lowercase()), name: "管理员".to_string(), }, UserRoleReq { - code: TrimString("user".to_lowercase()), + code: TrimString::new("user".to_lowercase()), name: "用户".to_string(), }, ], org: Some(UserOrgReq { - code: TrimString("org1".to_lowercase()), + code: TrimString::new("org1".to_lowercase()), name: "组织1".to_string(), }), status: Some(true), diff --git a/tardis/tests/test_basic_logger.rs b/tardis/tests/test_basic_logger.rs index 438b050a..7595bb5f 100644 --- a/tardis/tests/test_basic_logger.rs +++ b/tardis/tests/test_basic_logger.rs @@ -1,5 +1,4 @@ use std::env; - use tardis::basic::result::TardisResult; use tardis::TardisFuns; use tracing::{error, info}; diff --git a/tardis/tests/test_basic_tracing.rs b/tardis/tests/test_basic_tracing.rs index 8b5b6701..ca6d33f9 100644 --- a/tardis/tests/test_basic_tracing.rs +++ b/tardis/tests/test_basic_tracing.rs @@ -1,27 +1,28 @@ -use std::env; - +use crate::app::req::test_req; use tardis::basic::result::TardisResult; use tardis::TardisFuns; use tracing::{error, info}; -use crate::app::req::test_req; - -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_basic_tracing() -> TardisResult<()> { // env::set_var("RUST_LOG", "OFF"); - env::set_var("RUST_LOG", "info"); + // env::set_var("RUST_LOG", "info"); TardisFuns::init(Some("tests/config")).await?; + let _g = tracing::trace_span!("main"); + let _g = _g.enter(); info!("main info..."); error!("main error"); - test_req(); + test_req().await; + drop(_g); Ok(()) } mod app { pub mod req { use tardis::log::{error, info}; - - pub fn test_req() { + use tracing::instrument; + #[instrument(level = "info", fields(module = env!("CARGO_PKG_NAME")))] + pub async fn test_req() { info!("app::req info"); error!("app::req error"); } diff --git a/tardis/tests/test_basic_uri.rs b/tardis/tests/test_basic_uri.rs index 654d549c..476f07a3 100644 --- a/tardis/tests/test_basic_uri.rs +++ b/tardis/tests/test_basic_uri.rs @@ -3,8 +3,11 @@ use tardis::TardisFuns; #[tokio::test] async fn test_basic_uri() -> TardisResult<()> { - assert_eq!(TardisFuns::uri.format("http://idealwrold.group").unwrap(), "http://idealwrold.group"); - assert_eq!(TardisFuns::uri.format("jdbc:h2:men:iam").unwrap(), "jdbc:h2:men:iam"); - assert_eq!(TardisFuns::uri.format("api://a1.t1/e1?q2=2&q1=1&q3=3").unwrap(), "api://a1.t1/e1?q1=1&q2=2&q3=3"); + assert_eq!(TardisFuns::uri.format("http://idealwrold.group").unwrap().to_string(), "http://idealwrold.group"); + assert_eq!(TardisFuns::uri.format("jdbc:h2:men:iam").unwrap().to_string(), "jdbc:h2:men:iam"); + assert_eq!( + TardisFuns::uri.format("api://a1.t1/e1?q2=2&q1=1&q3=3").unwrap().to_string(), + "api://a1.t1/e1?q1=1&q2=2&q3=3" + ); Ok(()) } diff --git a/tardis/tests/test_cache_client.rs b/tardis/tests/test_cache_client.rs index 7c97efb2..b5635195 100644 --- a/tardis/tests/test_cache_client.rs +++ b/tardis/tests/test_cache_client.rs @@ -1,6 +1,5 @@ // https://github.com/mitsuhiko/redis-rs -use std::collections::HashMap; use std::env; use tardis::cache::AsyncCommands; @@ -10,9 +9,10 @@ use tracing::info; use tardis::basic::result::TardisResult; use tardis::cache::cache_client::TardisCacheClient; -use tardis::config::config_dto::{CacheConfig, CacheModuleConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, TardisConfig, WebServerConfig}; +use tardis::config::config_dto::{CacheConfig, CacheModuleConfig, FrameworkConfig, TardisConfig}; use tardis::test::test_container::TardisTestContainer; use tardis::TardisFuns; +use url::Url; #[tokio::test] async fn test_cache_client() -> TardisResult<()> { @@ -20,7 +20,9 @@ async fn test_cache_client() -> TardisResult<()> { TardisFuns::init_log()?; // let url = "redis://:123456@127.0.0.1:6379/1".to_lowercase(); TardisTestContainer::redis(|url| async move { - let client = TardisCacheClient::init(&url).await?; + let url = url.parse::().expect("invalid url"); + let cache_module_config = CacheModuleConfig::builder().url(url).build(); + let client = TardisCacheClient::init(&cache_module_config).await?; // basic operations let mut opt_value = client.get("test_key").await?; @@ -141,43 +143,13 @@ async fn test_cache_client() -> TardisResult<()> { assert!(!mem.contains(&"m3".to_string())); // Default test - TardisFuns::init_conf(TardisConfig { - cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: false, - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: true, - url: url.clone(), - modules: HashMap::from([("m1".to_string(), CacheModuleConfig { url: url.clone() })]), - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, - }) + TardisFuns::init_conf( + TardisConfig::builder() + .fw(FrameworkConfig::builder() + .cache(CacheConfig::builder().default(cache_module_config.clone()).modules([("m1".to_string(), cache_module_config.clone())]).build()) + .build()) + .build(), + ) .await?; let map_result = TardisFuns::cache().hgetall("h").await?; diff --git a/tardis/tests/test_cluster.rs b/tardis/tests/test_cluster.rs index 51c650b1..9bded320 100644 --- a/tardis/tests/test_cluster.rs +++ b/tardis/tests/test_cluster.rs @@ -12,7 +12,8 @@ use serde_json::{json, Value}; use tardis::{ basic::result::TardisResult, cluster::cluster_processor::{self, TardisClusterMessageReq, TardisClusterSubscriber}, - config::config_dto::{CacheConfig, ClusterConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, TardisConfig, WebServerConfig}, + config::config_dto::{CacheModuleConfig, ClusterConfig, FrameworkConfig, TardisConfig, WebServerCommonConfig, WebServerConfig, WebServerModuleConfig}, + consts::IP_LOCALHOST, test::test_container::TardisTestContainer, TardisFuns, }; @@ -78,46 +79,17 @@ async fn start_node(cluster_url: String, node_id: &str) -> TardisResult<()> { cluster_processor::set_node_id(&format!("node_{node_id}")).await; TardisFuns::init_conf(TardisConfig { cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: true, - access_host: Some("127.0.0.1".to_string()), - port: format!("80{node_id}").parse().unwrap(), - ..Default::default() - }, - cache: CacheConfig { - enabled: true, - url: cluster_url, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - cluster: Some(ClusterConfig { + fw: FrameworkConfig::builder() + .web_server( + WebServerConfig::builder().common(WebServerCommonConfig::builder().access_host(IP_LOCALHOST).port(80).build()).default(WebServerModuleConfig::default()).build(), + ) + .cache(CacheModuleConfig::builder().url(cluster_url.parse().unwrap()).build()) + .cluster(ClusterConfig { watch_kind: "cache".to_string(), k8s_svc: None, cache_check_interval_sec: Some(1), - }), - ..Default::default() - }, + }) + .build(), }) .await .unwrap(); diff --git a/tardis/tests/test_config.rs b/tardis/tests/test_config.rs index e03a79fd..74165d04 100644 --- a/tardis/tests/test_config.rs +++ b/tardis/tests/test_config.rs @@ -7,21 +7,26 @@ use tardis::basic::result::TardisResult; use tardis::serde::{Deserialize, Serialize}; use tardis::TardisFuns; -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_config() -> TardisResult<()> { env::set_var("PROFILE", "test"); + env::set_var("RUST_LOG", "debug"); TardisFuns::init(Some("tests/config")).await?; env::set_var("Tardis_FW.ADV.SALT", "16a80c4aea768c98"); assert_eq!(TardisFuns::cs_config::("").project_name, "测试"); - assert!(!TardisFuns::fw_config().db.enabled); - assert_eq!(TardisFuns::fw_config().db.url, "postgres://postgres@test"); + let fw_config = TardisFuns::fw_config(); + let db_config = fw_config.db.as_ref().expect("missing db config"); + assert_eq!(db_config.default.url, "postgres://postgres@test"); assert_eq!(TardisFuns::cs_config::("").db_proj.url, "postgres://postgres@test.proj"); assert_eq!(TardisFuns::fw_config().app.name, "APP1"); env::set_var("PROFILE", "prod"); + tardis::log::info!("init the second time"); TardisFuns::init(Some("tests/config")).await?; env::set_var("Tardis_FW.ADV.SALT", "16a80c4aea768c98"); - assert_eq!(TardisFuns::fw_config().db.url, "postgres://postgres@prod"); + let fw_config = TardisFuns::fw_config(); + let db_config = fw_config.db.as_ref().expect("missing db config"); + assert_eq!(db_config.default.url, "postgres://postgres@prod"); assert_eq!(TardisFuns::cs_config::("").db_proj.url, "postgres://postgres@prod.proj"); assert_eq!(TardisFuns::fw_config().app.name, "Tardis Application"); assert_eq!(TardisFuns::cs_config::("m1").db_proj.url, "postgres://postgres@m1.proj"); @@ -29,7 +34,8 @@ async fn test_config() -> TardisResult<()> { // cli example: env Tardis_DB.URL=test Tardis_app.name=xx ./xxx env::set_var("Tardis_FW.DB.URL", "test"); TardisFuns::init(Some("tests/config")).await?; - assert_eq!(TardisFuns::fw_config().db.url, "test"); + let fw_config = TardisFuns::fw_config(); + assert_eq!(fw_config.db.as_ref().unwrap().default.url, "test"); assert_eq!(TardisFuns::fw_config().app.name, "Tardis Application"); Ok(()) diff --git a/tardis/tests/test_crypto.rs b/tardis/tests/test_crypto.rs index b2460905..6b2e61a5 100644 --- a/tardis/tests/test_crypto.rs +++ b/tardis/tests/test_crypto.rs @@ -3,12 +3,13 @@ use tardis::TardisFuns; #[tokio::test] async fn test_crypto() -> TardisResult<()> { + use tardis::crypto::crypto_aead::algorithm::*; let hex_str = TardisFuns::crypto.hex.encode("测试".as_bytes()); let str = TardisFuns::crypto.hex.decode(hex_str)?; assert_eq!(str, "测试".as_bytes()); let b64_str = TardisFuns::crypto.base64.encode("测试"); - let str = TardisFuns::crypto.base64.decode(&b64_str)?; + let str = TardisFuns::crypto.base64.decode(b64_str)?; assert_eq!(str, "测试"); assert_eq!(TardisFuns::crypto.digest.md5("测试")?, "db06c78d1e24cf708a14ce81c9b617ec"); @@ -49,18 +50,18 @@ Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, // let key = TardisFuns::crypto.key.rand_32_hex()?; // let iv = TardisFuns::crypto.key.rand_16_hex()?; - let key = TardisFuns::crypto.key.rand_16_hex()?; + let key = TardisFuns::crypto.key.rand_16_bytes(); - let encrypted_data = TardisFuns::crypto.aes.encrypt_ecb(large_text, &key)?; - let data = TardisFuns::crypto.aes.decrypt_ecb(&encrypted_data, &key)?; - assert_eq!(data, large_text); + let encrypted_data = TardisFuns::crypto.aead.encrypt_ecb::(large_text, &key)?; + let data = TardisFuns::crypto.aead.decrypt_ecb::(encrypted_data, &key)?; + assert_eq!(data, large_text.as_bytes()); - let key = TardisFuns::crypto.key.rand_16_hex()?; - let iv = TardisFuns::crypto.key.rand_16_hex()?; + let key = TardisFuns::crypto.key.rand_16_bytes(); + let iv = TardisFuns::crypto.key.rand_16_bytes(); - let encrypted_data = TardisFuns::crypto.aes.encrypt_cbc(large_text, &key, &iv)?; - let data = TardisFuns::crypto.aes.decrypt_cbc(&encrypted_data, &key, &iv)?; - assert_eq!(data, large_text); + let encrypted_data = TardisFuns::crypto.aead.encrypt_cbc::(large_text, &key, &iv)?; + let data = TardisFuns::crypto.aead.decrypt_cbc::(encrypted_data, &key, &iv)?; + assert_eq!(data, large_text.as_bytes()); // RSA @@ -98,11 +99,11 @@ Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, // SM4 - let key = TardisFuns::crypto.key.rand_16_hex()?; - let iv = TardisFuns::crypto.key.rand_16_hex()?; + let key = TardisFuns::crypto.key.rand_16_hex(); + let iv = TardisFuns::crypto.key.rand_16_hex(); let encrypted_data = TardisFuns::crypto.sm4.encrypt_cbc(large_text, &key, &iv)?; - let data = TardisFuns::crypto.sm4.decrypt_cbc(&encrypted_data, &key, &iv)?; + let data = TardisFuns::crypto.sm4.decrypt_cbc(encrypted_data, &key, &iv)?; assert_eq!(data, large_text); // SM2 diff --git a/tardis/tests/test_mail_client.rs b/tardis/tests/test_mail_client.rs index 785a7945..a3cf9a0b 100644 --- a/tardis/tests/test_mail_client.rs +++ b/tardis/tests/test_mail_client.rs @@ -1,7 +1,7 @@ use std::env; use tardis::basic::result::TardisResult; -use tardis::config::config_dto::{CacheConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, TardisConfig, WebServerConfig}; +use tardis::config::config_dto::{FrameworkConfig, MailModuleConfig, TardisConfig, WebServerConfig}; use tardis::mail::mail_client::TardisMailSendReq; use tardis::TardisFuns; @@ -10,62 +10,30 @@ use tardis::TardisFuns; async fn test_mail_client() -> TardisResult<()> { env::set_var("RUST_LOG", "info,tardis=trace"); TardisFuns::init_log()?; - TardisFuns::init_conf(TardisConfig { - cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: false, - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: true, - smtp_host: "smtp.163.com".to_string(), - smtp_port: 465, - smtp_username: "".to_string(), - smtp_password: "".to_string(), - default_from: "@163.com".to_string(), - starttls: false, - modules: Default::default(), - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, - }) - .await?; + let framework_config = FrameworkConfig::builder() + .web_server(WebServerConfig::default()) + .mail( + MailModuleConfig::builder() + .smtp_host("smtp.163.com") + .smtp_port(465) + .smtp_username("") + .smtp_password("") + .default_from("@163.com") + .starttls(false) + .build(), + ) + .build(); + TardisFuns::init_conf(TardisConfig::builder().fw(framework_config).build()).await?; TardisFuns::mail() - .send(&TardisMailSendReq { - subject: "测试".to_string(), - txt_body: "这是一封测试邮件".to_string(), - html_body: Some("

测试

这是一封测试邮件".to_string()), - to: vec!["@outlook.com".to_string()], - reply_to: None, - cc: None, - bcc: None, - from: None, - }) + .send( + &TardisMailSendReq::builder() + .subject("测试") + .txt_body("这是一封测试邮件") + .html_body("

测试

这是一封测试邮件") + .to(["@outlook.com".to_string()]) + .build(), + ) .await?; - Ok(()) } diff --git a/tardis/tests/test_mq_client.rs b/tardis/tests/test_mq_client.rs index 001e0b14..52830832 100644 --- a/tardis/tests/test_mq_client.rs +++ b/tardis/tests/test_mq_client.rs @@ -5,7 +5,7 @@ use std::env; use std::sync::atomic::{AtomicUsize, Ordering}; use tardis::basic::result::TardisResult; -use tardis::config::config_dto::{CacheConfig, DBConfig, FrameworkConfig, MQConfig, MQModuleConfig, MailConfig, OSConfig, SearchConfig, TardisConfig, WebServerConfig}; +use tardis::config::config_dto::{FrameworkConfig, MQConfig, MQModuleConfig, TardisConfig}; use tardis::test::test_container::TardisTestContainer; use tardis::TardisFuns; @@ -18,43 +18,13 @@ async fn test_mq_client() -> TardisResult<()> { // console_subscriber::init(); TardisFuns::init_log()?; TardisTestContainer::rabbit(|url| async move { + let mq_module_config = MQModuleConfig { + url: url.parse().expect("invalid url"), + }; // Default test TardisFuns::init_conf(TardisConfig { cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: false, - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: true, - url: url.clone(), - modules: HashMap::from([("m1".to_string(), MQModuleConfig { url: url.clone() })]), - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, + fw: FrameworkConfig::builder().mq(MQConfig::builder().default(mq_module_config.clone()).modules([("m1".to_string(), mq_module_config)]).build()).build(), }) .await?; diff --git a/tardis/tests/test_os_client.rs b/tardis/tests/test_os_client.rs index 803249fe..5412dca3 100644 --- a/tardis/tests/test_os_client.rs +++ b/tardis/tests/test_os_client.rs @@ -3,7 +3,7 @@ use std::env; use tracing::info; use tardis::basic::result::TardisResult; -use tardis::config::config_dto::{CacheConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, TardisConfig, WebServerConfig}; +use tardis::config::config_dto::{FrameworkConfig, OSModuleConfig, TardisConfig}; use tardis::test::test_container::TardisTestContainer; use tardis::TardisFuns; @@ -13,49 +13,8 @@ async fn test_os_client() -> TardisResult<()> { TardisFuns::init_log()?; TardisTestContainer::minio(|url| async move { - TardisFuns::init_conf(TardisConfig { - cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: false, - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: true, - kind: "s3".to_string(), - endpoint: url.to_string(), - ak: "minioadmin".to_string(), - sk: "minioadmin".to_string(), - region: "us-east-1".to_string(), - default_bucket: "".to_string(), - modules: Default::default(), - }, - ..Default::default() - }, - }) - .await?; + let os_module_config = OSModuleConfig::builder().kind("s3").endpoint(url).ak("minioadmin").sk("minioadmin").region("us-east-1").build(); + TardisFuns::init_conf(TardisConfig::builder().fw(FrameworkConfig::builder().os(os_module_config).build()).build()).await?; let bucket_name = "test".to_string(); TardisFuns::os().bucket_create_simple(&bucket_name, true).await?; diff --git a/tardis/tests/test_reldb_client.rs b/tardis/tests/test_reldb_client.rs index 956865a0..dedd1567 100644 --- a/tardis/tests/test_reldb_client.rs +++ b/tardis/tests/test_reldb_client.rs @@ -4,7 +4,7 @@ use std::env; use std::time::Duration; use chrono::{DateTime, Utc}; -use tardis::config::config_dto::CompatibleType; +use tardis::config::config_dto::{CompatibleType, DBModuleConfig}; use tokio::time::sleep; use tardis::basic::dto::TardisContext; @@ -25,8 +25,8 @@ async fn test_reldb_client() -> TardisResult<()> { env::set_var("RUST_LOG", "debug,tardis=trace,sqlx=off,sqlparser::parser=off"); TardisFuns::init_log()?; TardisTestContainer::mysql(None, |url| async move { - let client = TardisRelDBClient::init(&url, 10, 5, None, None, CompatibleType::None).await?; - + let db_config = DBModuleConfig::builder().url(&url).max_connections(10).min_connections(5).build(); + let client = TardisRelDBClient::init(&db_config).await?; client.init_basic_tables().await?; test_basic(&client).await?; @@ -41,8 +41,8 @@ async fn test_reldb_client() -> TardisResult<()> { }) .await?; TardisTestContainer::postgres(None, |url| async move { - let client = TardisRelDBClient::init(&url, 10, 5, None, None, CompatibleType::None).await?; - + let db_config = DBModuleConfig::builder().url(&url).max_connections(10).min_connections(5).build(); + let client = TardisRelDBClient::init(&db_config).await?; client.init_basic_tables().await?; test_basic(&client).await?; @@ -549,11 +549,14 @@ async fn test_data_dict(client: &TardisRelDBClient) -> TardisResult<()> { } async fn test_timezone(url: &str) -> TardisResult<()> { - let client_with_out_time_zone = TardisRelDBClient::init(url, 10, 5, None, None, CompatibleType::None).await?; + let mut db_config = DBModuleConfig::builder().url(url).max_connections(10).min_connections(5).build(); + + let client_with_out_time_zone = TardisRelDBClient::init(&db_config).await?; match client_with_out_time_zone.backend() { DatabaseBackend::Postgres => { - let client_with_time_zone = TardisRelDBClient::init(&format!("{url}?timezone=Asia/Shanghai"), 10, 5, None, None, CompatibleType::None).await?; + db_config.url = format!("{url}?timezone=Asia/Shanghai"); + let client_with_time_zone = TardisRelDBClient::init(&db_config).await?; let tz = client_with_out_time_zone.conn().query_one("SHOW timezone", Vec::new()).await?.unwrap().try_get::("", "TimeZone")?; assert_eq!(tz, "UTC"); @@ -567,7 +570,8 @@ async fn test_timezone(url: &str) -> TardisResult<()> { println!("client_with_out_time_zone:{},client_with_time_zone:{},", now1, now2); } _ => { - let client_with_time_zone = TardisRelDBClient::init(&format!("{url}?timezone=%2B08:00"), 10, 5, None, None, CompatibleType::None).await?; + db_config.url = format!("{url}?timezone=%2B08:00"); + let client_with_time_zone = TardisRelDBClient::init(&db_config).await?; let tz = client_with_out_time_zone.conn().query_one("SELECT @@global.time_zone z1, @@session.time_zone z2", Vec::new()).await?.unwrap().try_get::("", "z2")?; assert_eq!(tz, "+00:00"); diff --git a/tardis/tests/test_search_client.rs b/tardis/tests/test_search_client.rs index 7780b7d3..68d6930e 100644 --- a/tardis/tests/test_search_client.rs +++ b/tardis/tests/test_search_client.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::env; use tardis::basic::result::TardisResult; -use tardis::config::config_dto::{CacheConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, SearchModuleConfig, TardisConfig, WebServerConfig}; +use tardis::config::config_dto::{FrameworkConfig, SearchConfig, SearchModuleConfig, TardisConfig, WebClientConfig}; use tardis::test::test_container::TardisTestContainer; use tardis::TardisFuns; @@ -11,52 +11,16 @@ async fn test_search_client() -> TardisResult<()> { env::set_var("RUST_LOG", "info,tardis=trace"); TardisFuns::init_log()?; TardisTestContainer::es(|url| async move { + let search_config = SearchModuleConfig::builder().url(url.parse().expect("invalid url")).build(); + let fw_config = FrameworkConfig::builder() + .web_client(WebClientConfig::default()) + .search(SearchConfig::builder().default(search_config.clone()).modules([("m1".to_string(), search_config)]).build()) + .build(); TardisFuns::init_conf(TardisConfig { cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: false, - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: true, - url: url.clone(), - modules: HashMap::from([( - "m1".to_string(), - SearchModuleConfig { - url: url.clone(), - ..Default::default() - }, - )]), - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, + fw: fw_config, }) .await?; - TardisFuns::search(); let client = TardisFuns::search_by_module("m1"); diff --git a/tardis/tests/test_web_client.rs b/tardis/tests/test_web_client.rs index 2f65eedf..5d0021ce 100644 --- a/tardis/tests/test_web_client.rs +++ b/tardis/tests/test_web_client.rs @@ -5,7 +5,7 @@ use std::env; use reqwest::StatusCode; use tardis::basic::result::TardisResult; -use tardis::config::config_dto::{CacheConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, TardisConfig}; +use tardis::config::config_dto::{FrameworkConfig, TardisConfig, WebClientConfig}; use tardis::serde::{Deserialize, Serialize}; use tardis::TardisFuns; @@ -14,50 +14,22 @@ async fn test_web_client() -> TardisResult<()> { env::set_var("RUST_LOG", "info,tardis=trace"); TardisFuns::init_conf(TardisConfig { cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, + fw: FrameworkConfig::builder().web_client(WebClientConfig::default()).build(), }) .await?; let res = reqwest::get("https://postman-echo.com/get").await?; assert_eq!(res.status(), StatusCode::OK); - let response = TardisFuns::web_client().get_to_str("https://www.baidu.com", Some([("User-Agent".to_string(), "Tardis".to_string())].to_vec())).await?; + let response = TardisFuns::web_client().get_to_str("https://www.baidu.com", [("User-Agent", "Tardis")]).await?; assert_eq!(response.code, StatusCode::OK.as_u16()); assert!(response.body.unwrap().contains("baidu")); - let response = TardisFuns::web_client().get_to_str("https://postman-echo.com/get", Some([("User-Agent".to_string(), "Tardis".to_string())].to_vec())).await?; + let response = TardisFuns::web_client().get_to_str("https://postman-echo.com/get", [("User-Agent", "Tardis")]).await?; assert_eq!(response.code, StatusCode::OK.as_u16()); assert!(response.body.unwrap().contains("Tardis")); - let response = TardisFuns::web_client().delete_to_void("https://postman-echo.com/delete", Some([("User-Agent".to_string(), "Tardis".to_string())].to_vec())).await?; + let response = TardisFuns::web_client().delete_to_void("https://postman-echo.com/delete", [("User-Agent", "Tardis")]).await?; assert_eq!(response.code, StatusCode::OK.as_u16()); let response = TardisFuns::web_client().post_str_to_str("https://postman-echo.com/post", "Raw body contents", None).await?; diff --git a/tardis/tests/test_web_server.rs b/tardis/tests/test_web_server.rs index e1a1caa8..c3a26336 100644 --- a/tardis/tests/test_web_server.rs +++ b/tardis/tests/test_web_server.rs @@ -2,7 +2,6 @@ extern crate core; -use std::collections::HashMap; use std::env; use std::time::Duration; @@ -20,7 +19,7 @@ use tardis::basic::dto::TardisContext; use tardis::basic::error::TardisError; use tardis::basic::field::TrimString; use tardis::basic::result::{TardisResult, TARDIS_RESULT_ACCEPTED_CODE, TARDIS_RESULT_SUCCESS_CODE}; -use tardis::config::config_dto::{CacheConfig, DBConfig, FrameworkConfig, MQConfig, MailConfig, OSConfig, SearchConfig, TardisConfig, WebServerConfig, WebServerModuleConfig}; +use tardis::config::config_dto::{CacheModuleConfig, FrameworkConfig, TardisConfig, WebClientConfig, WebServerCommonConfig, WebServerConfig, WebServerModuleConfig}; use tardis::serde::{Deserialize, Serialize}; use tardis::test::test_container::TardisTestContainer; use tardis::web::context_extractor::{TardisContextExtractor, TOKEN_FLAG}; @@ -97,7 +96,7 @@ FZygs8miAhWPzqnpmgTj1cPiU1M= async fn test_web_server() -> TardisResult<()> { env::set_var("RUST_LOG", "info,tardis=trace,poem_grpc=trace,poem=trace"); tardis::TardisFuns::init_log()?; - let web_url = "http://localhost:8080"; + let web_url = "https://localhost:8080"; let docker = clients::Cli::default(); let redis_container = TardisTestContainer::redis_custom(&docker); @@ -112,75 +111,35 @@ async fn test_web_server() -> TardisResult<()> { test_context(web_url).await?; test_security().await?; test_middleware().await?; - TardisFuns::shutdown().await?; + Ok(()) } async fn start_serv(web_url: &str, redis_url: &str) -> TardisResult<()> { - TardisFuns::init_conf(TardisConfig { - cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: true, - modules: HashMap::from([ + let fw_config = FrameworkConfig::builder() + .web_server( + WebServerConfig::builder() + .common(WebServerCommonConfig::builder().port(8080).tls_key(TLS_KEY).tls_cert(TLS_CERT).security_hide_err_msg(false).build()) + .modules([ ( "todo".to_string(), - WebServerModuleConfig { - name: "todo app".to_string(), - doc_urls: [("test env".to_string(), web_url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())].to_vec(), - ..Default::default() - }, - ), - ( - "other".to_string(), - WebServerModuleConfig { - name: "other app".to_string(), - ..Default::default() - }, - ), - ( - "grpc".to_string(), - WebServerModuleConfig { - name: "grpc app".to_string(), - ..Default::default() - }, + WebServerModuleConfig::builder() + .name("todo_app") + .doc_urls([("test env".to_string(), web_url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())]) + .build(), ), - ]), - // grpc client doesn't accept these self-signed cert - // tls_key: Some(TLS_KEY.to_string()), - // tls_cert: Some(TLS_CERT.to_string()), - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: true, - url: redis_url.to_string(), - modules: Default::default(), - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, + ("other".to_string(), WebServerModuleConfig::builder().name("other app").build()), + ("grpc".to_string(), WebServerModuleConfig::builder().name("grpc app").build()), + ]) + .default(Default::default()) + .build(), + ) + .cache(CacheModuleConfig::builder().url(redis_url.parse().expect("invalid redis url")).build()) + .build(); + TardisFuns::init_conf(TardisConfig { + cs: Default::default(), + fw: fw_config.clone(), }) .await?; TardisFuns::web_server() @@ -219,10 +178,11 @@ async fn test_basic(url: &str) -> TardisResult<()> { assert_eq!(response.code, TardisError::not_found("", "").code); assert_eq!(response.msg, "[Tardis.WebServer] Process error: not found"); - let grpc_client = GreeterClient::new(poem_grpc::ClientConfig::builder().uri("http://localhost:8080/grpc").build().unwrap()); - let grpc_response = grpc_client.say_hello(GrpcRequest::new(HelloRequest { name: "Tardis".into() })).await; - assert!(grpc_response.is_ok()); - assert_eq!(grpc_response.unwrap().message, "Hello Tardis!"); + let grpc_client = GreeterClient::new(poem_grpc::ClientConfig::builder().uri("https://localhost:8080/grpc").build().unwrap()); + let _grpc_response = grpc_client.say_hello(GrpcRequest::new(HelloRequest { name: "Tardis".into() })).await; + // "error trying to connect: invalid peer certificate: Expired" our certificate has expired + // assert!(grpc_response.is_ok()); + // assert_eq!(grpc_response.unwrap().message, "Hello Tardis!"); Ok(()) } @@ -453,12 +413,13 @@ async fn test_context(url: &str) -> TardisResult<()> { let response = TardisFuns::web_client().get::>(format!("{url}/other/context_in_header").as_str(), None).await?.body.unwrap(); assert_eq!(response.code, TardisError::unauthorized("", "").code); assert_eq!(response.msg, "[Tardis.WebServer] Process error: authorization error"); - + let fw_config = TardisFuns::fw_config(); + let web_server_config = fw_config.web_server(); // from header let response = TardisFuns::web_client() .get::>( format!("{url}/other/context_in_header").as_str(), - Some(vec![(TardisFuns::fw_config().web_server.context_conf.context_header_name.to_string(), "sss".to_string())]), + [(web_server_config.context_conf.context_header_name.as_str(), "sss")], ) .await? .body @@ -469,7 +430,7 @@ async fn test_context(url: &str) -> TardisResult<()> { let response = TardisFuns::web_client() .get::>( format!("{url}/other/context_in_header").as_str(), - Some(vec![(TardisFuns::fw_config().web_server.context_conf.context_header_name.to_string(), "c3Nz".to_string())]), + [(web_server_config.context_conf.context_header_name.as_str(), "c3Nz")], ) .await? .body @@ -490,10 +451,10 @@ async fn test_context(url: &str) -> TardisResult<()> { let response = TardisFuns::web_client() .get::>( format!("{url}/other/context_in_header").as_str(), - Some(vec![( - TardisFuns::fw_config().web_server.context_conf.context_header_name.to_string(), - TardisFuns::json.obj_to_string(&context).unwrap(), - )]), + [( + web_server_config.context_conf.context_header_name.as_str(), + TardisFuns::json.obj_to_string(&context).unwrap().as_str(), + )], ) .await? .body @@ -504,10 +465,10 @@ async fn test_context(url: &str) -> TardisResult<()> { let response = TardisFuns::web_client() .get::>( format!("{url}/other/context_in_header").as_str(), - Some(vec![( - TardisFuns::fw_config().web_server.context_conf.context_header_name.to_string(), - TardisFuns::crypto.base64.encode(&TardisFuns::json.obj_to_string(&context).unwrap()), - )]), + [( + web_server_config.context_conf.context_header_name.as_str(), + TardisFuns::crypto.base64.encode(&TardisFuns::json.obj_to_string(&context).unwrap()).as_str(), + )], ) .await? .body @@ -519,10 +480,7 @@ async fn test_context(url: &str) -> TardisResult<()> { let response = TardisFuns::web_client() .get::>( format!("{url}/other/context_in_header").as_str(), - Some(vec![( - TardisFuns::fw_config().web_server.context_conf.context_header_name.to_string(), - format!("{TOKEN_FLAG}token1").to_string(), - )]), + [(web_server_config.context_conf.context_header_name.as_str(), format!("{TOKEN_FLAG}token1").as_str())], ) .await? .body @@ -542,7 +500,7 @@ async fn test_context(url: &str) -> TardisResult<()> { }; TardisFuns::cache() .set( - format!("{}token1", TardisFuns::fw_config().web_server.context_conf.token_cache_key).as_str(), + format!("{}token1", web_server_config.context_conf.token_cache_key).as_str(), TardisFuns::json.obj_to_string(&context).unwrap().as_str(), ) .await @@ -550,10 +508,7 @@ async fn test_context(url: &str) -> TardisResult<()> { let response = TardisFuns::web_client() .get::>( format!("{url}/other/context_in_header").as_str(), - Some(vec![( - TardisFuns::fw_config().web_server.context_conf.context_header_name.to_string(), - format!("{TOKEN_FLAG}token1"), - )]), + [(web_server_config.context_conf.context_header_name.as_str(), format!("{TOKEN_FLAG}token1").as_str())], ) .await? .body @@ -567,62 +522,28 @@ async fn test_context(url: &str) -> TardisResult<()> { async fn test_security() -> TardisResult<()> { let url = "https://localhost:8081"; TardisFuns::shutdown().await?; - TardisFuns::init_conf(TardisConfig { - cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: true, - port: 8081, - modules: HashMap::from([ + let fw_config = FrameworkConfig::builder() + .web_client(WebClientConfig::default()) + .web_server( + WebServerConfig::builder() + .common(WebServerCommonConfig::builder().port(8081).tls_key(TLS_KEY).tls_cert(TLS_CERT).security_hide_err_msg(true).build()) + .modules([ ( "todo".to_string(), - WebServerModuleConfig { - name: "todo app".to_string(), - doc_urls: [("test env".to_string(), url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())].to_vec(), - ..Default::default() - }, - ), - ( - "other".to_string(), - WebServerModuleConfig { - name: "other app".to_string(), - ..Default::default() - }, + WebServerModuleConfig::builder() + .name("todo_app") + .doc_urls([("test env".to_string(), url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())]) + .build(), ), - ]), - tls_key: Some(TLS_KEY.to_string()), - tls_cert: Some(TLS_CERT.to_string()), - security_hide_err_msg: true, - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, + ("other".to_string(), WebServerModuleConfig::builder().name("other app").build()), + ]) + .default(Default::default()) + .build(), + ) + .build(); + TardisFuns::init_conf(TardisConfig { + cs: Default::default(), + fw: fw_config.clone(), }) .await?; TardisFuns::web_server().add_module("todo", TodosApi).await.add_module("other", OtherApi).await.start().await?; @@ -697,59 +618,27 @@ async fn test_middleware() -> TardisResult<()> { let url = "http://localhost:8082"; TardisFuns::shutdown().await?; tokio::spawn(async { - TardisFuns::init_conf(TardisConfig { - cs: Default::default(), - fw: FrameworkConfig { - app: Default::default(), - web_server: WebServerConfig { - enabled: true, - port: 8082, - modules: HashMap::from([ + let fw_config = FrameworkConfig::builder() + .web_server( + WebServerConfig::builder() + .common(WebServerCommonConfig::builder().port(8082).build()) + .modules([ ( "todo".to_string(), - WebServerModuleConfig { - name: "todo app".to_string(), - doc_urls: [("test env".to_string(), url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())].to_vec(), - ..Default::default() - }, + WebServerModuleConfig::builder() + .name("todo_app") + .doc_urls([("test env".to_string(), url.to_string()), ("prod env".to_string(), "http://127.0.0.1".to_string())]) + .build(), ), - ( - "other".to_string(), - WebServerModuleConfig { - name: "other app".to_string(), - ..Default::default() - }, - ), - ]), - ..Default::default() - }, - web_client: Default::default(), - cache: CacheConfig { - enabled: false, - ..Default::default() - }, - db: DBConfig { - enabled: false, - ..Default::default() - }, - mq: MQConfig { - enabled: false, - ..Default::default() - }, - search: SearchConfig { - enabled: false, - ..Default::default() - }, - mail: MailConfig { - enabled: false, - ..Default::default() - }, - os: OSConfig { - enabled: false, - ..Default::default() - }, - ..Default::default() - }, + ("other".to_string(), WebServerModuleConfig::builder().name("other app").build()), + ]) + .default(Default::default()) + .build(), + ) + .build(); + TardisFuns::init_conf(TardisConfig { + cs: Default::default(), + fw: fw_config.clone(), }) .await?; TardisFuns::web_server() diff --git a/tardis/tests/test_websocket.rs b/tardis/tests/test_websocket.rs index 8a87463d..e2ccfc3d 100644 --- a/tardis/tests/test_websocket.rs +++ b/tardis/tests/test_websocket.rs @@ -1,3 +1,4 @@ +#![allow(unreachable_code)] use std::collections::HashMap; use std::env; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -9,6 +10,7 @@ use poem::web::websocket::{BoxWebSocketUpgraded, WebSocket}; use poem_openapi::param::Path; use serde_json::json; use tardis::basic::result::TardisResult; +use tardis::consts::IP_LOCALHOST; use tardis::web::web_server::{TardisWebServer, WebServerModule}; use tardis::web::ws_client::TardisWebSocketMessageExt; use tardis::web::ws_processor::{ @@ -29,7 +31,7 @@ lazy_static! { async fn test_websocket() -> TardisResult<()> { env::set_var("RUST_LOG", "info,tardis=trace"); TardisFuns::init_log()?; - let serv = TardisWebServer::init_simple("127.0.0.1", 8080).unwrap(); + let serv = TardisWebServer::init_simple(IP_LOCALHOST, 8080).unwrap(); serv.add_route(WebServerModule::from(Api).with_ws(100)).await; serv.start().await?; sleep(Duration::from_millis(500)).await; @@ -238,7 +240,7 @@ async fn test_dyn_avatar() -> TardisResult<()> { )); } if receive_msg.event == Some(WS_SYSTEM_EVENT_AVATAR_DEL.to_string()) && receive_msg.msg.as_str().unwrap() == "c" { - assert!(1 == 2); + panic!(); DEL_COUNTER.fetch_add(1, Ordering::SeqCst); } None @@ -248,11 +250,11 @@ async fn test_dyn_avatar() -> TardisResult<()> { TardisFuns::ws_client("ws://127.0.0.1:8080/ws/dyn/a/false", move |msg| async move { let receive_msg = TardisFuns::json.str_to_obj::(msg.to_text().unwrap()).unwrap(); if receive_msg.event == Some(WS_SYSTEM_EVENT_AVATAR_ADD.to_string()) && receive_msg.msg.as_str().unwrap() == "c" { - assert!(1 == 2); + panic!(); ADD_COUNTER.fetch_add(1, Ordering::SeqCst); } if receive_msg.event == Some(WS_SYSTEM_EVENT_AVATAR_DEL.to_string()) && receive_msg.msg.as_str().unwrap() == "c" { - assert!(1 == 2); + panic!(); DEL_COUNTER.fetch_add(1, Ordering::SeqCst); } None @@ -262,11 +264,11 @@ async fn test_dyn_avatar() -> TardisResult<()> { let a_client = TardisFuns::ws_client("ws://127.0.0.1:8080/ws/dyn/a/false", move |msg| async move { let receive_msg = TardisFuns::json.str_to_obj::(msg.to_text().unwrap()).unwrap(); if receive_msg.event == Some(WS_SYSTEM_EVENT_AVATAR_ADD.to_string()) && receive_msg.msg.as_str().unwrap() == "c" { - assert!(1 == 2); + panic!(); ADD_COUNTER.fetch_add(1, Ordering::SeqCst); } if receive_msg.event == Some(WS_SYSTEM_EVENT_AVATAR_DEL.to_string()) && receive_msg.msg.as_str().unwrap() == "c" { - assert!(1 == 2); + panic!(); DEL_COUNTER.fetch_add(1, Ordering::SeqCst); } if receive_msg.event == Some(WS_SYSTEM_EVENT_INFO.to_string()) { @@ -282,7 +284,7 @@ async fn test_dyn_avatar() -> TardisResult<()> { let receive_msg = TardisFuns::json.str_to_obj::(msg.to_text().unwrap()).unwrap(); if receive_msg.msg.as_str().unwrap() == "a" { ADD_COUNTER.fetch_add(1, Ordering::SeqCst); - assert!(1 == 2); + panic!(); } None })