implement random url generator
This commit is contained in:
91
Cargo.lock
generated
91
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
@@ -9,4 +9,7 @@ tower = { workspace = true }
|
||||
reqwest = {workspace = true}
|
||||
url = {workspace = true}
|
||||
rand = {workspace = true}
|
||||
bytes = {workspace = true}
|
||||
thiserror = {workspace = true}
|
||||
compact_str = {workspace = true}
|
||||
bytes = {workspace = true}
|
||||
regex = "1.11"
|
||||
4
ubw-sward/src/http/mod.rs
Normal file
4
ubw-sward/src/http/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod simple;
|
||||
pub mod random;
|
||||
|
||||
pub use simple::{SimpleHttpRequest, SimpleHttpSward};
|
||||
107
ubw-sward/src/http/random.rs
Normal file
107
ubw-sward/src/http/random.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use compact_str::CompactString;
|
||||
use rand::seq::IteratorRandom;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RandomUrlGenerator<Rng: rand::Rng> {
|
||||
random_engine: Rng,
|
||||
random_parts: Box<[RegexPart]>,
|
||||
}
|
||||
|
||||
impl<Rng: rand::Rng> RandomUrlGenerator<Rng> {
|
||||
pub fn new(random_engine: Rng, template: &str) -> Result<Self, RandomUrlGeneratorBuildError> {
|
||||
let re = Regex::new(r"\[([^\]]+)\](?:\{(\d+)\})?")?;
|
||||
let mut parts: Vec<RegexPart> = 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::<usize>()
|
||||
.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::<CompactString>();
|
||||
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 },
|
||||
}
|
||||
@@ -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,
|
||||
@@ -1 +1,6 @@
|
||||
pub mod http;
|
||||
#![deny(clippy::unwrap_used)]
|
||||
#![deny(clippy::panic)]
|
||||
#![deny(clippy::expect_used)]
|
||||
|
||||
pub mod http;
|
||||
pub mod utils;
|
||||
0
ubw-sward/src/utils/mod.rs
Normal file
0
ubw-sward/src/utils/mod.rs
Normal file
Reference in New Issue
Block a user