diff --git a/Cargo.toml b/Cargo.toml index 24b2ddb..90ad051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prosa-hyper" -version = "0.3.0" +version = "0.3.1" authors = ["Jérémy HERGAULT ", "Anthony THOMAS ", "Julien TERUEL ", "Rene-Louis EYMARD "] description = "ProSA Hyper processor for HTTP client/server" homepage = "https://worldline.com/" @@ -31,6 +31,9 @@ proc = "client::proc::HyperClientProc" settings = "client::proc::HyperClientSettings" adaptor = [] +[lints.clippy] +unwrap_used = "deny" + [dependencies] bytes = ">=1.11.1, < 2" thiserror = "2" diff --git a/examples/client.rs b/examples/client.rs index f49a78d..a622426 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -139,18 +139,21 @@ async fn main() -> Result<(), Box> { .get_matches(); // load the configuration - let config = Config::builder() - .add_source(config::File::with_name( - matches.get_one::("config").unwrap().as_str(), - )) + let mut config_builder = Config::builder(); + if let Some(config_file) = matches + .get_one::("config") + .map(|c| config::File::with_name(c.as_str())) + { + config_builder = config_builder.add_source(config_file); + } + let config = config_builder .add_source( config::Environment::with_prefix("PROSA") .try_parsing(true) .separator("_") .list_separator(" "), ) - .build() - .unwrap(); + .build()?; let prosa_hyper_settings = config.try_deserialize::()?; let service_name = prosa_hyper_settings.hyper_client.service_name.clone(); @@ -174,7 +177,7 @@ async fn main() -> Result<(), Box> { bus.clone(), prosa_hyper_settings.hyper_client, ); - Proc::::run(http_proc); + Proc::::run(http_proc)?; if matches.contains_id("inj") && matches.get_flag("inj") { debug!("Start a Inj processor"); @@ -185,7 +188,7 @@ async fn main() -> Result<(), Box> { bus.clone(), inj_settings, ); - Proc::::run(inj_proc); + Proc::::run(inj_proc)?; } // Wait on main task diff --git a/examples/config.yml b/examples/config.yml index 0585a82..d191579 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -20,24 +20,18 @@ observability: level: DEBUG metrics: #otlp: # Opentelemetry protocol - # endpoint: "http://localhost:4137" - # timeout_sec: 3 - # protocol: Grpc + # endpoint: "grpc://localhost:4137" prometheus: endpoint: "0.0.0.0:9090" stdout: level: debug traces: #otlp: # Opentelemetry protocol - # endpoint: "http://localhost:4137" - # timeout_sec: 3 - # protocol: Grpc + # endpoint: "grpc://localhost:4137" stdout: level: debug logs: #otlp: # Opentelemetry protocol - # endpoint: "http://localhost:4137" - # timeout_sec: 3 - # protocol: Grpc + # endpoint: "grpc://localhost:4137" stdout: level: debug diff --git a/examples/server.rs b/examples/server.rs index 0aa91b2..cf3ab1d 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -123,18 +123,21 @@ async fn main() -> Result<(), Box> { .get_matches(); // load the configuration - let config = Config::builder() - .add_source(config::File::with_name( - matches.get_one::("config").unwrap().as_str(), - )) + let mut config_builder = Config::builder(); + if let Some(config_file) = matches + .get_one::("config") + .map(|c| config::File::with_name(c.as_str())) + { + config_builder = config_builder.add_source(config_file); + } + let config = config_builder .add_source( config::Environment::with_prefix("PROSA") .try_parsing(true) .separator("_") .list_separator(" "), ) - .build() - .unwrap(); + .build()?; let prosa_hyper_settings = config.try_deserialize::()?; @@ -157,7 +160,7 @@ async fn main() -> Result<(), Box> { bus.clone(), prosa_hyper_settings.hyper_server, ); - Proc::::run(http_proc); + Proc::::run(http_proc)?; if matches.contains_id("stub") && matches.get_flag("stub") { debug!("Start a Stub processor"); @@ -168,7 +171,7 @@ async fn main() -> Result<(), Box> { bus.clone(), stub_settings, ); - Proc::::run(stub_proc); + Proc::::run(stub_proc)?; } // Wait on main task diff --git a/src/server.rs b/src/server.rs index f536020..c8189cc 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,7 +26,7 @@ mod tests { use std::{ env, fs::{self, File}, - io::Read as _, + io::{self, Read as _}, time::Duration, }; use tokio::time; @@ -80,7 +80,11 @@ mod tests { } } - async fn run_test(settings: HttpTestSettings, certificate: Option, http2: bool) { + async fn run_test( + settings: HttpTestSettings, + certificate: Option, + http2: bool, + ) -> io::Result<()> { let url = settings.server.listener.url.clone(); // Create bus and main processor @@ -96,7 +100,7 @@ mod tests { bus.clone(), settings.server, ); - Proc::::run(http_server_proc); + Proc::::run(http_server_proc)?; // Wait for processor to start std::thread::sleep(Duration::from_secs(1)); @@ -106,12 +110,14 @@ mod tests { .timeout(Duration::from_secs(WAIT_TIME.as_secs())) .use_rustls_tls(); if let Some(cert) = certificate { - client_builder = client_builder.add_root_certificate(cert); + client_builder = client_builder.tls_certs_only(vec![cert]); } if http2 { client_builder = client_builder.http2_prior_knowledge(); } - let client = client_builder.build().unwrap(); + let client = client_builder + .build() + .expect("reqwest client should be valid"); for _i in 0..20 { let resp = client .get(url.clone()) @@ -119,29 +125,36 @@ mod tests { .await .expect("Failed to send request"); assert_eq!(resp.status(), StatusCode::OK); - let server_header = resp.headers().get(hyper::header::SERVER).unwrap(); - assert!(server_header.to_str().unwrap().starts_with(concat!( - env!("CARGO_PKG_NAME"), - "/", - env!("CARGO_PKG_VERSION") - ))); + assert!(resp.headers().get(hyper::header::SERVER).is_some_and(|h| { + h.to_str().is_ok_and(|s| { + s.starts_with(concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION") + )) + }) + })); } bus.stop("ProSA HTTP client server unit test end".into()) .await - .unwrap(); + .map_err(io::Error::other)?; // Wait on main task to end main_task.await; + Ok(()) } #[tokio::test] async fn http_client_server() { - let test_settings = - HttpTestSettings::new(Url::parse("http://localhost:48180").unwrap(), None, None); + let test_settings = HttpTestSettings::new( + Url::parse("http://localhost:48180").expect("HTTP client/server URL should be valid"), + None, + None, + ); // Run a ProSA to test - run_test(test_settings, None, false).await; + assert!(run_test(test_settings, None, false).await.is_ok()); } #[tokio::test] @@ -150,37 +163,57 @@ mod tests { let prosa_temp_dir = env::temp_dir().join(PROSA_HTTPS_TEST_DIR_NAME); let _ = fs::remove_dir_all(&prosa_temp_dir); - fs::create_dir_all(&prosa_temp_dir).unwrap(); + fs::create_dir_all(&prosa_temp_dir) + .expect("Can't create ProSA temporary directory for HTTPS"); let key_path = prosa_temp_dir.join("prosa_server_https.key"); let cert_path = prosa_temp_dir.join("prosa_server_https.pem"); + let cert_path_str = cert_path + .as_os_str() + .to_str() + .expect("Cert path should be a valid String"); let server_ssl_config = HttpTestSettings::create_server_cert( - key_path.as_os_str().to_str().unwrap().into(), - cert_path.as_os_str().to_str().unwrap().into(), + key_path + .as_os_str() + .to_str() + .expect("Key path should be a valid String") + .into(), + cert_path_str.into(), ) - .unwrap(); + .expect("Server certificate should be created"); let mut buf = Vec::new(); - File::open(cert_path.as_os_str().to_str().unwrap()) - .unwrap() + File::open(cert_path_str) + .expect("Cert file should exist") .read_to_end(&mut buf) - .unwrap(); - let client_cert = reqwest::Certificate::from_pem(&buf).unwrap(); + .expect("Cert file should be read"); + let client_cert = + reqwest::Certificate::from_pem(&buf).expect("Certificate should be valid for reqwest"); let client_ssl_store = Store::File { - path: format!("{}/", prosa_temp_dir.as_os_str().to_str().unwrap()), + path: format!( + "{}/", + prosa_temp_dir + .as_os_str() + .to_str() + .expect("ProSA temp dir should be a valid String") + ), }; let mut client_ssl_config = SslConfig::default(); client_ssl_config.set_store(client_ssl_store); let test_settings = HttpTestSettings::new( - Url::parse("https://localhost:48543").unwrap(), + Url::parse("https://localhost:48543").expect("HTTPS client/server URL should be valid"), Some(server_ssl_config), Some(client_ssl_config), ); // Run a ProSA to test - run_test(test_settings, Some(client_cert), false).await; + assert!( + run_test(test_settings, Some(client_cert), false) + .await + .is_ok() + ); } #[tokio::test] @@ -189,39 +222,58 @@ mod tests { let prosa_temp_dir = env::temp_dir().join(PROSA_H2_TEST_DIR_NAME); let _ = fs::remove_dir_all(&prosa_temp_dir); - fs::create_dir_all(&prosa_temp_dir).unwrap(); + fs::create_dir_all(&prosa_temp_dir).expect("Can't create ProSA temporary directory for H2"); let key_path = prosa_temp_dir.join("prosa_server_h2.key"); let cert_path = prosa_temp_dir.join("prosa_server_h2.pem"); + let cert_path_str = cert_path + .as_os_str() + .to_str() + .expect("Cert path should be a valid String"); let mut server_ssl_config = HttpTestSettings::create_server_cert( - key_path.as_os_str().to_str().unwrap().into(), - cert_path.as_os_str().to_str().unwrap().into(), + key_path + .as_os_str() + .to_str() + .expect("Key path should be a valid String") + .into(), + cert_path_str.into(), ) - .unwrap(); + .expect("Server certificate should be created"); // Need to set the ALPN for server because of inline configuration @see TargetSetting::new server_ssl_config.set_alpn(vec!["h2".into()]); let mut buf = Vec::new(); - File::open(cert_path.as_os_str().to_str().unwrap()) - .unwrap() + File::open(cert_path_str) + .expect("Cert file should exist") .read_to_end(&mut buf) - .unwrap(); - let client_cert = reqwest::Certificate::from_pem(&buf).unwrap(); + .expect("Cert file should be read"); + let client_cert = + reqwest::Certificate::from_pem(&buf).expect("Certificate should be valid for reqwest"); let client_ssl_store = Store::File { - path: format!("{}/", prosa_temp_dir.as_os_str().to_str().unwrap()), + path: format!( + "{}/", + prosa_temp_dir + .as_os_str() + .to_str() + .expect("ProSA temp dir should be a valid String") + ), }; let mut client_ssl_config = SslConfig::default(); client_ssl_config.set_store(client_ssl_store); client_ssl_config.set_alpn(vec!["h2".into()]); let test_settings = HttpTestSettings::new( - Url::parse("https://localhost:49543").unwrap(), + Url::parse("https://localhost:49543").expect("HTTP2 client/server URL should be valid"), Some(server_ssl_config), Some(client_ssl_config), ); // Run a ProSA to test - run_test(test_settings, Some(client_cert), true).await; + assert!( + run_test(test_settings, Some(client_cert), true) + .await + .is_ok() + ); } } diff --git a/src/server/proc.rs b/src/server/proc.rs index 8b034a2..18d3c0e 100644 --- a/src/server/proc.rs +++ b/src/server/proc.rs @@ -38,9 +38,11 @@ pub struct HyperServerSettings { impl HyperServerSettings { fn default_listener() -> ListenerSetting { - let mut url = Url::parse("http://0.0.0.0:8080").unwrap(); + let mut url = + Url::parse("http://0.0.0.0:8080").expect("Default Hyper server URL should be valid"); if let Ok(Ok(port)) = env::var("PORT").map(|p| p.parse::()) { - url.set_port(Some(port)).unwrap(); + url.set_port(Some(port)) + .expect("Default Hyper server URL should be base"); } ListenerSetting::new(url, None) diff --git a/src/tests.rs b/src/tests.rs index a2aa919..28382c8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -13,7 +13,7 @@ use openssl::{ use prosa::{core::settings::settings, inj::proc::InjSettings, stub::proc::StubSettings}; use prosa_utils::config::{ConfigError, os_country, ssl::SslConfig}; use serde::Serialize; -use std::{fs::File, io::Write as _}; +use std::{fs::File, io::Write as _, num::TryFromIntError}; use crate::{client::proc::HyperClientSettings, server::proc::HyperServerSettings}; @@ -58,8 +58,17 @@ impl HttpTestSettings { let serial_number = Asn1Integer::from_bn(&serial_bn)?; cert.set_serial_number(&serial_number)?; - let begin_valid_time = - Asn1Time::from_unix(std::time::UNIX_EPOCH.elapsed().unwrap().as_secs() as i64 - 360)?; + let begin_valid_time = Asn1Time::from_unix( + (std::time::UNIX_EPOCH + .elapsed() + .expect("UNIX epoch should be valid") + .as_secs() + - 360) + .try_into() + .map_err(|e: TryFromIntError| { + ConfigError::WrongValue("Asn1Time::from_UNIX_EPOCH".to_string(), e.to_string()) + })?, + )?; cert.set_not_before(&begin_valid_time)?; let end_valid_time = Asn1Time::days_from_now(3)?; // 3 days from now cert.set_not_after(&end_valid_time)?; @@ -150,7 +159,7 @@ mod tests { msg::simple_string_tvf::SimpleStringTvf, }; use std::{ - env, fs, + env, fs, io, sync::atomic::{AtomicU32, Ordering}, }; use tokio::{runtime, time}; @@ -429,25 +438,25 @@ mod tests { } } - async fn run_test(settings: HttpTestSettings, test_type: u64) { + async fn run_test(settings: HttpTestSettings, test_type: u64) -> Result<(), io::Error> { // Create bus and main processor let (bus, main) = MainProc::::create(&settings, Some(4)); // Launch the main task in a separate thread to run the ProSA runtime - let main_handle = std::thread::Builder::new() - .name("main".to_string()) - .spawn(move || { - runtime::Builder::new_multi_thread() - .worker_threads(1) - .enable_all() - .thread_name("main") - .build() - .unwrap() - .block_on(async { - main.run().await; - }) - }) - .unwrap(); + let main_handle = + std::thread::Builder::new() + .name("main".to_string()) + .spawn(move || { + runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .thread_name("main") + .build() + .expect("Runtime should be valid") + .block_on(async { + main.run().await; + }) + })?; // Launch stub to respond to the HTTP server let http_server_stub = StubProc::::create( @@ -456,7 +465,7 @@ mod tests { bus.clone(), settings.stub, ); - Proc::::run(http_server_stub); + Proc::::run(http_server_stub)?; // Launch an HTTP server processor let http_server_proc = HyperServerProc::::create( @@ -465,7 +474,7 @@ mod tests { bus.clone(), settings.server, ); - Proc::::run(http_server_proc); + Proc::::run(http_server_proc)?; // Wait for processor to start std::thread::sleep(WAIT_TIME); @@ -477,7 +486,7 @@ mod tests { bus.clone(), settings.client, ); - Proc::::run(http_client_proc); + Proc::::run(http_client_proc)?; // Launch an HTTP injector processor let http_inj_proc = InjProc::::create( @@ -486,14 +495,14 @@ mod tests { bus.clone(), settings.inj, ); - Proc::::run(http_inj_proc); + Proc::::run(http_inj_proc)?; // Wait for processor to finish processing std::thread::sleep(WAIT_TIME); bus.stop("ProSA HTTP client server unit test end".into()) .await - .unwrap(); + .map_err(io::Error::other)?; assert!( COUNTER[test_type as usize].load(Ordering::SeqCst) > 0, @@ -503,15 +512,19 @@ mod tests { // Wait on main task to end let _ = main_handle.join(); + Ok(()) } #[tokio::test] async fn http_client_server() { - let test_settings = - HttpTestSettings::new(Url::parse("http://localhost:48080").unwrap(), None, None); + let test_settings = HttpTestSettings::new( + Url::parse("http://localhost:48080").expect("HTTP client/server URL should be valid"), + None, + None, + ); // Run a ProSA to test - run_test(test_settings, 0).await; + assert!(run_test(test_settings, 0).await.is_ok()); } #[tokio::test] @@ -520,30 +533,43 @@ mod tests { let prosa_temp_dir = env::temp_dir().join(PROSA_HTTPS_TEST_DIR_NAME); let _ = fs::remove_dir_all(&prosa_temp_dir); - fs::create_dir_all(&prosa_temp_dir).unwrap(); + fs::create_dir_all(&prosa_temp_dir) + .expect("Can't create ProSA temporary directory for HTTPS"); let key_path = prosa_temp_dir.join("prosa_https.key"); let cert_path = prosa_temp_dir.join("prosa_https.pem"); let server_ssl_config = HttpTestSettings::create_server_cert( - key_path.as_os_str().to_str().unwrap().into(), - cert_path.as_os_str().to_str().unwrap().into(), + key_path + .as_os_str() + .to_str() + .expect("Key path should be a valid String") + .into(), + cert_path + .as_os_str() + .to_str() + .expect("Cert path should be a valid String") + .into(), ) - .unwrap(); + .expect("Server certificate should be created"); let client_ssl_store = Store::File { - path: prosa_temp_dir.as_os_str().to_str().unwrap().into(), + path: prosa_temp_dir + .as_os_str() + .to_str() + .expect("ProSA temp dir should be a valid String") + .into(), }; let mut client_ssl_config = SslConfig::default(); client_ssl_config.set_store(client_ssl_store); let test_settings = HttpTestSettings::new( - Url::parse("https://localhost:48443").unwrap(), + Url::parse("https://localhost:48443").expect("HTTPS client/server URL should be valid"), Some(server_ssl_config), Some(client_ssl_config), ); // Run a ProSA to test - run_test(test_settings, 1).await; + assert!(run_test(test_settings, 1).await.is_ok()); } #[tokio::test] @@ -552,32 +578,46 @@ mod tests { let prosa_temp_dir = env::temp_dir().join(PROSA_H2_TEST_DIR_NAME); let _ = fs::remove_dir_all(&prosa_temp_dir); - fs::create_dir_all(&prosa_temp_dir).unwrap(); + fs::create_dir_all(&prosa_temp_dir).expect("Can't create ProSA temporary directory for H2"); let key_path = prosa_temp_dir.join("prosa_h2.key"); let cert_path = prosa_temp_dir.join("prosa_h2.pem"); let mut server_ssl_config = HttpTestSettings::create_server_cert( - key_path.as_os_str().to_str().unwrap().into(), - cert_path.as_os_str().to_str().unwrap().into(), + key_path + .as_os_str() + .to_str() + .expect("Key path should be a valid String") + .into(), + cert_path + .as_os_str() + .to_str() + .expect("Cert path should be a valid String") + .into(), ) - .unwrap(); + .expect("Server certificate should be created"); // Need to set the ALPN for server because of inline configuration @see TargetSetting::new server_ssl_config.set_alpn(vec!["h2".into()]); let client_ssl_store = Store::File { - path: format!("{}/", prosa_temp_dir.as_os_str().to_str().unwrap()), + path: format!( + "{}/", + prosa_temp_dir + .as_os_str() + .to_str() + .expect("ProSA temp dir should be a valid String") + ), }; let mut client_ssl_config = SslConfig::default(); client_ssl_config.set_store(client_ssl_store); client_ssl_config.set_alpn(vec!["h2".into()]); let test_settings = HttpTestSettings::new( - Url::parse("h2://localhost:49443").unwrap(), + Url::parse("h2://localhost:49443").expect("HTTP2 client/server URL should be valid"), Some(server_ssl_config), Some(client_ssl_config), ); // Run a ProSA to test - run_test(test_settings, 2).await; + assert!(run_test(test_settings, 2).await.is_ok()); } }