use super::*;
use core::fmt;
use core::ops::Deref;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[derive(Eq, Hash)]
pub struct RelRef(pub(super) UriRef);
_impl_uri_traits_base!(RelRef);
impl Deref for RelRef {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Default for &RelRef {
fn default() -> Self {
irel_ref!("")
}
}
impl Default for &mut RelRef {
fn default() -> Self {
use core::slice::from_raw_parts_mut;
use core::str::from_utf8_unchecked_mut;
unsafe {
let empty_slice = from_raw_parts_mut(core::ptr::NonNull::<u8>::dangling().as_ptr(), 0);
let empty_string = from_utf8_unchecked_mut(empty_slice);
RelRef::from_str_unchecked_mut(empty_string)
}
}
}
impl AnyUriRef for RelRef {
unsafe fn write_to_unsafe<T: fmt::Write + ?Sized>(&self, write: &mut T) -> fmt::Result {
if let Some(i) = self.colon_in_first_path_segment() {
write!(write, "{}%3A{}", &self[..i], &self[i + 1..])
} else {
if self.starts_with("//") {
write.write_str("/.")?;
}
write.write_str(self.as_str())
}
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn uri_type(&self) -> UriType {
if self.starts_with('#') {
UriType::Fragment
} else if self.starts_with('?') {
UriType::Query
} else if self.starts_with('/') {
UriType::AbsolutePath
} else {
UriType::RelativePath
}
}
fn components(&self) -> UriRawComponents<'_> {
UriRawComponents {
scheme: None,
authority: None,
userinfo: None,
host: None,
port: None,
path: self.path_as_rel_ref(),
query: self.raw_query(),
fragment: self.raw_fragment(),
}
}
}
impl fmt::Display for RelRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.write_to(f)
}
}
impl RelRef {
pub fn from_str(s: &str) -> Result<&RelRef, ParseError> {
if let Some(first_error) = s.unescape_uri().first_error() {
Err(ParseError::from(first_error))
} else {
Ok(unsafe { Self::from_str_unchecked(s) })
}
}
pub fn is_str_valid<S: AsRef<str>>(s: S) -> bool {
s.as_ref().unescape_uri().first_error().is_none()
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn to_rel_ref_buf(&self) -> RelRefBuf {
RelRefBuf::from_rel_ref(self)
}
#[cfg(feature = "alloc")]
pub fn to_uri_ref_buf(&self) -> UriRefBuf {
self.to_rel_ref_buf().into()
}
#[inline(always)]
pub const fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn try_as_uri_ref(&self) -> Option<&UriRef> {
if self.is_degenerate() {
None
} else {
Some(&self.0)
}
}
#[cfg(feature = "alloc")]
pub fn as_uri_ref(&self) -> Cow<'_, UriRef> {
if let Some(uri_ref) = self.try_as_uri_ref() {
Cow::Borrowed(uri_ref)
} else {
Cow::Owned(self.to_uri_ref_buf())
}
}
}
impl RelRef {
#[must_use = "this returns a new slice, without modifying the original"]
pub fn path_as_rel_ref(&self) -> &RelRef {
self.trim_query()
}
#[must_use = "this returns a new slice, without modifying the original"]
#[inline(always)]
pub fn query_as_rel_ref(&self) -> Option<&RelRef> {
self.0.query_as_rel_ref()
}
#[must_use]
#[inline(always)]
pub fn raw_path(&self) -> &str {
self.path_as_rel_ref().as_str()
}
#[must_use = "this returns a new slice, without modifying the original"]
#[inline(always)]
pub fn raw_query(&self) -> Option<&str> {
self.0.raw_query()
}
#[must_use = "this returns a new slice, without modifying the original"]
#[inline(always)]
pub fn raw_fragment(&self) -> Option<&str> {
self.0.raw_fragment()
}
pub fn raw_path_segments(&self) -> impl Iterator<Item = &str> {
let path = self.path_as_rel_ref();
let mut ret = path.as_str().split('/');
if path.is_empty() {
let _ = ret.next();
} else if path.starts_with('/') {
let _ = ret.next();
}
ret
}
#[inline(always)]
pub fn raw_query_items(&self) -> impl Iterator<Item = &str> {
self.0.raw_query_items()
}
#[inline(always)]
pub fn raw_query_key_values(&self) -> impl Iterator<Item = (&str, &str)> {
self.0.raw_query_key_values()
}
#[must_use]
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn fragment(&self) -> Option<Cow<'_, str>> {
self.0.fragment()
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn path_segments(&self) -> impl Iterator<Item = Cow<'_, str>> {
self.0.path_segments()
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn query_items(&self) -> impl Iterator<Item = Cow<'_, str>> {
self.0.query_items()
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn query_key_values(&self) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)> {
self.0.query_key_values()
}
#[must_use]
#[inline(always)]
pub fn has_trailing_slash(&self) -> bool {
self.0.has_trailing_slash()
}
#[must_use]
pub fn colon_in_first_path_segment(&self) -> Option<usize> {
for (i, b) in self.bytes().enumerate() {
match b {
b if i == 0 && (b as char).is_numeric() => return None,
b if (b as char).is_ascii_alphanumeric() => continue,
b'+' | b'-' | b'.' => continue,
b':' => return Some(i),
_ => return None,
}
}
None
}
pub fn is_degenerate(&self) -> bool {
self.starts_with("//") || self.colon_in_first_path_segment().is_some()
}
}
impl RelRef {
#[cfg(feature = "alloc")]
#[must_use]
pub fn resolved_rel_ref<UF: AsRef<RelRef>>(&self, dest: UF) -> RelRefBuf {
let mut ret = String::with_capacity(self.len() + dest.as_ref().len());
self.write_resolved(dest.as_ref(), &mut ret)
.expect("URI resolution failed");
ret.shrink_to_fit();
let mut ret = unsafe { RelRefBuf::from_string_unchecked(ret) };
ret.disambiguate();
ret
}
}
impl RelRef {
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_fragment(&self) -> &RelRef {
unsafe { RelRef::from_str_unchecked(self.0.trim_fragment().as_str()) }
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_query(&self) -> &RelRef {
unsafe { RelRef::from_str_unchecked(self.0.trim_query().as_str()) }
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_resource(&self) -> &RelRef {
unsafe { RelRef::from_str_unchecked(self.0.trim_resource().as_str()) }
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_trailing_slash(&self) -> &RelRef {
let path_end = self.0.path_end();
if path_end > 1 && &self[path_end - 1..path_end] == "/" {
unsafe { Self::from_str_unchecked(&self[..path_end - 1]) }
} else {
self.trim_query()
}
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_leading_slashes(&self) -> &RelRef {
unsafe { RelRef::from_str_unchecked(self.trim_start_matches('/')) }
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_leading_dot_slashes(&self) -> &RelRef {
unsafe {
let mut str_ref = self.as_str();
while str_ref.starts_with("/./") {
str_ref = &str_ref[2..];
}
str_ref = str_ref.trim_start_matches("./");
if str_ref == "." {
str_ref = &str_ref[..0];
}
RelRef::from_str_unchecked(str_ref)
}
}
#[must_use = "this returns the leading path item trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_leading_path_segment(&self) -> (&str, &RelRef) {
let trimmed = self.trim_leading_slashes();
if let Some(i) = trimmed.find(|c| c == '/' || c == '?' || c == '#') {
match trimmed.as_bytes()[i] {
b'/' => (&trimmed[..i], unsafe {
RelRef::from_str_unchecked(&trimmed[i + 1..])
}),
_ => (&trimmed[..i], unsafe {
RelRef::from_str_unchecked(&trimmed[i..])
}),
}
} else {
(trimmed.as_str(), unsafe {
RelRef::from_str_unchecked(&trimmed[trimmed.len()..])
})
}
}
#[must_use]
fn _trim_leading_n_path_segments(&self, n: usize) -> (&str, &RelRef) {
let mut next = self;
for _ in 0..n {
next = next.trim_leading_path_segment().1;
}
let i = next.as_ptr() as usize - self.as_ptr() as usize;
((&self[..i]).trim_end_matches('/'), next)
}
#[must_use = "this returns the trimmed uri as a new slice, without modifying the original"]
pub fn trim_leading_n_path_segments(&self, n: usize) -> (&str, &RelRef) {
self.trim_leading_slashes()._trim_leading_n_path_segments(n)
}
#[must_use = "this returns the trimmed uri as a new slice, without modifying the original"]
pub fn trim_to_shorten(&self, base: &RelRef) -> Option<&RelRef> {
self.0.trim_to_shorten(base.try_as_uri_ref()?)
}
}
impl RelRef {
#[inline(always)]
pub unsafe fn from_str_unchecked(s: &str) -> &RelRef {
&*(s as *const str as *const RelRef)
}
#[inline(always)]
pub unsafe fn from_str_unchecked_mut(s: &mut str) -> &mut RelRef {
&mut *(s as *mut str as *mut RelRef)
}
#[inline(always)]
pub unsafe fn as_mut_str(&mut self) -> &mut str {
self.0.as_mut_str()
}
#[inline(always)]
pub const unsafe fn as_uri_ref_unchecked(&self) -> &UriRef {
&self.0
}
#[doc(hidden)]
#[must_use = "this returns a new slice, without modifying the original"]
pub unsafe fn path_as_rel_ref_mut(&mut self) -> &mut RelRef {
let i = self.trim_query().len();
let str_mut: &mut str = core::mem::transmute(self.as_mut_str());
RelRef::from_str_unchecked_mut(&mut str_mut[..i])
}
#[doc(hidden)]
#[must_use = "this returns a new slice, without modifying the original"]
pub unsafe fn query_as_rel_ref_mut(&mut self) -> Option<&mut RelRef> {
self.0.query_as_rel_ref_mut()
}
pub unsafe fn unsafe_path_segment_iter(&mut self) -> impl Iterator<Item = &str> {
let path = self.path_as_rel_ref_mut();
let is_empty = path.is_empty();
let starts_with_slash = path.starts_with('/');
let mut_bytes = path.as_mut_str().as_bytes_mut();
let mut ret = mut_bytes.split_mut(|b| *b == b'/').filter_map(|seg| {
let seg = core::str::from_utf8_unchecked_mut(seg);
if seg == "." {
None
} else {
Some(&*seg.unescape_uri_in_place())
}
});
if is_empty || starts_with_slash {
let _ = ret.next();
}
ret
}
pub unsafe fn unsafe_query_item_iter(&mut self) -> impl Iterator<Item = &str> {
let query = self.query_as_rel_ref_mut().unwrap_or_default();
let is_empty = query.is_empty();
let starts_with_delim = query.starts_with(|c| c == '&' || c == ';');
let mut mut_bytes = query.as_mut_str().as_bytes_mut();
if !is_empty && mut_bytes[0] == b'?' {
mut_bytes = &mut mut_bytes[1..];
}
let mut ret = mut_bytes
.split_mut(|b| *b == b'&' || *b == b';')
.map(|seg| {
let seg = core::str::from_utf8_unchecked_mut(seg);
&*seg.unescape_uri_in_place()
});
if is_empty || starts_with_delim {
let _ = ret.next();
}
ret
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec, string::ToString};
#[test]
fn path() {
assert_eq!(
irel_ref!("example/"),
irel_ref!("example/").path_as_rel_ref()
);
assert_eq!(
irel_ref!(unsafe "http:example.com/blah/"),
irel_ref!(unsafe "http:example.com/blah/?q").path_as_rel_ref()
);
}
#[test]
fn path_segment_iter() {
assert_eq!(
vec!["example", ""],
irel_ref!("example/")
.raw_path_segments()
.collect::<Vec::<_>>()
);
assert_eq!(
vec!["http:example.com", "blah", ""],
irel_ref!(unsafe "http:example.com/blah/?q")
.raw_path_segments()
.collect::<Vec::<_>>()
);
}
#[test]
fn avoid_scheme_confusion() {
assert_eq!(None, irel_ref!("this/that").colon_in_first_path_segment());
assert_eq!(None, irel_ref!("1this:that").colon_in_first_path_segment());
assert_eq!(None, irel_ref!("/this:that").colon_in_first_path_segment());
assert_eq!(
None,
irel_ref!("%20this:that").colon_in_first_path_segment()
);
assert_eq!(
Some(4),
irel_ref!(unsafe "this:that").colon_in_first_path_segment()
);
assert_eq!(
Some(4),
irel_ref!(unsafe "th1s:that").colon_in_first_path_segment()
);
assert_eq!(
None,
irel_ref!(unsafe "this:that").to_uri_ref_buf().scheme()
);
assert_eq!(None, irel_ref!(unsafe "this:that").try_as_uri_ref());
assert_eq!(
&irel_ref!(unsafe "this:that").to_uri_ref_buf(),
irel_ref!("this%3Athat"),
);
}
#[test]
fn trim_leading_path_segment() {
assert_eq!(
("example", irel_ref!("")),
irel_ref!("example/").trim_leading_path_segment()
);
assert_eq!(
("example", irel_ref!("")),
irel_ref!("/example/").trim_leading_path_segment()
);
assert_eq!(
("a", irel_ref!("b/c/d/")),
irel_ref!("a/b/c/d/").trim_leading_path_segment()
);
assert_eq!(
("a", irel_ref!("?query")),
irel_ref!("a?query").trim_leading_path_segment()
);
assert_eq!(
("a", irel_ref!("#frag")),
irel_ref!("a#frag").trim_leading_path_segment()
);
assert_eq!(
("fool:ish", irel_ref!("/thoughts?")),
irel_ref!(unsafe "fool:ish//thoughts?").trim_leading_path_segment()
);
assert_eq!(
("", irel_ref!("")),
irel_ref!("").trim_leading_path_segment()
);
}
#[test]
fn trim_leading_n_path_segments() {
assert_eq!(
("", irel_ref!("a/b/c/d")),
irel_ref!("a/b/c/d").trim_leading_n_path_segments(0)
);
assert_eq!(
("a", irel_ref!("b/c/d")),
irel_ref!("a/b/c/d").trim_leading_n_path_segments(1)
);
assert_eq!(
("a/b", irel_ref!("c/d")),
irel_ref!("a/b/c/d").trim_leading_n_path_segments(2)
);
assert_eq!(
("a/b/c", irel_ref!("d")),
irel_ref!("a/b/c/d").trim_leading_n_path_segments(3)
);
assert_eq!(
("a/b/c/d", irel_ref!("")),
irel_ref!("a/b/c/d").trim_leading_n_path_segments(4)
);
assert_eq!(
("a/b/c/d", irel_ref!("")),
irel_ref!("a/b/c/d").trim_leading_n_path_segments(5)
);
assert_eq!(
("a/b/c", irel_ref!("d?blah")),
irel_ref!("a/b/c/d?blah").trim_leading_n_path_segments(3)
);
assert_eq!(
("a/b/c/d", irel_ref!("?blah")),
irel_ref!("a/b/c/d?blah").trim_leading_n_path_segments(4)
);
assert_eq!(
("a/b/c/d", irel_ref!("?blah")),
irel_ref!("a/b/c/d?blah").trim_leading_n_path_segments(5)
);
assert_eq!(
("a/b/c", irel_ref!("d?blah")),
irel_ref!("/a/b/c/d?blah").trim_leading_n_path_segments(3)
);
}
}