use crate::uri::{ParseError, Uri};
#[test]
fn host_port_pair() {
let u = Uri::parse_authority_form("example.com:443").unwrap();
assert!(u.scheme().is_none(), "authority-form has no scheme");
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.port_u16(), Some(443));
assert_eq!(u.path().map(|p| p.as_encoded_str()).as_deref(), Some(""));
}
#[test]
fn host_port_with_userinfo() {
let u = Uri::parse_authority_form("user:pass@example.com:443").unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.port_u16(), Some(443));
assert!(u.userinfo().is_some());
}
#[test]
fn ipv4_literal() {
let u = Uri::parse_authority_form("127.0.0.1:8080").unwrap();
assert_eq!(u.host().unwrap().to_str(), "127.0.0.1");
assert_eq!(u.port_u16(), Some(8080));
}
#[test]
fn ipv6_bracketed_literal() {
let u = Uri::parse_authority_form("[::1]:443").unwrap();
assert_eq!(u.host().unwrap().to_str(), "::1");
assert_eq!(u.port_u16(), Some(443));
let u = Uri::parse_authority_form("[2001:db8::1]:80").unwrap();
assert_eq!(u.host().unwrap().to_str(), "2001:db8::1");
assert_eq!(u.port_u16(), Some(80));
}
#[test]
fn bare_host_without_port_accepted() {
let u = Uri::parse_authority_form("example.com").unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.port_u16(), None);
}
#[test]
fn path_query_fragment_delimiters_rejected() {
Uri::parse_authority_form("example.com:443/foo").unwrap_err();
Uri::parse_authority_form("example.com:443?x=1").unwrap_err();
Uri::parse_authority_form("example.com:443#frag").unwrap_err();
Uri::parse_authority_form("https://example.com:443").unwrap_err();
}
#[test]
fn empty_input_rejected() {
assert!(matches!(
Uri::parse_authority_form(""),
Err(ParseError::Empty)
));
}
#[test]
fn invalid_port_rejected() {
Uri::parse_authority_form("example.com:99999").unwrap_err();
Uri::parse_authority_form("example.com:abc").unwrap_err();
}
#[test]
fn empty_port_accepted_in_graceful_as_bare_host() {
let u = Uri::parse_authority_form("example.com:").unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.port_u16(), None);
}
#[test]
fn empty_port_rejected_in_strict_authority_form() {
let r = Uri::parse_authority_form_strict("example.com:");
assert!(matches!(r, Err(crate::uri::ParseError::StrictViolation)));
}
#[cfg(feature = "idna")]
#[test]
fn graceful_preserves_non_ascii_host_in_authority_form() {
let u = Uri::parse_authority_form("münchen.de:443").unwrap();
assert_eq!(u.host().unwrap().to_str(), "münchen.de");
}
#[cfg(feature = "idna")]
#[test]
fn strict_rejects_non_ascii_host() {
let r = Uri::parse_authority_form_strict("münchen.de:443");
assert!(r.is_err(), "strict authority-form must reject non-ASCII");
}
#[test]
fn renders_without_scheme_or_path() {
let u = Uri::parse_authority_form("example.com:443").unwrap();
let s = u.to_string();
assert!(!s.contains("://"), "rendered has scheme prefix: {s}");
assert!(s.contains("example.com"));
assert!(s.contains("443"));
}
#[test]
fn plain_parse_treats_host_port_as_scheme_path() {
let u = Uri::parse("example.com:443").unwrap();
assert_eq!(u.scheme().map(|s| s.as_str()), Some("example.com"));
assert!(u.host().is_none());
}
#[test]
fn strict_accepts_host_port() {
let u = Uri::parse_authority_form_strict("example.com:443").unwrap();
assert_eq!(u.host().unwrap().to_str(), "example.com");
assert_eq!(u.port_u16(), Some(443));
}
#[test]
fn strict_accepts_bracketed_ipv6_host_port() {
let u = Uri::parse_authority_form_strict("[2001:db8::1]:443").unwrap();
assert_eq!(u.host().unwrap().to_str(), "2001:db8::1");
assert_eq!(u.port_u16(), Some(443));
}
#[test]
fn strict_rejects_userinfo() {
let err = Uri::parse_authority_form_strict("user:pass@example.com:443").unwrap_err();
assert!(
matches!(err, ParseError::StrictViolation),
"expected StrictViolation, got {err:?}"
);
Uri::parse_authority_form_strict("user@example.com:443").unwrap_err();
}
#[test]
fn strict_rejects_bare_host_without_port() {
let err = Uri::parse_authority_form_strict("example.com").unwrap_err();
assert!(
matches!(err, ParseError::StrictViolation),
"expected StrictViolation, got {err:?}"
);
Uri::parse_authority_form_strict("[2001:db8::1]").unwrap_err();
}
#[test]
fn strict_keeps_path_query_fragment_rejection() {
Uri::parse_authority_form_strict("example.com:443/p").unwrap_err();
Uri::parse_authority_form_strict("example.com:443?q").unwrap_err();
Uri::parse_authority_form_strict("example.com:443#f").unwrap_err();
}
#[test]
fn as_authority_form_projects_full_uri() {
let u = Uri::parse("https://example.com:8443/some/path?q=1#frag").unwrap();
let auth = u.as_authority_form().unwrap();
assert!(auth.scheme().is_none());
assert_eq!(auth.host().unwrap().to_str(), "example.com");
assert_eq!(auth.port_u16(), Some(8443));
assert_eq!(auth.path().map(|p| p.as_encoded_str()).as_deref(), Some(""));
assert_eq!(auth.as_str(), "example.com:8443");
let u = Uri::parse("http://example.com/a").unwrap();
assert_eq!(u.as_authority_form().unwrap().as_str(), "example.com");
let u = Uri::parse_authority_form("example.com:443").unwrap();
assert_eq!(u.as_authority_form().unwrap().as_str(), "example.com:443");
assert!(
Uri::parse("/just/a/path")
.unwrap()
.as_authority_form()
.is_none()
);
assert!(Uri::parse("*").unwrap().as_authority_form().is_none());
}
#[test]
fn ergonomic_accessors() {
let u = Uri::parse("https://example.com:8443/api/v2/users?q=1").unwrap();
assert_eq!(u.path_or_root(), "/api/v2/users");
assert_eq!(u.query_or_empty(), "q=1");
assert_eq!(u.scheme_str(), Some("https"));
assert_eq!(u.host_str().as_deref(), Some("example.com"));
assert_eq!(u.request_target(), "/api/v2/users?q=1");
assert!(u.has_path_prefix("/api"));
assert!(u.has_path_suffix("/users"));
assert_eq!(
u.first_path_segment()
.map(|s| s.as_encoded_str())
.as_deref(),
Some("api")
);
assert_eq!(
u.path_segment(2).map(|s| s.as_encoded_str()).as_deref(),
Some("users")
);
assert!(u.path_segment(3).is_none());
let u = Uri::parse("http://example.com").unwrap();
assert!(u.is_path_empty());
assert_eq!(u.path_or_root(), "/");
assert_eq!(u.query_or_empty(), "");
assert_eq!(u.request_target(), "/");
assert!(!u.has_path_suffix("/x"));
let u = Uri::parse("/foo/bar").unwrap();
assert!(!u.is_path_empty());
assert_eq!(u.request_target(), "/foo/bar");
assert_eq!(u.scheme_str(), None);
assert_eq!(u.host_str(), None);
}
#[test]
fn ensure_path_or_root_only_roots_empty_non_asterisk_path() {
let mut u = Uri::parse("http://example.com?x=1").unwrap();
assert!(u.is_path_empty());
u.ensure_path_or_root();
assert!(!u.is_path_empty());
assert_eq!(u.request_target(), "/?x=1");
let mut u = Uri::parse("*").unwrap();
assert!(u.is_path_empty());
u.ensure_path_or_root();
assert!(u.is_asterisk());
assert_eq!(u.request_target(), "*");
}
#[test]
fn ensure_path_trailing_slash_works() {
let mut u = Uri::parse("http://example.com/dir").unwrap();
u.ensure_path_trailing_slash();
assert_eq!(u.path_or_root(), "/dir/");
u.ensure_path_trailing_slash();
assert_eq!(u.path_or_root(), "/dir/");
let mut u = Uri::parse("http://example.com/dir?x=1").unwrap();
u.ensure_path_trailing_slash();
assert_eq!(u.request_target(), "/dir/?x=1");
}
#[test]
fn ensure_path_trailing_slash_leaves_asterisk_untouched() {
let mut u = Uri::parse("*").unwrap();
u.ensure_path_trailing_slash();
assert!(u.is_asterisk());
assert_eq!(u.request_target(), "*");
}
#[test]
fn trim_path_trailing_slash_works() {
let mut u = Uri::parse("http://example.com/dir/").unwrap();
assert!(u.trim_path_trailing_slash());
assert_eq!(u.path_or_root(), "/dir");
assert!(!u.trim_path_trailing_slash());
assert_eq!(u.path_or_root(), "/dir");
let mut u = Uri::parse("http://example.com/").unwrap();
assert!(!u.trim_path_trailing_slash());
assert_eq!(u.path_or_root(), "/");
let mut u = Uri::parse("http://example.com/dir///?x=1").unwrap();
assert!(u.trim_path_trailing_slash());
assert_eq!(u.request_target(), "/dir?x=1");
}
#[test]
fn trim_path_trailing_slash_leaves_asterisk_untouched() {
let mut u = Uri::parse("*").unwrap();
assert!(!u.trim_path_trailing_slash());
assert!(u.is_asterisk());
assert_eq!(u.request_target(), "*");
}
#[cfg(test)]
mod path_match {
use crate::uri::{PathMatchOptions, Uri};
fn uri(s: &str) -> Uri {
Uri::parse(s).unwrap()
}
#[test]
fn has_prefix_boundary_default() {
let u = uri("https://example.com/api/v2/users");
assert!(u.has_path_prefix("/api"));
assert!(u.has_path_prefix("api")); assert!(u.has_path_prefix("/api/v2"));
assert!(u.has_path_prefix("")); assert!(u.has_path_prefix("/api/v2/users")); assert!(!u.has_path_prefix("/ap"));
assert!(!u.has_path_prefix("/api/v")); assert!(!uri("https://example.com/apixyz").has_path_prefix("/api"));
}
#[test]
fn has_prefix_partial() {
let opts = PathMatchOptions {
partial: true,
..Default::default()
};
let u = uri("https://example.com/apixyz/v2");
assert!(u.has_path_prefix_with_opts("/api", opts));
assert!(u.has_path_prefix_with_opts("/apixyz/v", opts));
assert!(!u.has_path_prefix_with_opts("/xyz", opts));
}
#[test]
fn has_suffix_boundary_and_partial() {
let u = uri("https://example.com/api/style.css");
assert!(u.has_path_suffix("style.css"));
assert!(u.has_path_suffix("api/style.css"));
assert!(!u.has_path_suffix(".css")); assert!(!u.has_path_suffix("le.css"));
let partial = PathMatchOptions {
partial: true,
..Default::default()
};
assert!(u.has_path_suffix_with_opts(".css", partial));
assert!(u.has_path_suffix_with_opts("le.css", partial));
assert!(!u.has_path_suffix_with_opts(".png", partial));
}
#[test]
fn percent_decode_default_on() {
let u = uri("https://example.com/foo%20bar/baz");
assert!(u.has_path_prefix("/foo bar"));
assert!(u.has_path_prefix("/foo%20bar"));
let raw = PathMatchOptions {
percent_decode: false,
..Default::default()
};
assert!(!u.has_path_prefix_with_opts("/foo bar", raw));
assert!(u.has_path_prefix_with_opts("/foo%20bar", raw));
assert!(!uri("https://example.com/a%2Fb/c").has_path_prefix("/a/b"));
}
#[test]
fn ignore_ascii_case_opt() {
let u = uri("https://example.com/API/v2");
assert!(!u.has_path_prefix("/api"));
let ci = PathMatchOptions {
ignore_ascii_case: true,
..Default::default()
};
assert!(u.has_path_prefix_with_opts("/api", ci));
assert!(u.has_path_prefix_with_opts("/API", ci));
}
#[test]
fn strip_prefix_boundary_and_partial() {
let mut u = uri("https://example.com/api/v2/x");
assert!(u.path_mut().strip_prefix("/api"));
assert_eq!(u.as_str(), "https://example.com/v2/x");
let mut u = uri("https://example.com/api/v2");
assert!(!u.path_mut().strip_prefix("/ap"));
assert_eq!(u.as_str(), "https://example.com/api/v2");
let mut u = uri("https://example.com/api/v2");
let partial = PathMatchOptions {
partial: true,
..Default::default()
};
assert!(u.path_mut().strip_prefix_with_opts("/ap", partial));
assert_eq!(u.as_str(), "https://example.com/i/v2");
}
#[test]
fn strip_suffix_works() {
let mut u = uri("https://example.com/a/b/c");
assert!(u.path_mut().strip_suffix("c"));
assert_eq!(u.as_str(), "https://example.com/a/b");
let mut u = uri("https://example.com/a/b/c");
assert!(u.path_mut().strip_suffix("b/c"));
assert_eq!(u.as_str(), "https://example.com/a");
let mut u = uri("https://example.com/a/bc");
assert!(!u.path_mut().strip_suffix("c"));
assert_eq!(u.as_str(), "https://example.com/a/bc");
let mut u = uri("https://example.com/a/bc");
let partial = PathMatchOptions {
partial: true,
..Default::default()
};
assert!(u.path_mut().strip_suffix_with_opts("c", partial));
assert_eq!(u.as_str(), "https://example.com/a/b");
}
#[test]
fn has_and_strip_agree() {
let cases = ["/api/v2", "/apixyz", "/a/b/c", "/", "/foo%20bar/x"];
let patterns = ["/api", "api", "/a/b", "ap", "/foo bar"];
for opts in [
PathMatchOptions::default(),
PathMatchOptions {
partial: true,
..Default::default()
},
PathMatchOptions {
ignore_ascii_case: true,
..Default::default()
},
PathMatchOptions {
percent_decode: false,
..Default::default()
},
] {
for path in cases {
for pat in patterns {
let u = uri(&format!("http://h{path}"));
let has = u.has_path_prefix_with_opts(pat, opts);
let mut s = u.clone();
let stripped = s.path_mut().strip_prefix_with_opts(pat, opts);
assert_eq!(
has, stripped,
"disagree for path={path:?} pat={pat:?} opts={opts:?}"
);
}
}
}
}
}