diff --git a/Cargo.lock b/Cargo.lock index 7eecab7..eee5b22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "async-compression" version = "0.4.30" @@ -81,6 +90,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.34" @@ -96,6 +114,21 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "bytes", + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "compression-codecs" version = "0.4.30" @@ -976,6 +1009,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + [[package]] name = "reqwest" version = "0.12.23" @@ -1226,6 +1288,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.6.1" @@ -1297,6 +1365,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.42" @@ -1476,8 +1564,11 @@ name = "ubw-sward" version = "0.1.0" dependencies = [ "bytes", + "compact_str", "rand", + "regex", "reqwest", + "thiserror", "tokio", "tower", "url", diff --git a/Cargo.toml b/Cargo.toml index f331cb9..f4d99db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,6 @@ rand = "0.9" thiserror = "2.0" anyhow = "1.0" url = "2.5" +compact_str = { version = "0.9.0", features = ["bytes"] } time = "0.3" bytes = "1.10" \ No newline at end of file diff --git a/ubw-sward/Cargo.toml b/ubw-sward/Cargo.toml index 60f81de..391af72 100644 --- a/ubw-sward/Cargo.toml +++ b/ubw-sward/Cargo.toml @@ -9,4 +9,7 @@ tower = { workspace = true } reqwest = {workspace = true} url = {workspace = true} rand = {workspace = true} -bytes = {workspace = true} \ No newline at end of file +thiserror = {workspace = true} +compact_str = {workspace = true} +bytes = {workspace = true} +regex = "1.11" \ No newline at end of file diff --git a/ubw-sward/src/http/mod.rs b/ubw-sward/src/http/mod.rs new file mode 100644 index 0000000..d9e3e18 --- /dev/null +++ b/ubw-sward/src/http/mod.rs @@ -0,0 +1,4 @@ +pub mod simple; +pub mod random; + +pub use simple::{SimpleHttpRequest, SimpleHttpSward}; diff --git a/ubw-sward/src/http/random.rs b/ubw-sward/src/http/random.rs new file mode 100644 index 0000000..958c358 --- /dev/null +++ b/ubw-sward/src/http/random.rs @@ -0,0 +1,107 @@ +use compact_str::CompactString; +use rand::seq::IteratorRandom; +use regex::Regex; + +#[derive(Clone)] +pub struct RandomUrlGenerator { + random_engine: Rng, + random_parts: Box<[RegexPart]>, +} + +impl RandomUrlGenerator { + pub fn new(random_engine: Rng, template: &str) -> Result { + let re = Regex::new(r"\[([^\]]+)\](?:\{(\d+)\})?")?; + let mut parts: Vec = Vec::new(); + let mut last_end = 0; + + for caps in re.captures_iter(template) { + let m = caps + .get(0) + .ok_or(RandomUrlGeneratorBuildError::SyntaxError)?; + if m.start() > last_end { + parts.push(RegexPart::Literal(template[last_end..m.start()].into())); + } + let expr = caps + .get(1) + .ok_or(RandomUrlGeneratorBuildError::SyntaxError)? + .as_str(); + let count = caps + .get(2) + .map(|m| { + m.as_str() + .parse::() + .map_err(|_| RandomUrlGeneratorBuildError::SyntaxError) + }) + .transpose()? + .unwrap_or(1); + let mut chars = Vec::new(); + let mut chars_iter = expr.chars().peekable(); + while let Some(c) = chars_iter.next() { + if let Some(&dash) = chars_iter.peek() + && dash == '-' + { + chars_iter.next(); + if let Some(end) = chars_iter.next() { + for ch in c..=end { + chars.push(ch); + } + continue; + } + } + chars.push(c); + } + let chars = chars.into_boxed_slice(); + parts.push(RegexPart::RandomChars { chars, count }); + last_end = m.end(); + } + if last_end < template.len() { + parts.push(RegexPart::Literal(template[last_end..].into())); + } + Ok(Self { + random_engine, + random_parts: parts.into_boxed_slice(), + }) + } + + fn expected_length(&self) -> usize { + self.random_parts.iter().fold(0, |acc, part| match part { + RegexPart::Literal(s) => acc + s.len(), + RegexPart::RandomChars { count, .. } => acc + *count, + }) + } + + pub fn generate(&mut self) -> CompactString { + self.random_parts.iter().fold( + CompactString::with_capacity(self.expected_length()), + |mut acc, part| { + match &part { + RegexPart::Literal(s) => { + acc.push_str(s); + } + RegexPart::RandomChars { chars, count } => { + let next = (0..*count) + .filter_map(|_| chars.iter().choose(&mut self.random_engine)) + .collect::(); + acc.push_str(&next); + } + }; + acc + }, + ) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RandomUrlGeneratorBuildError { + #[error("Invalid regex: {0}")] + InvalidRegex(#[from] regex::Error), + + #[error("Syntax error in template.")] + SyntaxError, +} + +#[derive(Clone)] +enum RegexPart { + Literal(CompactString), + RandomChars { chars: Box<[char]>, count: usize }, +} diff --git a/ubw-sward/src/http.rs b/ubw-sward/src/http/simple.rs similarity index 95% rename from ubw-sward/src/http.rs rename to ubw-sward/src/http/simple.rs index 5601062..b0938a2 100644 --- a/ubw-sward/src/http.rs +++ b/ubw-sward/src/http/simple.rs @@ -7,6 +7,7 @@ use tower::Service; use url::Url; #[derive(Clone)] +/// A simple http sward that sends one request per call. pub struct SimpleHttpSward { client: Client, method: Method, diff --git a/ubw-sward/src/lib.rs b/ubw-sward/src/lib.rs index e05256f..bbc265b 100644 --- a/ubw-sward/src/lib.rs +++ b/ubw-sward/src/lib.rs @@ -1 +1,6 @@ -pub mod http; \ No newline at end of file +#![deny(clippy::unwrap_used)] +#![deny(clippy::panic)] +#![deny(clippy::expect_used)] + +pub mod http; +pub mod utils; \ No newline at end of file diff --git a/ubw-sward/src/utils/mod.rs b/ubw-sward/src/utils/mod.rs new file mode 100644 index 0000000..e69de29