use std::iter;
use o3::alloc::array;
use crate::uri::{
path,
target::{self, Error, Origin, Slash, Target},
};
mod raw;
#[derive(Clone, Copy)]
pub struct Relative<'a> {
value: &'a str,
path_end: u32,
}
impl<'a> Relative<'a> {
pub(super) fn from_target(target: &'a Target) -> Self {
Self {
value: target.path_query(),
path_end: target.path_end - target.authority_end,
}
}
pub fn parse(value: &'a str) -> Result<Self, Error> {
let plan = path::Scan::new(value.as_bytes())
.complete()
.map_err(Error::Origin)?;
Ok(Self {
value,
path_end: target::narrow(plan.path_end())?,
})
}
pub fn resolve(self, base: &Target) -> Result<Target, Error> {
if self.value.is_empty() {
return base.try_clone().map_err(Error::Allocation);
}
if self.value.starts_with('/') && !self.has_dots() {
return base.with_origin(Origin {
value: self.value,
path_end: self.path_end,
slash: Slash::Present,
});
}
self.materialize(base)
}
pub(super) fn materialize(self, base: &Target) -> Result<Target, Error> {
let (path, query) = unsafe {
(
target::raw::slice(self.value, 0..self.path_end as usize),
target::raw::slice(self.value, self.path_end as usize..self.value.len()),
)
};
let (value, path_end) = if path.is_empty() {
let capacity = (base.path_end as usize)
.checked_add(query.len())
.ok_or(Error::Length)?;
let mut value = target::buffer(capacity)?;
value.push_str(unsafe { target::raw::slice(&base.value, 0..base.path_end as usize) });
value.push_str(query);
(value, base.path_end)
} else {
let (prefix, path) = match path.strip_prefix('/') {
Some(path) => ("", path),
None => {
let base = base.path();
let end = base.rfind('/').unwrap_or(0) + 1;
(unsafe { target::raw::slice(base, 1..end) }, path)
}
};
let segments = segments(prefix, path);
let path_len = segments.clone().try_fold(0usize, |len, part| {
len.checked_add(part.len() + 1).ok_or(Error::Length)
})?;
let path_end = (base.authority_end as usize)
.checked_add(path_len)
.ok_or(Error::Length)?;
let capacity = path_end.checked_add(query.len()).ok_or(Error::Length)?;
let buffer = array::Plan::try_new(capacity).map_err(|_| Error::Length)?;
let value = unsafe {
raw::normalize(
buffer,
target::raw::slice(&base.value, 0..base.authority_end as usize),
path_len,
segments,
query,
)
}
.map_err(Error::Allocation)?;
target::narrow(path_len)?;
let path_end = target::narrow(path_end)?;
(value, path_end)
};
Ok(Target {
value: unsafe { target::raw::boxed(value) },
scheme_end: base.scheme_end,
host_end: base.host_end,
authority_end: base.authority_end,
path_end,
})
}
pub(super) fn has_dots(self) -> bool {
unsafe { target::raw::slice(self.value, 0..self.path_end as usize) }
.split('/')
.any(|part| matches!(part, "." | ".."))
}
}
fn segments<'a>(prefix: &'a str, path: &'a str) -> impl Iterator<Item = &'a str> + Clone {
let trailing = matches!(path.rsplit('/').next(), Some("." | ".."));
let mut parents = 0usize;
iter::once("").take(usize::from(trailing)).chain(
path.rsplit('/')
.chain(prefix.rsplit_terminator('/'))
.filter(move |part| match *part {
"." => false,
".." => {
parents += 1;
false
}
_ if parents != 0 => {
parents -= 1;
false
}
_ => true,
}),
)
}