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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
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]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.30"
|
version = "0.4.30"
|
||||||
@@ -81,6 +90,15 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castaway"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.34"
|
version = "1.2.34"
|
||||||
@@ -96,6 +114,21 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
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]]
|
[[package]]
|
||||||
name = "compression-codecs"
|
name = "compression-codecs"
|
||||||
version = "0.4.30"
|
version = "0.4.30"
|
||||||
@@ -976,6 +1009,35 @@ dependencies = [
|
|||||||
"bitflags",
|
"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]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.23"
|
version = "0.12.23"
|
||||||
@@ -1226,6 +1288,12 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@@ -1297,6 +1365,26 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.42"
|
version = "0.3.42"
|
||||||
@@ -1476,8 +1564,11 @@ name = "ubw-sward"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"compact_str",
|
||||||
"rand",
|
"rand",
|
||||||
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"url",
|
"url",
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ rand = "0.9"
|
|||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
|
compact_str = { version = "0.9.0", features = ["bytes"] }
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
bytes = "1.10"
|
bytes = "1.10"
|
||||||
@@ -9,4 +9,7 @@ tower = { workspace = true }
|
|||||||
reqwest = {workspace = true}
|
reqwest = {workspace = true}
|
||||||
url = {workspace = true}
|
url = {workspace = true}
|
||||||
rand = {workspace = true}
|
rand = {workspace = true}
|
||||||
|
thiserror = {workspace = true}
|
||||||
|
compact_str = {workspace = true}
|
||||||
bytes = {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;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
/// A simple http sward that sends one request per call.
|
||||||
pub struct SimpleHttpSward {
|
pub struct SimpleHttpSward {
|
||||||
client: Client,
|
client: Client,
|
||||||
method: Method,
|
method: Method,
|
||||||
@@ -1 +1,6 @@
|
|||||||
|
#![deny(clippy::unwrap_used)]
|
||||||
|
#![deny(clippy::panic)]
|
||||||
|
#![deny(clippy::expect_used)]
|
||||||
|
|
||||||
pub mod http;
|
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