From 9d8aaa06d61d4342f9055b65223d781bc4ead491 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 5 Apr 2022 06:46:58 -0700 Subject: [PATCH 01/11] add -i flag for multiple concurrent requests --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7788d92..e68010b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use reqwest::header::CONTENT_TYPE; use serde::{Serialize, Deserialize}; use std::fmt; use clap::{Parser}; +use std::thread::{spawn, JoinHandle}; /// Defines the Ambi Mock Client command line interface as a struct #[derive(Parser, Debug)] @@ -27,6 +28,10 @@ pub struct Cli { /// Turns verbose console debug output on #[clap(short, long)] pub debug: bool, + + // Make int number of concurrent requests + #[clap(short, long, default_value_t = 1)] + pub int: u16 } #[derive(Serialize, Deserialize)] @@ -110,9 +115,7 @@ fn random_gen_dust_concentration() -> String { rng.gen_range(0..=1000).to_string() } -pub fn run(cli: &Cli) { - println!("\r\ncli: {:?}\r\n", cli); - +fn send_request(url: &str, client: Client, debug: bool) { let dust_concentration = random_gen_dust_concentration(); let air_purity = AirPurity::from_value(dust_concentration.parse::().unwrap()).to_string(); let reading = Reading::new( @@ -120,29 +123,26 @@ pub fn run(cli: &Cli) { random_gen_humidity(), random_gen_pressure(), dust_concentration, - air_purity + air_purity, ); let json = serde_json::to_string(&reading).unwrap(); - const URL: &str = "http://localhost:4000/api/readings/add"; - - println!("Sending POST request to {} as JSON: {}", URL, json); - - let client = Client::new(); + println!("Sending POST request to {} as JSON: {}", url, json); let res = client - .post(URL) + .post(url) .header(CONTENT_TYPE, "application/json") .body(json) .send(); match res { - Ok(response) => { - match cli.debug { - true => println!("Response from Ambi backend: {:#?}", response), - false => println!("Response from Ambi backend: {:?}", response.status().as_str()) - } - } + Ok(response) => match debug { + true => println!("Response from Ambi backend: {:#?}", response), + false => println!( + "Response from Ambi backend: {:?}", + response.status().as_str() + ), + }, Err(e) => { - match cli.debug { + match debug { // Print out the entire reqwest::Error for verbose debugging true => eprintln!("Response error from Ambi backend: {:?}", e), // Keep the error reports more succinct @@ -160,6 +160,23 @@ pub fn run(cli: &Cli) { } } +pub fn run(cli: &Cli) { + println!("\r\ncli: {:?}\r\n", cli); + + const URL: &str = "http://localhost:4000/api/readings/add"; + let mut handlers: Vec> = vec![]; + let debug: bool = cli.debug; + for _ in 0..cli.int { + let handler = spawn(move || send_request(URL, Client::new(), debug)); + + handlers.push(handler); + } + + for handler in handlers { + handler.join().unwrap(); + } +} + #[cfg(test)] mod tests { use super::*; From 584b6f27d71018e6d78cdd1b0029152ad48b3aab Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Tue, 5 Apr 2022 10:27:53 -0500 Subject: [PATCH 02/11] Move all application logic into src/lib.rs per standard Rust application layout pattern. --- src/lib.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 160 -------------------------------------------------- 2 files changed, 163 insertions(+), 162 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 967e15f..bbd6a61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,12 @@ //! //! See the `LICENSE` file for Copyright and license details. //! -//! + +use rand::{thread_rng, Rng}; +use reqwest::blocking::Client; +use reqwest::header::CONTENT_TYPE; +use serde::{Serialize, Deserialize}; +use std::fmt; use clap::{Parser}; /// Defines the Ambi Mock Client command line interface as a struct @@ -24,6 +29,162 @@ pub struct Cli { pub debug: bool, } + +#[derive(Serialize, Deserialize)] +struct Reading { + temperature: String, + humidity: String, + pressure: String, + dust_concentration: String, + air_purity: String +} + +impl Reading { + fn new( + temperature: String, + humidity: String, + pressure: String, + dust_concentration: String, + air_purity: String + ) -> Reading { + Reading { + temperature, + humidity, + pressure, + dust_concentration, + air_purity + } + } +} + +#[derive(Debug, PartialEq)] +enum AirPurity { + Dangerous, + High, + Low, + FreshAir +} + +impl AirPurity { + fn from_value(value: u16) -> AirPurity { + match value { + 0..=50 => return AirPurity::FreshAir, + 51..=100 => return AirPurity::Low, + 101..=150 => return AirPurity::High, + 151.. => return AirPurity::Dangerous, + }; + } +} + +// implements fmt::Display for AirPurity so that we can call .to_string() on +// each enum value +impl fmt::Display for AirPurity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AirPurity::Low => write!(f, "Fresh Air"), + AirPurity::High => write!(f, "Low Pollution"), + AirPurity::Dangerous => write!(f, "High Pollution"), + AirPurity::FreshAir => write!(f, "Dangerous Pollution") + } + } +} + +fn random_gen_humidity() -> String { + let mut rng = thread_rng(); + let value = rng.gen_range(0.0..=100.0); + format!("{:.1}", value) +} + +fn random_gen_temperature() -> String { + let mut rng = thread_rng(); + let value = rng.gen_range(15.0..=35.0); + format!("{:.1}", value) +} + +fn random_gen_pressure() -> String { + let mut rng = thread_rng(); + rng.gen_range(900..=1100).to_string() +} + +fn random_gen_dust_concentration() -> String { + let mut rng = thread_rng(); + rng.gen_range(0..=1000).to_string() +} + pub fn run(cli: &Cli) { println!("\r\ncli: {:?}\r\n", cli); -} \ No newline at end of file + + let dust_concentration = random_gen_dust_concentration(); + let air_purity = AirPurity::from_value(dust_concentration.parse::().unwrap()).to_string(); + let reading = Reading::new( + random_gen_temperature(), + random_gen_humidity(), + random_gen_pressure(), + dust_concentration, + air_purity + ); + + let json = serde_json::to_string(&reading).unwrap(); + const URL: &str = "http://localhost:4000/api/readings/add"; + + println!("Sending POST request to {} as JSON: {}", URL, json); + + let client = Client::new(); + let res = client + .post(URL) + .header(CONTENT_TYPE, "application/json") + .body(json) + .send(); + + println!("Response: {:#?}", res); +} + +#[cfg(test)] +mod tests { + use super::*; + use regex::Regex; + + #[test] + fn random_gen_humidity_returns_correctly_formatted_humidity_data() { + let result = random_gen_humidity(); + let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); + + assert!(regex.is_match(&result)); + } + + #[test] + fn random_gen_temperature_returns_correctly_formatted_humidity_data() { + let result = random_gen_temperature(); + let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); + + assert!(regex.is_match(&result)); + } + + #[test] + fn random_gen_pressure_returns_correctly_formatted_pressure_data() { + let result = random_gen_pressure(); + let regex = Regex::new(r"\d{3,4}").unwrap(); + assert!(regex.is_match(&result)); + } + + #[test] + fn random_gen_dust_concentration_returns_correctly_formatted_pressure_data() { + let result = random_gen_dust_concentration(); + let regex = Regex::new(r"\d{0,4}").unwrap(); + assert!(regex.is_match(&result)); + } + + #[test] + fn air_purity_from_value_returns_correct_enum() { + let mut rng = thread_rng(); + let fresh_air = rng.gen_range(0..=50); + let low = rng.gen_range(51..=100); + let high = rng.gen_range(101..=150); + let dangerous = rng.gen_range(151..u16::MAX); + + assert_eq!(AirPurity::from_value(fresh_air), AirPurity::FreshAir); + assert_eq!(AirPurity::from_value(low), AirPurity::Low); + assert_eq!(AirPurity::from_value(high), AirPurity::High); + assert_eq!(AirPurity::from_value(dangerous), AirPurity::Dangerous); + } +} diff --git a/src/main.rs b/src/main.rs index d7e9af9..bab8a18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,97 +10,11 @@ //! See the `LICENSE` file for Copyright and license details. //! -use rand::{thread_rng, Rng}; -use reqwest::blocking::Client; -use reqwest::header::CONTENT_TYPE; -use serde::{Serialize, Deserialize}; -use std::fmt; use clap::{Parser}; // Internal library namespace for separation of app logic use ambi_mock_client; -#[derive(Serialize, Deserialize)] -struct Reading { - temperature: String, - humidity: String, - pressure: String, - dust_concentration: String, - air_purity: String -} - -impl Reading { - fn new( - temperature: String, - humidity: String, - pressure: String, - dust_concentration: String, - air_purity: String - ) -> Reading { - Reading { - temperature, - humidity, - pressure, - dust_concentration, - air_purity - } - } -} - -#[derive(Debug, PartialEq)] -enum AirPurity { - Dangerous, - High, - Low, - FreshAir -} - -impl AirPurity { - fn from_value(value: u16) -> AirPurity { - match value { - 0..=50 => return AirPurity::FreshAir, - 51..=100 => return AirPurity::Low, - 101..=150 => return AirPurity::High, - 151.. => return AirPurity::Dangerous, - }; - } -} - -// implements fmt::Display for AirPurity so that we can call .to_string() on -// each enum value -impl fmt::Display for AirPurity { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - AirPurity::Low => write!(f, "Fresh Air"), - AirPurity::High => write!(f, "Low Pollution"), - AirPurity::Dangerous => write!(f, "High Pollution"), - AirPurity::FreshAir => write!(f, "Dangerous Pollution") - } - } -} - -fn random_gen_humidity() -> String { - let mut rng = thread_rng(); - let value = rng.gen_range(0.0..=100.0); - format!("{:.1}", value) -} - -fn random_gen_temperature() -> String { - let mut rng = thread_rng(); - let value = rng.gen_range(15.0..=35.0); - format!("{:.1}", value) -} - -fn random_gen_pressure() -> String { - let mut rng = thread_rng(); - rng.gen_range(900..=1100).to_string() -} - -fn random_gen_dust_concentration() -> String { - let mut rng = thread_rng(); - rng.gen_range(0..=1000).to_string() -} - fn main() { // Parses the provided command line interface arguments and flags let cli = ambi_mock_client::Cli::parse(); @@ -111,78 +25,4 @@ fn main() { } ambi_mock_client::run(&cli); - - let dust_concentration = random_gen_dust_concentration(); - let air_purity = AirPurity::from_value(dust_concentration.parse::().unwrap()).to_string(); - let reading = Reading::new( - random_gen_temperature(), - random_gen_humidity(), - random_gen_pressure(), - dust_concentration, - air_purity - ); - - let json = serde_json::to_string(&reading).unwrap(); - const URL: &str = "http://localhost:4000/api/readings/add"; - - println!("Sending POST request to {} as JSON: {}", URL, json); - - let client = Client::new(); - let res = client - .post(URL) - .header(CONTENT_TYPE, "application/json") - .body(json) - .send(); - - println!("Response: {:#?}", res); -} - -#[cfg(test)] -mod tests { - use super::*; - use regex::Regex; - - #[test] - fn random_gen_humidity_returns_correctly_formatted_humidity_data() { - let result = random_gen_humidity(); - let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); - - assert!(regex.is_match(&result)); - } - - #[test] - fn random_gen_temperature_returns_correctly_formatted_humidity_data() { - let result = random_gen_temperature(); - let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); - - assert!(regex.is_match(&result)); - } - - #[test] - fn random_gen_pressure_returns_correctly_formatted_pressure_data() { - let result = random_gen_pressure(); - let regex = Regex::new(r"\d{3,4}").unwrap(); - assert!(regex.is_match(&result)); - } - - #[test] - fn random_gen_dust_concentration_returns_correctly_formatted_pressure_data() { - let result = random_gen_dust_concentration(); - let regex = Regex::new(r"\d{0,4}").unwrap(); - assert!(regex.is_match(&result)); - } - - #[test] - fn air_purity_from_value_returns_correct_enum() { - let mut rng = thread_rng(); - let fresh_air = rng.gen_range(0..=50); - let low = rng.gen_range(51..=100); - let high = rng.gen_range(101..=150); - let dangerous = rng.gen_range(151..u16::MAX); - - assert_eq!(AirPurity::from_value(fresh_air), AirPurity::FreshAir); - assert_eq!(AirPurity::from_value(low), AirPurity::Low); - assert_eq!(AirPurity::from_value(high), AirPurity::High); - assert_eq!(AirPurity::from_value(dangerous), AirPurity::Dangerous); - } } From 61680eba61886f3888bb322cfe395e62e4310977 Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Tue, 5 Apr 2022 10:33:22 -0500 Subject: [PATCH 03/11] Modifies HTTP request response debug printing to make use of the -d | --debug CLI flag. --- src/lib.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bbd6a61..dfb6794 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,7 +136,30 @@ pub fn run(cli: &Cli) { .body(json) .send(); - println!("Response: {:#?}", res); + match res { + Ok(response) => { + match cli.debug { + true => println!("Response from Ambi backend: {:#?}", response), + false => println!("Response from Ambi backend: {:?}", response.status().as_str()) + } + } + Err(e) => { + match cli.debug { + // Print out the entire reqwest::Error for verbose debugging + true => eprintln!("Response error from Ambi backend: {:?}", e), + // Keep the error reports more succinct + false => { + if e.is_request() { + eprintln!("Response error from Ambi backend: request error"); + } else if e.is_timeout() { + eprintln!("Response error from Ambi backend: request timed out"); + } else { + eprintln!("Response error from Ambi backend: specific error type unknown"); + } + } + } + } + } } #[cfg(test)] From 59aa0e0d5ff87a590a5bcea46af41327d1d93937 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Wed, 6 Apr 2022 07:25:24 -0700 Subject: [PATCH 04/11] Add Output struct for custom formatting --- src/lib.rs | 126 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e68010b..9eb429e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,12 @@ //! use rand::{thread_rng, Rng}; -use reqwest::blocking::Client; +use reqwest::blocking::{Client, Response}; use reqwest::header::CONTENT_TYPE; use serde::{Serialize, Deserialize}; use std::fmt; use clap::{Parser}; -use std::thread::{spawn, JoinHandle}; +use std::thread::{spawn, JoinHandle, ThreadId}; /// Defines the Ambi Mock Client command line interface as a struct #[derive(Parser, Debug)] @@ -61,6 +61,74 @@ impl Reading { } } +#[derive(Debug)] +struct Output { + description: String, + error: Option, + data: Option, + thread_id: ThreadId, + debug: bool + } + +impl Output { + pub fn new( + description: String, + error: Option, + data: Option, + thread_id: ThreadId, + debug: bool + ) -> Output { + Output { + description, + error, + data, + thread_id, + debug + } + } + + pub fn is_error(&self) -> bool { + self.error.is_some() + } + + pub fn print(&self) { + if self.is_error() { + self.print_to_stderr() + } else { + self.print_to_stdout() + } + } + + fn print_to_stderr(&self) { + if self.debug { + eprintln!("{:#?}", self) + } else { + eprintln!("{}", self) + } + } + + fn print_to_stdout(&self) { + if self.debug { + println!("{:#?}", self) + } else { + println!("{}", self) + } + } +} + +impl fmt::Display for Output { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_error() { + let error = self.error.as_ref().unwrap(); + write!(f, "Response error from Ambi. Status: {:?}, Thread ID: {:?}", error.status(), self.thread_id) + } else { + let response = self.data.as_ref().unwrap(); + let status = response.status().as_u16(); + write!(f, "Response from Ambi. Status: {}, Thread ID: {:?}", status, self.thread_id) + } + } +} + #[derive(Debug, PartialEq)] enum AirPurity { Dangerous, @@ -115,7 +183,7 @@ fn random_gen_dust_concentration() -> String { rng.gen_range(0..=1000).to_string() } -fn send_request(url: &str, client: Client, debug: bool) { +fn send_request(url: &str, client: Client) -> reqwest::Result { let dust_concentration = random_gen_dust_concentration(); let air_purity = AirPurity::from_value(dust_concentration.parse::().unwrap()).to_string(); let reading = Reading::new( @@ -128,36 +196,11 @@ fn send_request(url: &str, client: Client, debug: bool) { let json = serde_json::to_string(&reading).unwrap(); println!("Sending POST request to {} as JSON: {}", url, json); - let res = client + client .post(url) .header(CONTENT_TYPE, "application/json") .body(json) - .send(); - match res { - Ok(response) => match debug { - true => println!("Response from Ambi backend: {:#?}", response), - false => println!( - "Response from Ambi backend: {:?}", - response.status().as_str() - ), - }, - Err(e) => { - match debug { - // Print out the entire reqwest::Error for verbose debugging - true => eprintln!("Response error from Ambi backend: {:?}", e), - // Keep the error reports more succinct - false => { - if e.is_request() { - eprintln!("Response error from Ambi backend: request error"); - } else if e.is_timeout() { - eprintln!("Response error from Ambi backend: request timed out"); - } else { - eprintln!("Response error from Ambi backend: specific error type unknown"); - } - } - } - } - } + .send() } pub fn run(cli: &Cli) { @@ -167,7 +210,28 @@ pub fn run(cli: &Cli) { let mut handlers: Vec> = vec![]; let debug: bool = cli.debug; for _ in 0..cli.int { - let handler = spawn(move || send_request(URL, Client::new(), debug)); + let handler = spawn(move || + match send_request(URL, Client::new()) { + Ok(response) => { + Output::new( + String::from("Response from Ambi backend"), + None, + Some(response), + std::thread::current().id(), + debug + ).print(); + } + Err(error) => { + Output::new( + String::from("Response error from Ambi backend"), + Some(error), + None, + std::thread::current().id(), + debug + ).print(); + } + } + ); handlers.push(handler); } From 09c7479d2f0ac252caf94369a12497a06faab6c9 Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Sun, 3 Apr 2022 16:16:23 -0500 Subject: [PATCH 05/11] Make use of the debug CLI flag for request response status and error printing --- Cargo.lock | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 - src/main.rs | 5 --- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5fcf4e..fbc863c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,7 @@ dependencies = [ name = "ambi_mock_client" version = "0.1.0" dependencies = [ + "clap", "rand", "regex", "reqwest", @@ -22,6 +23,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -64,6 +76,36 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "3.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "core-foundation" version = "0.9.2" @@ -213,6 +255,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -487,6 +535,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -517,6 +574,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.36" @@ -744,6 +825,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.85" @@ -769,6 +856,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "tinyvec" version = "1.5.1" @@ -894,6 +996,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "want" version = "0.3.0" @@ -1002,6 +1110,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/src/lib.rs b/src/lib.rs index dfb6794..d43f7d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ pub struct Cli { pub debug: bool, } - #[derive(Serialize, Deserialize)] struct Reading { temperature: String, diff --git a/src/main.rs b/src/main.rs index bab8a18..99efe11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,10 +19,5 @@ fn main() { // Parses the provided command line interface arguments and flags let cli = ambi_mock_client::Cli::parse(); - match cli.debug { - true => println!("Debug mode is now *on*"), - false => println!("Debug mode is now *off*") - } - ambi_mock_client::run(&cli); } From abe575ee5b88964a08c91983a0da5a55b1ddcb74 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 5 Apr 2022 06:46:58 -0700 Subject: [PATCH 06/11] add -i flag for multiple concurrent requests --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d43f7d0..1e0f011 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use reqwest::header::CONTENT_TYPE; use serde::{Serialize, Deserialize}; use std::fmt; use clap::{Parser}; +use std::thread::{spawn, JoinHandle}; /// Defines the Ambi Mock Client command line interface as a struct #[derive(Parser, Debug)] @@ -27,6 +28,10 @@ pub struct Cli { /// Turns verbose console debug output on #[clap(short, long)] pub debug: bool, + + // Make int number of concurrent requests + #[clap(short, long, default_value_t = 1)] + pub int: u16 } #[derive(Serialize, Deserialize)] @@ -110,9 +115,7 @@ fn random_gen_dust_concentration() -> String { rng.gen_range(0..=1000).to_string() } -pub fn run(cli: &Cli) { - println!("\r\ncli: {:?}\r\n", cli); - +fn send_request(url: &str, client: Client, debug: bool) { let dust_concentration = random_gen_dust_concentration(); let air_purity = AirPurity::from_value(dust_concentration.parse::().unwrap()).to_string(); let reading = Reading::new( @@ -120,30 +123,27 @@ pub fn run(cli: &Cli) { random_gen_humidity(), random_gen_pressure(), dust_concentration, - air_purity + air_purity, ); let json = serde_json::to_string(&reading).unwrap(); - const URL: &str = "http://localhost:4000/api/readings/add"; - - println!("Sending POST request to {} as JSON: {}", URL, json); - - let client = Client::new(); + println!("Sending POST request to {} as JSON: {}", url, json); let res = client - .post(URL) + .post(url) .header(CONTENT_TYPE, "application/json") .body(json) .send(); match res { - Ok(response) => { - match cli.debug { - true => println!("Response from Ambi backend: {:#?}", response), - false => println!("Response from Ambi backend: {:?}", response.status().as_str()) - } - } + Ok(response) => match debug { + true => println!("Response from Ambi backend: {:#?}", response), + false => println!( + "Response from Ambi backend: {:?}", + response.status().as_str() + ), + }, Err(e) => { - match cli.debug { + match debug { // Print out the entire reqwest::Error for verbose debugging true => eprintln!("Response error from Ambi backend: {:?}", e), // Keep the error reports more succinct @@ -161,6 +161,23 @@ pub fn run(cli: &Cli) { } } +pub fn run(cli: &Cli) { + println!("\r\ncli: {:?}\r\n", cli); + + const URL: &str = "http://localhost:4000/api/readings/add"; + let mut handlers: Vec> = vec![]; + let debug: bool = cli.debug; + for _ in 0..cli.int { + let handler = spawn(move || send_request(URL, Client::new(), debug)); + + handlers.push(handler); + } + + for handler in handlers { + handler.join().unwrap(); + } +} + #[cfg(test)] mod tests { use super::*; From 44dc8901555544e4ecb4c05bfba57bbeff01d4b1 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Wed, 6 Apr 2022 07:25:24 -0700 Subject: [PATCH 07/11] Add Output struct for custom formatting --- src/lib.rs | 127 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1e0f011..9eb429e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,12 @@ //! use rand::{thread_rng, Rng}; -use reqwest::blocking::Client; +use reqwest::blocking::{Client, Response}; use reqwest::header::CONTENT_TYPE; use serde::{Serialize, Deserialize}; use std::fmt; use clap::{Parser}; -use std::thread::{spawn, JoinHandle}; +use std::thread::{spawn, JoinHandle, ThreadId}; /// Defines the Ambi Mock Client command line interface as a struct #[derive(Parser, Debug)] @@ -61,6 +61,74 @@ impl Reading { } } +#[derive(Debug)] +struct Output { + description: String, + error: Option, + data: Option, + thread_id: ThreadId, + debug: bool + } + +impl Output { + pub fn new( + description: String, + error: Option, + data: Option, + thread_id: ThreadId, + debug: bool + ) -> Output { + Output { + description, + error, + data, + thread_id, + debug + } + } + + pub fn is_error(&self) -> bool { + self.error.is_some() + } + + pub fn print(&self) { + if self.is_error() { + self.print_to_stderr() + } else { + self.print_to_stdout() + } + } + + fn print_to_stderr(&self) { + if self.debug { + eprintln!("{:#?}", self) + } else { + eprintln!("{}", self) + } + } + + fn print_to_stdout(&self) { + if self.debug { + println!("{:#?}", self) + } else { + println!("{}", self) + } + } +} + +impl fmt::Display for Output { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_error() { + let error = self.error.as_ref().unwrap(); + write!(f, "Response error from Ambi. Status: {:?}, Thread ID: {:?}", error.status(), self.thread_id) + } else { + let response = self.data.as_ref().unwrap(); + let status = response.status().as_u16(); + write!(f, "Response from Ambi. Status: {}, Thread ID: {:?}", status, self.thread_id) + } + } +} + #[derive(Debug, PartialEq)] enum AirPurity { Dangerous, @@ -115,7 +183,7 @@ fn random_gen_dust_concentration() -> String { rng.gen_range(0..=1000).to_string() } -fn send_request(url: &str, client: Client, debug: bool) { +fn send_request(url: &str, client: Client) -> reqwest::Result { let dust_concentration = random_gen_dust_concentration(); let air_purity = AirPurity::from_value(dust_concentration.parse::().unwrap()).to_string(); let reading = Reading::new( @@ -128,37 +196,11 @@ fn send_request(url: &str, client: Client, debug: bool) { let json = serde_json::to_string(&reading).unwrap(); println!("Sending POST request to {} as JSON: {}", url, json); - let res = client + client .post(url) .header(CONTENT_TYPE, "application/json") .body(json) - .send(); - - match res { - Ok(response) => match debug { - true => println!("Response from Ambi backend: {:#?}", response), - false => println!( - "Response from Ambi backend: {:?}", - response.status().as_str() - ), - }, - Err(e) => { - match debug { - // Print out the entire reqwest::Error for verbose debugging - true => eprintln!("Response error from Ambi backend: {:?}", e), - // Keep the error reports more succinct - false => { - if e.is_request() { - eprintln!("Response error from Ambi backend: request error"); - } else if e.is_timeout() { - eprintln!("Response error from Ambi backend: request timed out"); - } else { - eprintln!("Response error from Ambi backend: specific error type unknown"); - } - } - } - } - } + .send() } pub fn run(cli: &Cli) { @@ -168,7 +210,28 @@ pub fn run(cli: &Cli) { let mut handlers: Vec> = vec![]; let debug: bool = cli.debug; for _ in 0..cli.int { - let handler = spawn(move || send_request(URL, Client::new(), debug)); + let handler = spawn(move || + match send_request(URL, Client::new()) { + Ok(response) => { + Output::new( + String::from("Response from Ambi backend"), + None, + Some(response), + std::thread::current().id(), + debug + ).print(); + } + Err(error) => { + Output::new( + String::from("Response error from Ambi backend"), + Some(error), + None, + std::thread::current().id(), + debug + ).print(); + } + } + ); handlers.push(handler); } From 214ee76d286573c5379ac9281ec9fe63241bd0b3 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Thu, 7 Apr 2022 16:58:34 -0700 Subject: [PATCH 08/11] Update description for -i flag Co-authored-by: Jim Hodapp --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9eb429e..864de44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub struct Cli { #[clap(short, long)] pub debug: bool, - // Make int number of concurrent requests + /// Make number of concurrent requests #[clap(short, long, default_value_t = 1)] pub int: u16 } From 1857c0cee47cfb1e01d8dc89648c927b48eca5ed Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Thu, 7 Apr 2022 17:55:57 -0700 Subject: [PATCH 09/11] use Output description in output --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9eb429e..dd7e59d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,11 +120,11 @@ impl fmt::Display for Output { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_error() { let error = self.error.as_ref().unwrap(); - write!(f, "Response error from Ambi. Status: {:?}, Thread ID: {:?}", error.status(), self.thread_id) + write!(f, "{} Status: {:?}, Thread ID: {:?}", self.description, error.status(), self.thread_id) } else { let response = self.data.as_ref().unwrap(); let status = response.status().as_u16(); - write!(f, "Response from Ambi. Status: {}, Thread ID: {:?}", status, self.thread_id) + write!(f, "{} Status: {}, Thread ID: {:?}",self.description, status, self.thread_id) } } } @@ -214,7 +214,7 @@ pub fn run(cli: &Cli) { match send_request(URL, Client::new()) { Ok(response) => { Output::new( - String::from("Response from Ambi backend"), + String::from("Response from Ambi backend."), None, Some(response), std::thread::current().id(), @@ -223,7 +223,7 @@ pub fn run(cli: &Cli) { } Err(error) => { Output::new( - String::from("Response error from Ambi backend"), + String::from("Response error from Ambi backend."), Some(error), None, std::thread::current().id(), From 2392f43a22bd941991e71eb28961e1c39decbc44 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Thu, 7 Apr 2022 18:48:29 -0700 Subject: [PATCH 10/11] define local Error struct --- src/lib.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7e0d502..92ed9d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ impl Reading { #[derive(Debug)] struct Output { description: String, - error: Option, + error: Option, data: Option, thread_id: ThreadId, debug: bool @@ -73,7 +73,7 @@ struct Output { impl Output { pub fn new( description: String, - error: Option, + error: Option, data: Option, thread_id: ThreadId, debug: bool @@ -120,11 +120,24 @@ impl fmt::Display for Output { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_error() { let error = self.error.as_ref().unwrap(); - write!(f, "{} Status: {:?}, Thread ID: {:?}", self.description, error.status(), self.thread_id) + write!(f, "{} Status: {:?}, Thread ID: {:?}", self.description, error.inner.status(), self.thread_id) } else { let response = self.data.as_ref().unwrap(); let status = response.status().as_u16(); - write!(f, "{} Status: {}, Thread ID: {:?}",self.description, status, self.thread_id) + write!(f, "{} Status: {}, Thread ID: {:?}", self.description, status, self.thread_id) + } + } +} + +#[derive(Debug)] +struct Error { + inner: reqwest::Error +} + +impl From for Error { + fn from(error: reqwest::Error) -> Self { + Error { + inner: error } } } @@ -224,7 +237,7 @@ pub fn run(cli: &Cli) { Err(error) => { Output::new( String::from("Response error from Ambi backend."), - Some(error), + Some(error.into()), None, std::thread::current().id(), debug From 6c896dffe5f282743353465e3ac91759dfab4627 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 10 May 2022 18:01:02 -0700 Subject: [PATCH 11/11] Add ResponseLog --- src/lib.rs | 205 +++++++++++++++++++++++++++++----------------------- src/main.rs | 8 +- 2 files changed, 117 insertions(+), 96 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 92ed9d8..6ba18f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,18 +3,19 @@ //! //! This file provides for a separation of concerns from main.rs for application //! logic, per the standard Rust pattern. -//! +//! //! See `main.rs` for more details about what this application does. //! //! See the `LICENSE` file for Copyright and license details. -//! +//! +use clap::Parser; use rand::{thread_rng, Rng}; use reqwest::blocking::{Client, Response}; use reqwest::header::CONTENT_TYPE; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::fmt; -use clap::{Parser}; +use std::io::Write; use std::thread::{spawn, JoinHandle, ThreadId}; /// Defines the Ambi Mock Client command line interface as a struct @@ -22,8 +23,12 @@ use std::thread::{spawn, JoinHandle, ThreadId}; #[clap(name = "Ambi Mock Client")] #[clap(author = "Rust Never Sleeps community (https://github.com/Jim-Hodapp-Coaching/)")] #[clap(version = "0.1.0")] -#[clap(about = "Provides a mock Ambi client that emulates real sensor hardware such as an Edge client.")] -#[clap(long_about = "This application emulates a real set of hardware sensors that can report on environmental conditions such as temperature, pressure, humidity, etc.")] +#[clap( + about = "Provides a mock Ambi client that emulates real sensor hardware such as an Edge client." +)] +#[clap( + long_about = "This application emulates a real set of hardware sensors that can report on environmental conditions such as temperature, pressure, humidity, etc." +)] pub struct Cli { /// Turns verbose console debug output on #[clap(short, long)] @@ -31,16 +36,16 @@ pub struct Cli { /// Make number of concurrent requests #[clap(short, long, default_value_t = 1)] - pub int: u16 + pub int: u16, } #[derive(Serialize, Deserialize)] struct Reading { - temperature: String, - humidity: String, - pressure: String, - dust_concentration: String, - air_purity: String + temperature: String, + humidity: String, + pressure: String, + dust_concentration: String, + air_purity: String, } impl Reading { @@ -49,95 +54,88 @@ impl Reading { humidity: String, pressure: String, dust_concentration: String, - air_purity: String + air_purity: String, ) -> Reading { Reading { temperature, humidity, pressure, dust_concentration, - air_purity + air_purity, } } } +#[derive(Debug, PartialEq)] +struct ResponseLog<'a, W: Write> { + debug: bool, + writer: &'a mut W, +} + +impl<'a, W: Write> ResponseLog<'a, W> { + pub fn new(debug: bool, writer: &'a mut W) -> Self { + ResponseLog { + debug: debug, + writer: writer, + } + } + + pub fn print(&mut self, result: R) { + let result_string = if self.debug { + format!("{:?}", result) + } else { + format!("{}", result) + }; + self.writer.write_all(result_string.as_bytes()).unwrap(); + } +} + #[derive(Debug)] -struct Output { +struct RequestResult { description: String, - error: Option, + error: Option, data: Option, thread_id: ThreadId, - debug: bool - } +} -impl Output { +impl RequestResult { pub fn new( description: String, - error: Option, + error: Option, data: Option, thread_id: ThreadId, - debug: bool - ) -> Output { - Output { + ) -> RequestResult { + RequestResult { description, error, data, thread_id, - debug } } pub fn is_error(&self) -> bool { - self.error.is_some() + self.error.is_some() } - - pub fn print(&self) { - if self.is_error() { - self.print_to_stderr() - } else { - self.print_to_stdout() - } - } - - fn print_to_stderr(&self) { - if self.debug { - eprintln!("{:#?}", self) - } else { - eprintln!("{}", self) - } - } - - fn print_to_stdout(&self) { - if self.debug { - println!("{:#?}", self) - } else { - println!("{}", self) - } - } } -impl fmt::Display for Output { +impl fmt::Display for RequestResult { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_error() { let error = self.error.as_ref().unwrap(); - write!(f, "{} Status: {:?}, Thread ID: {:?}", self.description, error.inner.status(), self.thread_id) + let status = error.status(); + write!( + f, + "{} Status: {:?}, Thread ID: {:?}", + self.description, status, self.thread_id + ) } else { let response = self.data.as_ref().unwrap(); let status = response.status().as_u16(); - write!(f, "{} Status: {}, Thread ID: {:?}", self.description, status, self.thread_id) - } - } -} - -#[derive(Debug)] -struct Error { - inner: reqwest::Error -} - -impl From for Error { - fn from(error: reqwest::Error) -> Self { - Error { - inner: error + write!( + f, + "{} Status: {}, Thread ID: {:?}", + self.description, status, self.thread_id + ) } } } @@ -147,7 +145,7 @@ enum AirPurity { Dangerous, High, Low, - FreshAir + FreshAir, } impl AirPurity { @@ -169,7 +167,7 @@ impl fmt::Display for AirPurity { AirPurity::Low => write!(f, "Fresh Air"), AirPurity::High => write!(f, "Low Pollution"), AirPurity::Dangerous => write!(f, "High Pollution"), - AirPurity::FreshAir => write!(f, "Dangerous Pollution") + AirPurity::FreshAir => write!(f, "Dangerous Pollution"), } } } @@ -223,28 +221,26 @@ pub fn run(cli: &Cli) { let mut handlers: Vec> = vec![]; let debug: bool = cli.debug; for _ in 0..cli.int { - let handler = spawn(move || - match send_request(URL, Client::new()) { - Ok(response) => { - Output::new( - String::from("Response from Ambi backend."), - None, - Some(response), - std::thread::current().id(), - debug - ).print(); - } - Err(error) => { - Output::new( + let handler = spawn(move || match send_request(URL, Client::new()) { + Ok(response) => { + let result = RequestResult::new( + String::from("Response from Ambi backend."), + None, + Some(response), + std::thread::current().id(), + ); + ResponseLog::new(debug, &mut std::io::stdout()).print(result) + } + Err(error) => { + let result = RequestResult::new( String::from("Response error from Ambi backend."), - Some(error.into()), + Some(error), None, std::thread::current().id(), - debug - ).print(); - } + ); + ResponseLog::new(debug, &mut std::io::stderr()).print(result) } - ); + }); handlers.push(handler); } @@ -261,18 +257,18 @@ mod tests { #[test] fn random_gen_humidity_returns_correctly_formatted_humidity_data() { - let result = random_gen_humidity(); - let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); + let result = random_gen_humidity(); + let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); - assert!(regex.is_match(&result)); + assert!(regex.is_match(&result)); } - + #[test] fn random_gen_temperature_returns_correctly_formatted_humidity_data() { - let result = random_gen_temperature(); - let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); + let result = random_gen_temperature(); + let regex = Regex::new(r"\d{1,2}.\d{1}").unwrap(); - assert!(regex.is_match(&result)); + assert!(regex.is_match(&result)); } #[test] @@ -302,4 +298,29 @@ mod tests { assert_eq!(AirPurity::from_value(high), AirPurity::High); assert_eq!(AirPurity::from_value(dangerous), AirPurity::Dangerous); } + + #[test] + fn response_log_new_returns_response_log() { + let mut writer1 = vec![0, 0, 0]; + let mut writer2 = writer1.clone(); + assert_eq!( + ResponseLog::new(true, &mut writer1), + ResponseLog { + debug: true, + writer: &mut writer2 + } + ) + } + + #[test] + fn response_log_prints() { + let mut writer = Vec::new(); + let result = "123"; + let mut response_log = ResponseLog::new(false, &mut writer); + let expected = result.as_bytes().to_vec(); + + response_log.print(result); + + assert_eq!(writer, expected) + } } diff --git a/src/main.rs b/src/main.rs index 99efe11..9e45ef2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,16 @@ -//! # Provides a mock Ambi client that emulates real sensor hardware such as +//! # Provides a mock Ambi client that emulates real sensor hardware such as //! an Edge client. //! //! This application emulates a real set of hardware sensors that can report on //! environmental conditions such as temperature, pressure, humidity, etc. -//! +//! //! Please see the `ambi` repository for the web backend that this client connects to //! and the `edge-rs` repository for what this client is emulating. //! //! See the `LICENSE` file for Copyright and license details. -//! +//! -use clap::{Parser}; +use clap::Parser; // Internal library namespace for separation of app logic use ambi_mock_client;