use crate::std::borrow::Cow;
use super::{UriMatchError, UriMatchReplace};
use crate::uri::Uri;
macro_rules! impl_uri_match_replace_on_slice {
() => {
fn match_replace_uri<'a>(
&self,
mut uri: Cow<'a, Uri>,
) -> Result<Cow<'a, Uri>, UriMatchError<'a>> {
for rule in self.iter() {
match rule.match_replace_uri(uri) {
Ok(new_uri) => {
return Ok(new_uri);
}
Err(UriMatchError::NoMatch(original_uri)) => uri = original_uri,
Err(UriMatchError::Unexpected(err)) => {
return Err(UriMatchError::Unexpected(err));
}
}
}
Err(UriMatchError::NoMatch(uri))
}
};
}
impl<R: UriMatchReplace, const N: usize> UriMatchReplace for [R; N] {
impl_uri_match_replace_on_slice!();
}
impl<R: UriMatchReplace> UriMatchReplace for &[R] {
impl_uri_match_replace_on_slice!();
}
impl<R: UriMatchReplace> UriMatchReplace for Vec<R> {
impl_uri_match_replace_on_slice!();
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use super::*;
use crate::http::uri::UriMatchReplaceRule;
fn rule(ptn: &'static str, fmt: &'static str) -> UriMatchReplaceRule {
UriMatchReplaceRule::try_new(ptn, fmt).unwrap_or_else(|err| {
panic!("valid rule expected for ptn={ptn:?}, fmt={fmt:?}; err = {err}")
})
}
fn uri(s: &str) -> Uri {
Uri::from_str(s).unwrap_or_else(|err| panic!("valid URI expected: {s:?}; err = {err}"))
}
fn match_replace_uri_to_test_option_str(
result: Result<Cow<'_, Uri>, UriMatchError<'_>>,
) -> Option<String> {
match result {
Ok(uri) => Some(uri.to_string()),
Err(UriMatchError::NoMatch(_)) => None,
Err(UriMatchError::Unexpected(err)) => {
panic!("unexpected match replace uri error: {err}")
}
}
}
fn apply_multiple_views(slice: &[UriMatchReplaceRule], input: &str) -> [Option<String>; 2] {
let u = uri(input);
let vec_rules = slice.to_vec();
let out_slice = match_replace_uri_to_test_option_str(UriMatchReplace::match_replace_uri(
&slice,
Cow::Borrowed(&u),
));
let out_vec = match_replace_uri_to_test_option_str(UriMatchReplace::match_replace_uri(
&vec_rules,
Cow::Borrowed(&u),
));
[out_slice, out_vec]
}
fn expect_all_views_eq(rules: &[UriMatchReplaceRule], input: &str, want: Option<&str>) {
let got = apply_multiple_views(rules, input);
let want = want.map(str::to_owned);
for (i, g) in got.into_iter().enumerate() {
assert_eq!(g, want, "container idx {i} wrong result for input: {input}");
}
}
#[test]
fn picks_first_matching_rule_in_iteration_order() {
let r1 = rule("https://example.com/*", "https://example.com/a");
let r2 = rule("https://example.com/*", "https://example.com/b");
expect_all_views_eq(
&[r1, r2],
"https://example.com/x",
Some("https://example.com/a"),
);
}
#[test]
fn aggregates_include_query_across_rules_for_uri_slice_but_not_match() {
let r1 = rule("https://example.com/path", "https://example.com/untouched");
let r2 = rule(
"https://example.com/path\\?*", "https://example.com/rewrite?$1",
);
expect_all_views_eq(
&[r1, r2],
"https://example.com/path?x=1&y=2",
Some("https://example.com/untouched"),
);
}
#[test]
fn non_match_returns_none_for_all_containers() {
let r = rule("http://only*", "https://$1");
expect_all_views_eq(&[r], "https://example.com", None);
}
#[test]
fn tiny_fuzz_consistent_with_single_rule_behavior() {
let single = rule("http://*", "https://$1");
let set = vec![
rule("ftp://*", "https://$1"),
rule("http://host\\?*", "https://host?$1"), single.clone(),
];
let hosts = ["a.com", "host", "x.y"];
let paths = ["", "/p", "/p/q"];
let queries = ["", "?k=v", "?x=1&y=2"];
for h in hosts {
for p in paths {
for q in queries {
let http_in = format!("http://{h}{p}{q}");
let ref_out = single
.match_replace_uri(Cow::Borrowed(&uri(&http_in)))
.map(|u| u.to_string())
.expect("single rule always matches http");
for (idx, got) in apply_multiple_views(&set[..], &http_in)
.into_iter()
.enumerate()
{
let got = got
.unwrap_or_else(|| panic!("set {set:?} should match http: {http_in}"));
assert_eq!(got, ref_out, "container idx {idx} for input {http_in}");
}
}
}
}
for input in ["https://a.com", "https://host/p?q=1"] {
for (idx, got) in apply_multiple_views(&set[..], input)
.into_iter()
.enumerate()
{
assert!(
got.is_none(),
"container idx {idx} should not match: {input}"
);
}
}
}
}