implement random url generator

This commit is contained in:
2025-09-01 19:46:17 +09:00
parent f441abd5eb
commit 1b661f9848
8 changed files with 214 additions and 2 deletions

91
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,4 @@
pub mod simple;
pub mod random;
pub use simple::{SimpleHttpRequest, SimpleHttpSward};

View 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 },
}

View File

@@ -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,

View File

@@ -1 +1,6 @@
pub mod http;
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
#![deny(clippy::expect_used)]
pub mod http;
pub mod utils;

View File