use std::num::NonZero;
use o3::alloc::{self, array};
use crate::uri::target::{self, Absolute, Error, Slash, Target};
pub struct Plan {
value: String,
prefix: bool,
slash: Slash,
buffer: array::Plan<u8, usize>,
bounds: Bounds,
}
pub(super) struct Bounds {
scheme_end: Option<NonZero<u32>>,
host_end: u32,
authority_end: u32,
path_end: u32,
}
impl Bounds {
pub(super) fn new(scheme: usize, host: usize, end: usize, path: usize) -> Result<Self, Error> {
Ok(Self {
scheme_end: NonZero::new(target::narrow(scheme)?),
host_end: target::narrow(host)?,
authority_end: target::narrow(end)?,
path_end: target::narrow(path)?,
})
}
pub(super) fn build(self, value: Box<str>) -> Target {
Target {
value,
scheme_end: self.scheme_end,
host_end: self.host_end,
authority_end: self.authority_end,
path_end: self.path_end,
}
}
}
impl Plan {
pub fn parse(value: String) -> Result<Self, Error> {
let parsed = Absolute::parse(&value)?;
let origin = parsed.origin();
let scheme = parsed.scheme().len();
let end = value.len() - origin.value.len();
let host = scheme + 3 + parsed.authority().host().len();
let path = end.checked_add(origin.path_end()).ok_or(Error::Length)?;
let capacity = value
.len()
.checked_add(usize::from(origin.slash == Slash::Insert))
.ok_or(Error::Length)?;
let bounds = Bounds::new(scheme, host, end, path)?;
let buffer = array::Plan::try_new(capacity).map_err(|_| Error::Length)?;
let slash = origin.slash;
Ok(Self {
value,
prefix: false,
slash,
buffer,
bounds,
})
}
pub fn http(value: String) -> Result<Self, Error> {
let parsed = target::authority(&value)?;
let end = 7usize.checked_add(value.len()).ok_or(Error::Length)?;
let capacity = end.checked_add(1).ok_or(Error::Length)?;
let bounds = Bounds::new(4, 7 + parsed.host().len(), end, capacity)?;
let buffer = array::Plan::try_new(capacity).map_err(|_| Error::Length)?;
Ok(Self {
value,
prefix: true,
slash: Slash::Insert,
buffer,
bounds,
})
}
pub fn path(&self) -> &str {
match self.slash {
Slash::Insert => "/",
Slash::Present => {
let range = self.bounds.authority_end as usize..self.bounds.path_end as usize;
unsafe { target::raw::slice(&self.value, range) }
}
}
}
pub fn try_build(self) -> Result<Target, alloc::Exhausted> {
let mut value = self.value;
if value.capacity() != *self.buffer.extent() {
let mut output = self.buffer.try_string()?;
if self.prefix {
output.push_str("http://");
}
let end = self.bounds.authority_end as usize - output.len();
let (head, tail) = unsafe {
(
target::raw::slice(&value, 0..end),
target::raw::slice(&value, end..value.len()),
)
};
output.push_str(head);
if self.slash == Slash::Insert {
output.push('/');
}
output.push_str(tail);
value = output;
} else {
if self.prefix {
unsafe { target::raw::insert(&mut value, 0, "http://") };
}
if self.slash == Slash::Insert {
unsafe { target::raw::insert(&mut value, self.bounds.authority_end as usize, "/") };
}
}
Ok(self.bounds.build(unsafe { target::raw::boxed(value) }))
}
}