diff --git a/cli-client/Cargo.lock b/cli-client/Cargo.lock index 80b0975..542a716 100644 --- a/cli-client/Cargo.lock +++ b/cli-client/Cargo.lock @@ -265,6 +265,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "comfy-table" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +dependencies = [ + "crossterm", + "strum", + "strum_macros", + "unicode-width", +] + [[package]] name = "config" version = "0.14.0" @@ -364,6 +376,28 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "libc", + "parking_lot", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -1400,6 +1434,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.18" @@ -1559,6 +1599,7 @@ dependencies = [ "assert_fs", "async-trait", "clap", + "comfy-table", "config", "openssl", "predicates", @@ -1575,6 +1616,25 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.74" @@ -1832,6 +1892,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "url" version = "2.5.2" @@ -1971,6 +2037,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -1980,6 +2062,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/cli-client/Cargo.toml b/cli-client/Cargo.toml index 53a3d93..ae45791 100644 --- a/cli-client/Cargo.toml +++ b/cli-client/Cargo.toml @@ -19,6 +19,7 @@ config = "0.14.0" serde_json = "1.0" openssl = { version = "0.10", features = ["vendored"] } async-trait = "0.1.81" +comfy-table = "7.1.1" [dev-dependencies] assert_cmd = "2.0.16" diff --git a/cli-client/src/cli.rs b/cli-client/src/cli.rs index 9ddcf7f..7c9ae37 100644 --- a/cli-client/src/cli.rs +++ b/cli-client/src/cli.rs @@ -3,7 +3,10 @@ use clap::{Parser, Subcommand}; #[derive(Subcommand, Clone, Debug)] pub enum Command { #[command(about = "Add a strike", alias = "s")] - Strike { name: String }, + Strike { + #[arg(help = "Name of the tarnished", value_parser = parse_username)] + name: String, + }, #[command(about = "List all strikes")] Ls, #[command(about = "Clear strikes", alias = "c")] @@ -26,3 +29,15 @@ pub struct Cli { #[command(subcommand)] pub command: Option, } + +fn parse_username(s: &str) -> Result> { + if s.is_empty() { + return Err("Username cannot be empty".into()); + } + + if s.len() > 20 { + return Err("Username cannot be longer than 20 characters".into()); + } + + Ok(s.to_lowercase()) +} diff --git a/cli-client/src/main.rs b/cli-client/src/main.rs index 46bc0d0..86d41d1 100644 --- a/cli-client/src/main.rs +++ b/cli-client/src/main.rs @@ -5,6 +5,7 @@ use strikes::clients::local_client::LocalClient; use strikes::clients::remote_client::RemoteClient; use strikes::configuration::{get_configuration, Settings}; use strikes::output::{print_as_table, print_strikes}; +use strikes::tarnished::Tarnished; #[tokio::main] async fn main() { @@ -18,7 +19,7 @@ async fn main() { Err(err) => eprintln!("Failed to add strike: {}", err), }, Command::Ls => match client.get_tarnished().await { - Ok(tarnished) => print_as_table(tarnished), + Ok(tarnished) => print_as_table(Tarnished::sort_desc_by_strike(tarnished)), Err(err) => eprintln!("Failed to get strikes: {}", err), }, Command::Clear => { diff --git a/cli-client/src/output.rs b/cli-client/src/output.rs index 74c94bd..1eaa400 100644 --- a/cli-client/src/output.rs +++ b/cli-client/src/output.rs @@ -1,4 +1,5 @@ use crate::tarnished::Tarnished; +use comfy_table::Table; pub fn print_as_table(tarnished: Vec) { if tarnished.is_empty() { @@ -6,10 +7,14 @@ pub fn print_as_table(tarnished: Vec) { return; } - println!("{0: <10} | {1: <10} |", "Tarnished", "Strikes"); + let mut table = Table::new(); + table.set_header(vec!["Tarnished", "Strikes"]); + for tarnished in tarnished { - println!("{0: <10} | {1: <10} |", tarnished.name, tarnished.strikes); + table.add_row(vec![tarnished.name, tarnished.strikes.to_string()]); } + + println!("{table}"); } pub fn print_strikes(name: &str, strikes: u8) { diff --git a/cli-client/tests/cli.rs b/cli-client/tests/cli.rs index 1288fab..fbb46d0 100644 --- a/cli-client/tests/cli.rs +++ b/cli-client/tests/cli.rs @@ -56,9 +56,13 @@ fn it_should_list_strikes_in_descending_order() -> Result<(), Box Result<(), Box> { let mut cmd = Command::cargo_bin("strikes")?; cmd.arg("--config-path").arg(config_file.path()).arg("ls"); - cmd.assert() - .success() - .stdout("Tarnished | Strikes |\nguenther | 1 |\n"); + let expected_output = "+-----------+---------+\n\ + | Tarnished | Strikes |\n\ + +=====================+\n\ + | guenther | 1 |\n\ + +-----------+---------+\n"; + + cmd.assert().success().stdout(expected_output); let mut cmd = Command::cargo_bin("strikes")?; cmd.arg("--config-path") @@ -106,3 +114,28 @@ fn it_should_clear_all_strikes() -> Result<(), Box> { Ok(()) } + +#[test] +fn it_should_reject_usernames_longer_than_20_characters() -> Result<(), Box> +{ + let db_file = assert_fs::NamedTempFile::new("./tests/fixtures/db.json")?; + let config_file = assert_fs::NamedTempFile::new("./tests/fixtures/configuration.yaml")?; + config_file.write_str( + format!( + "{{\"local\": {{\"db_path\": \"{}\"}}}}", + db_file.path().to_str().unwrap() + ) + .as_str(), + )?; + + let mut cmd = Command::cargo_bin("strikes")?; + cmd.arg("--config-path") + .arg(config_file.path()) + .arg("strike") + .arg("guentherguentherguenther"); + cmd.assert().failure().stderr(predicate::str::contains( + "Username cannot be longer than 20 characters", + )); + + Ok(()) +} diff --git a/infrastructure/lambdas/src/put_strike.rs b/infrastructure/lambdas/src/put_strike.rs index a26d063..14c228c 100644 --- a/infrastructure/lambdas/src/put_strike.rs +++ b/infrastructure/lambdas/src/put_strike.rs @@ -16,6 +16,13 @@ pub async fn function_handler(request: Request) -> Result, Error> match user { Some(username) => { + if username.is_empty() || username.len() > 20 { + return Ok(Response::builder() + .status(400) + .body(Body::Text("Invalid username".to_string())) + .expect("Failed to render response")); + } + let strike_count = increment_strikes(username, "Strikes", &client).await?; Ok(Response::builder() .status(200)