mod components;
#[macro_use]
mod utils;
#[cfg(feature = "alloc")]
mod resolve_refimpl;
use iri_string::format::write_to_slice;
#[cfg(feature = "alloc")]
use iri_string::format::ToDedicatedString;
use iri_string::resolve::FixedBaseResolver;
use iri_string::types::*;
#[cfg(feature = "alloc")]
use self::resolve_refimpl::resolve as resolve_refimpl;
#[allow(clippy::type_complexity)]
const TEST_CASES: &[(&str, &[(&str, &str, Option<&str>)])] = &[
("scheme:///a/b/c/./../../", &[("g", "scheme:///a/g", None)]),
("scheme:///a/b/c/./../", &[("../g", "scheme:///a/g", None)]),
("scheme:///a/b/c/./", &[("../../g", "scheme:///a/g", None)]),
("scheme:///a/b/c/", &[("./../../g", "scheme:///a/g", None)]),
("scheme:///a/b/", &[("c/./../../g", "scheme:///a/g", None)]),
("scheme:///a/", &[("b/c/./../../g", "scheme:///a/g", None)]),
("scheme:///", &[("a/b/c/./../../g", "scheme:///a/g", None)]),
("scheme:mid/content=5/../", &[("6", "scheme:mid/6", None)]),
("scheme:mid/content=5/", &[("../6", "scheme:mid/6", None)]),
("scheme:mid/", &[("content=5/../6", "scheme:mid/6", None)]),
("scheme:", &[("mid/content=5/../6", "scheme:mid/6", None)]),
(
"http://a/b/c/d;p?q",
&[
("g:h", "g:h", None),
("g", "http://a/b/c/g", None),
("./g", "http://a/b/c/g", None),
("g/", "http://a/b/c/g/", None),
("/g", "http://a/g", None),
("//g", "http://g", None),
("?y", "http://a/b/c/d;p?y", None),
("g?y", "http://a/b/c/g?y", None),
("#s", "http://a/b/c/d;p?q#s", None),
("g#s", "http://a/b/c/g#s", None),
("g?y#s", "http://a/b/c/g?y#s", None),
(";x", "http://a/b/c/;x", None),
("g;x", "http://a/b/c/g;x", None),
("g;x?y#s", "http://a/b/c/g;x?y#s", None),
("", "http://a/b/c/d;p?q", None),
(".", "http://a/b/c/", None),
("./", "http://a/b/c/", None),
("..", "http://a/b/", None),
("../", "http://a/b/", None),
("../g", "http://a/b/g", None),
("../..", "http://a/", None),
("../../", "http://a/", None),
("../../g", "http://a/g", None),
],
),
(
"http://a/b/c/d;p?q",
&[
("../../../g", "http://a/g", None),
("../../../../g", "http://a/g", None),
("/./g", "http://a/g", None),
("/../g", "http://a/g", None),
("g.", "http://a/b/c/g.", None),
(".g", "http://a/b/c/.g", None),
("g..", "http://a/b/c/g..", None),
("..g", "http://a/b/c/..g", None),
("./../g", "http://a/b/g", None),
("./g/.", "http://a/b/c/g/", None),
("g/./h", "http://a/b/c/g/h", None),
("g/../h", "http://a/b/c/h", None),
("g;x=1/./y", "http://a/b/c/g;x=1/y", None),
("g;x=1/../y", "http://a/b/c/y", None),
("g?y/./x", "http://a/b/c/g?y/./x", None),
("g?y/../x", "http://a/b/c/g?y/../x", None),
("g#s/./x", "http://a/b/c/g#s/./x", None),
("g#s/../x", "http://a/b/c/g#s/../x", None),
("http:g", "http:g", None),
],
),
(
"http://a/b/c/d/e/../..",
&[
("", "http://a/b/c/d/e/../..", Some("http://a/b/c/")),
("..", "http://a/b/c/", None),
("../", "http://a/b/c/", None),
(".", "http://a/b/c/d/", None),
("./", "http://a/b/c/d/", None),
(
"?query",
"http://a/b/c/d/e/../..?query",
Some("http://a/b/c/?query"),
),
(
"#frag",
"http://a/b/c/d/e/../..#frag",
Some("http://a/b/c/#frag"),
),
("http://example.com", "http://example.com", None),
("http://example.com/", "http://example.com/", None),
("http://example.com/..", "http://example.com/", None),
("scheme:", "scheme:", None),
("scheme:foo#frag", "scheme:foo#frag", None),
],
),
(
"https://a/b/c",
&[
("", "https://a/b/c", None),
("x/", "https://a/b/x/", None),
("x//", "https://a/b/x//", None),
("x///", "https://a/b/x///", None),
("x//y", "https://a/b/x//y", None),
("x//y/", "https://a/b/x//y/", None),
("x//y//", "https://a/b/x//y//", None),
("x//..//y//", "https://a/b/x//y//", None),
],
),
(
"scheme:a/b/c",
&[
("", "scheme:a/b/c", None),
("x/", "scheme:a/b/x/", None),
("x//", "scheme:a/b/x//", None),
("x///", "scheme:a/b/x///", None),
("x//y", "scheme:a/b/x//y", None),
("x//y/", "scheme:a/b/x//y/", None),
("x//..//y//", "scheme:a/b/x//y//", None),
],
),
(
"scheme:a",
&[
("x/../..", "scheme:/", None),
("x/../../y", "scheme:/y", None),
],
),
(
"scheme://host",
&[
("", "scheme://host", None),
(".", "scheme://host/", None),
("..", "scheme://host/", None),
("foo", "scheme://host/foo", None),
],
),
(
"HTTP://USER:PASS@EXAMPLE.COM:80/1/2/3/4/.././5/../6/?QUERY",
&[(
"A/b/c/d/e/f/g/h/i/../../../j/k/l/../../../../m/n/./o",
"HTTP://USER:PASS@EXAMPLE.COM:80/1/2/3/6/A/b/c/d/e/m/n/o",
Some("http://USER:PASS@example.com:80/1/2/3/6/A/b/c/d/e/m/n/o"),
)],
),
(
"HTTP://USER:PASS@EXAMPLE.COM:/%7e/2/beta=%CE%B2/4/.././5/../6/",
&[(
"a/b/alpha=%CE%B1/d/e/f/g/h/i/../../../j/k/l/../../../../%3c/%7e/./%3e?query#fragment",
"HTTP://USER:PASS@EXAMPLE.COM:/%7e/2/beta=%CE%B2/6/a/b/alpha=%CE%B1/d/e/%3c/%7e/%3e?query#fragment",
Some("http://USER:PASS@example.com/~/2/beta=\u{03B2}/6/a/b/alpha=\u{03B1}/d/e/%3C/~/%3E?query#fragment")
)],
),
(
"http://user:pass@example.com:/%7e/2/beta=%ce%b2/4/.././5/../6/",
&[(
"a/b/alpha=%ce%b1/d/e/f/g/h/i/../../../j/k/l/../../../../%3c/%7e/./%3e?query#fragment",
"http://user:pass@example.com:/%7e/2/beta=%ce%b2/6/a/b/alpha=%ce%b1/d/e/%3c/%7e/%3e?query#fragment",
Some("http://user:pass@example.com/~/2/beta=\u{03B2}/6/a/b/alpha=\u{03B1}/d/e/%3C/~/%3E?query#fragment")
)],
),
];
#[test]
fn resolve() {
for (base, pairs) in TEST_CASES {
let base = IriAbsoluteStr::new(base).expect("should be valid base IRI");
for (target, expected, _normalized_expected) in *pairs {
let target = IriReferenceStr::new(target).expect("should be valid IRI reference");
let resolved = target.resolve_against(base);
assert_eq_display!(resolved, expected, "base={base:?}, target={target:?}");
#[cfg(feature = "alloc")]
assert_eq!(
resolved.to_dedicated_string().as_str(),
*expected,
"base={base:?}, target={target:?}"
);
}
}
}
#[test]
fn resolve_normalize() {
for (base, pairs) in TEST_CASES {
let base = IriAbsoluteStr::new(base).expect("should be valid base IRI");
for (target, expected, expected_normalized) in *pairs {
let target = IriReferenceStr::new(target).expect("should be valid IRI reference");
let resolved_normalized = target.resolve_against(base).and_normalize();
let expected = expected_normalized.unwrap_or(*expected);
assert_eq_display!(
resolved_normalized,
expected,
"base={base:?}, target={target:?}"
);
#[cfg(feature = "alloc")]
assert_eq!(
resolved_normalized.to_dedicated_string().as_str(),
expected,
"base={base:?}, target={target:?}"
);
}
}
}
#[test]
fn fixed_base_resolver() {
for (base, pairs) in TEST_CASES {
let base = IriAbsoluteStr::new(base).expect("should be valid base IRI");
let resolver = FixedBaseResolver::new(base);
for (target, expected, _normalized_expected) in *pairs {
let target = IriReferenceStr::new(target).expect("should be valid IRI reference");
let resolved = resolver.resolve(target);
assert_eq_display!(resolved, expected, "base={base:?}, target={target:?}");
#[cfg(feature = "alloc")]
assert_eq!(
resolved.to_dedicated_string().as_str(),
*expected,
"base={base:?}, target={target:?}"
);
}
}
}
#[cfg(feature = "alloc")]
#[test]
fn same_result_as_reference_impl() {
for (base, pairs) in TEST_CASES {
let base = IriAbsoluteStr::new(base).expect("should be valid base IRI");
for (target, expected, _normalized_expected) in *pairs {
let target = IriReferenceStr::new(target).expect("should be valid IRI reference");
let resolved = target.resolve_against(base).to_dedicated_string();
let expected_refimpl = resolve_refimpl(target, base);
assert_eq!(
*expected, expected_refimpl,
"base={base:?}, target={target:?}"
);
assert_eq!(
resolved, expected_refimpl,
"base={base:?}, target={target:?}"
);
}
}
}
#[test]
fn percent_encoded_dots() {
const TEST_CASES: &[(&str, &str, &str)] = &[
("scheme:", ".", "scheme:"),
("scheme:", "%2e", "scheme:"),
("scheme:", "%2E", "scheme:"),
("scheme://a", ".", "scheme://a/"),
("scheme://a", "%2e", "scheme://a/"),
("scheme://a", "%2E", "scheme://a/"),
("scheme://a/b/c", ".", "scheme://a/b/"),
("scheme://a/b/c", "%2e", "scheme://a/b/"),
("scheme://a/b/c", "%2E", "scheme://a/b/"),
("scheme://a/b/c", "./g", "scheme://a/b/g"),
("scheme://a/b/c", "%2e/g", "scheme://a/b/g"),
("scheme://a/b/c", "%2E/g", "scheme://a/b/g"),
("scheme://a/b/c/d/e/f", "../../../g", "scheme://a/b/g"),
(
"scheme://a/b/c/d/e/f",
"%2E%2E/%2E%2e/%2E./g",
"scheme://a/b/g",
),
(
"scheme://a/b/c/d/e/f",
"%2e%2E/%2e%2e/%2e./g",
"scheme://a/b/g",
),
("scheme://a/b/c/d/e/f", ".%2E/.%2e/../g", "scheme://a/b/g"),
];
for (base, reference, expected) in TEST_CASES {
let base = IriAbsoluteStr::new(base).expect("should be valid base IRI");
let reference = IriReferenceStr::new(reference).expect("should be valid IRI reference");
let resolved = reference.resolve_against(base);
assert_eq_display!(resolved, *expected);
#[cfg(feature = "alloc")]
assert_eq!(resolved.to_dedicated_string(), *expected);
}
}
#[test]
fn write_to_slice_dont_require_extra_capacity() {
let mut buf = [0_u8; 128];
for (base, pairs) in TEST_CASES {
let base = IriAbsoluteStr::new(base).expect("should be valid base IRI");
let resolver = FixedBaseResolver::new(base);
for (target, expected, _normalized_expected) in *pairs {
if expected.is_empty() {
continue;
}
let target = IriReferenceStr::new(target).expect("should be valid IRI reference");
let resolved = resolver.resolve(target);
let result_small = write_to_slice(&mut buf[..expected.len() - 1], &resolved);
assert!(result_small.is_err(), "should fail due to too small buffer");
let result_enough = write_to_slice(&mut buf[..expected.len()], &resolved);
assert!(result_enough.is_ok(), "buffer should have enough size");
assert_eq!(
result_enough.unwrap(),
*expected,
"correct result should be written"
);
}
}
}
#[test]
fn resolution_result_live_longer_than_fixed_base_resolver() {
let mut buf = [0_u8; 128];
let base = IriAbsoluteStr::new("http://example.com/").expect("should be valid base IRI");
let reference = IriReferenceStr::new("foo/bar").expect("should be valid IRI reference");
let resolved = {
let resolver = FixedBaseResolver::new(base);
resolver.resolve(reference)
};
let result = write_to_slice(&mut buf, &resolved).expect("`buf` should have enough capacity");
assert_eq!(result, "http://example.com/foo/bar");
}
#[test]
fn uri_resolution_against_self_with_normalization() {
for case in components::TEST_CASES
.iter()
.filter(|case| case.is_uri_class() && case.is_absolute())
{
let reference = UriStr::new(case.composed).expect("should be valid URI");
let resolved_normalized = AsRef::<UriReferenceStr>::as_ref(reference)
.resolve_against(reference.to_absolute())
.and_normalize();
assert_eq_display!(resolved_normalized, case.normalized_uri, "case={case:#?}");
#[cfg(feature = "alloc")]
assert_eq!(
resolved_normalized.to_string(),
case.normalized_uri,
"case={case:#?}"
);
#[cfg(feature = "alloc")]
assert_eq!(
resolved_normalized.to_dedicated_string(),
case.normalized_uri,
"case={case:#?}"
);
assert_eq!(
case.is_rfc3986_normalizable(),
resolved_normalized.ensure_rfc3986_normalizable().is_ok(),
"case={case:#?}"
);
}
}
#[test]
fn iri_resolution_against_self_with_normalization() {
for case in components::TEST_CASES
.iter()
.filter(|case| case.is_iri_class() && case.is_absolute())
{
let reference = IriStr::new(case.composed).expect("should be valid IRI");
let resolved_normalized = AsRef::<IriReferenceStr>::as_ref(reference)
.resolve_against(reference.to_absolute())
.and_normalize();
assert_eq_display!(resolved_normalized, case.normalized_iri, "case={case:#?}");
#[cfg(feature = "alloc")]
assert_eq!(
resolved_normalized.to_string(),
case.normalized_iri,
"case={case:#?}"
);
#[cfg(feature = "alloc")]
assert_eq!(
resolved_normalized.to_dedicated_string(),
case.normalized_iri,
"case={case:#?}"
);
assert_eq!(
case.is_rfc3986_normalizable(),
resolved_normalized.ensure_rfc3986_normalizable().is_ok(),
"case={case:#?}"
);
}
}