From ca89818c5fef291c121ffce51da3baf47c02828b Mon Sep 17 00:00:00 2001 From: Matz Hilven Date: Sat, 16 Dec 2023 14:34:09 +0100 Subject: [PATCH] day 15 --- Cargo.lock | 49 ++++++++++++++++ Cargo.toml | 3 + src/days/fifteen/mod.rs | 123 ++++++++++++++++++++++++++++++++++++++++ src/days/mod.rs | 2 + 4 files changed, 177 insertions(+) create mode 100644 src/days/fifteen/mod.rs diff --git a/Cargo.lock b/Cargo.lock index bf340e5..4878335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -583,11 +583,13 @@ dependencies = [ "actix-web", "base64", "chrono", + "emojis", "fancy-regex", "image", "reqwest", "serde", "serde_json", + "sha256", "shuttle-actix-web", "shuttle-runtime", "shuttle-shared-db", @@ -595,6 +597,7 @@ dependencies = [ "tempfile", "tokio", "ulid", + "unicode-segmentation", "uuid", ] @@ -920,6 +923,15 @@ dependencies = [ "serde", ] +[[package]] +name = "emojis" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee61eb945bff65ee7d19d157d39c67c33290ff0742907413fd5eefd29edc979" +dependencies = [ + "phf", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -2093,6 +2105,24 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -2652,6 +2682,19 @@ dependencies = [ "digest", ] +[[package]] +name = "sha256" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2849,6 +2892,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 25eeb79..0a6fc6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,13 @@ actix-multipart = "0.6.1" actix-web = "4.3.1" base64 = "0.21.5" chrono = "0.4.31" +emojis = "0.6.1" fancy-regex = "0.12.0" image = "0.24.7" reqwest = "0.11.22" serde = "1.0.193" serde_json = "1.0.108" +sha256 = "1.4.0" shuttle-actix-web = "0.35.1" shuttle-runtime = { version = "0.35.1", default-features = false } shuttle-shared-db = { version = "0.35.1", features = ["postgres"] } @@ -21,4 +23,5 @@ sqlx = { version = "0.7.3", features = ["runtime-tokio-native-tls", "postgres"] tempfile = "3.8.1" tokio = "1.26.0" ulid = { version = "1.1.0", features = ["serde", "uuid"] } +unicode-segmentation = "1.10.1" uuid = "1.6.1" diff --git a/src/days/fifteen/mod.rs b/src/days/fifteen/mod.rs new file mode 100644 index 0000000..36814a5 --- /dev/null +++ b/src/days/fifteen/mod.rs @@ -0,0 +1,123 @@ +use std::str::FromStr; + +use actix_web::{HttpResponse, post, Responder, web}; +use serde_json::json; +use unicode_segmentation::UnicodeSegmentation; + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service(part_1); + cfg.service(part_2); +} + +#[derive(serde::Deserialize)] +struct Content { + input: String, +} + +#[post("/15/nice")] +async fn part_1( + content: web::Json +) -> impl Responder { + let password = content.input.clone(); + + let re = fancy_regex::Regex::new(r"^(?=.*[aeiouy].*[aeiouy].*[aeiouy])(?=.*([a-z])\1)(?!.*(?:ab|cd|pq|xy)).*$").unwrap(); + + return match re.is_match(&password).unwrap() { + true => HttpResponse::Ok().json(json!({"result": "nice"})), + false => HttpResponse::BadRequest().json(json!({"result": "naughty"})), + }; +} + +fn check_rules(password: String) -> Option { + // Rule 1: must be at least 8 characters long + if password.len() < 8 { + return Some(1); + } + + // Rule 2: must contain uppercase letters, lowercase letters, and digits + let re = fancy_regex::Regex::new(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$").unwrap(); + if !re.is_match(&password).unwrap() { + return Some(2); + } + // Rule 3: must contain at least 5 digits + let re = fancy_regex::Regex::new(r"^(.*\d.*){5,}$").unwrap(); + if !re.is_match(&password).unwrap() { + return Some(3); + } + // Rule 4: all integers (sequences of consecutive digits) in the string must add up to 2023 + let re = fancy_regex::Regex::new(r"\d+").unwrap(); + let mut sum = 0; + for mat in re.find_iter(password.as_str()) { + sum += i32::from_str(mat.unwrap().as_str()).unwrap(); + } + + if sum != 2023 { + return Some(4); + } + + // Rule 5: must contain the letters j, o, and y in that order and in no other order + // get index of j, o, and y, if not present return 5 don't unwrap + let j = match password.find("j") { + Some(i) => i, + None => return Some(5), + }; + let o = match password.find("o") { + Some(i) => i, + None => return Some(5), + }; + let y = match password.find("y") { + Some(i) => i, + None => return Some(5), + }; + let re = fancy_regex::Regex::new(r"j.+o.+y").unwrap(); + if !re.is_match(&password).unwrap() { + return Some(5); + } + + if j > o || o > y { + return Some(5); + } + // Rule 6: must contain a letter that repeats with exactly one other letter between them (like xyx) + let re = fancy_regex::Regex::new(r"([a-zA-Z]).\1").unwrap(); + if !re.is_match(&password).unwrap() { + return Some(6); + } + // Rule 7: must contain at least one unicode character in the range [U+2980, U+2BFF] + if !password.chars().any(|c| ('\u{2980}'..='\u{2BFF}').contains(&c)) { + return Some(7); + } + // Rule 8: must contain at least one emoji + + if !password.graphemes(true).any(|el| emojis::get(el).is_some()) { + return Some(8); + } + + // Rule 9: the hexadecimal representation of the sha256 hash of the string must end with an 'a' + if !sha256::digest(password).ends_with("a") { + return Some(9); + } + + None +} + +#[post("/15/game")] +async fn part_2( + content: web::Json +) -> impl Responder { + let password = content.input.clone(); + return match check_rules(password) { + Some(i) => match i { + 1 => HttpResponse::BadRequest().json(json!({"result": "naughty", "reason": "8 chars"})), + 2 => HttpResponse::BadRequest().json(json!({"result": "naughty", "reason": "more types of chars"})), + 3 => HttpResponse::BadRequest().json(json!({"result": "naughty", "reason": "55555"})), + 4 => HttpResponse::BadRequest().json(json!({"result": "naughty", "reason": "math is hard"})), + 5 => HttpResponse::NotAcceptable().json(json!({"result": "naughty", "reason": "not joyful enough"})), + 6 => HttpResponse::UnavailableForLegalReasons().json(json!({"result": "naughty", "reason": "illegal: no sandwich"})), + 7 => HttpResponse::RangeNotSatisfiable().json(json!({"result": "naughty", "reason": "outranged"})), + 8 => HttpResponse::UpgradeRequired().json(json!({"result": "naughty", "reason": "😳"})), + 9 => HttpResponse::ImATeapot().json(json!({"result": "naughty", "reason": "not a coffee brewer"})), + _ => HttpResponse::InternalServerError().json(json!({"result": "naughty", "reason": "unknown"})), + } + None => HttpResponse::Ok().json(json!({"result": "nice", "reason": "that's a nice password"})), + }; +} diff --git a/src/days/mod.rs b/src/days/mod.rs index 15eeabb..4671fbc 100644 --- a/src/days/mod.rs +++ b/src/days/mod.rs @@ -9,6 +9,7 @@ pub fn configure(cfg: &mut actix_web::web::ServiceConfig) { cfg.configure(eleven::configure); cfg.configure(thirteen::configure); cfg.configure(fourteen::configure); + cfg.configure(fifteen::configure); } pub mod four; @@ -22,3 +23,4 @@ pub mod eleven; pub mod twelve; pub mod thirteen; pub mod fourteen; +pub mod fifteen;