Skip to main content

sark_http/uri/target/
plan.rs

1use std::num::NonZero;
2
3use o3::alloc::{self, array};
4
5use crate::uri::target::{self, Absolute, Error, Slash, Target};
6
7/// An admitted owned target whose canonical bytes are materialized on consumption.
8/// Admission keeps the original buffer and checks every grammar and offset bound.
9pub struct Plan {
10    value: String,
11    prefix: bool,
12    slash: Slash,
13    buffer: array::Plan<u8, usize>,
14    bounds: Bounds,
15}
16
17pub(super) struct Bounds {
18    scheme_end: Option<NonZero<u32>>,
19    host_end: u32,
20    authority_end: u32,
21    path_end: u32,
22}
23
24impl Bounds {
25    pub(super) fn new(scheme: usize, host: usize, end: usize, path: usize) -> Result<Self, Error> {
26        Ok(Self {
27            scheme_end: NonZero::new(target::narrow(scheme)?),
28            host_end: target::narrow(host)?,
29            authority_end: target::narrow(end)?,
30            path_end: target::narrow(path)?,
31        })
32    }
33
34    pub(super) fn build(self, value: Box<str>) -> Target {
35        Target {
36            value,
37            scheme_end: self.scheme_end,
38            host_end: self.host_end,
39            authority_end: self.authority_end,
40            path_end: self.path_end,
41        }
42    }
43}
44
45impl Plan {
46    pub fn parse(value: String) -> Result<Self, Error> {
47        let parsed = Absolute::parse(&value)?;
48        let origin = parsed.origin();
49        let scheme = parsed.scheme().len();
50        let end = value.len() - origin.value.len();
51        let host = scheme + 3 + parsed.authority().host().len();
52        let path = end.checked_add(origin.path_end()).ok_or(Error::Length)?;
53        let capacity = value
54            .len()
55            .checked_add(usize::from(origin.slash == Slash::Insert))
56            .ok_or(Error::Length)?;
57        let bounds = Bounds::new(scheme, host, end, path)?;
58        let buffer = array::Plan::try_new(capacity).map_err(|_| Error::Length)?;
59        let slash = origin.slash;
60        Ok(Self {
61            value,
62            prefix: false,
63            slash,
64            buffer,
65            bounds,
66        })
67    }
68
69    /// Admits an HTTP authority with the root origin form.
70    pub fn http(value: String) -> Result<Self, Error> {
71        let parsed = target::authority(&value)?;
72        let end = 7usize.checked_add(value.len()).ok_or(Error::Length)?;
73        let capacity = end.checked_add(1).ok_or(Error::Length)?;
74        let bounds = Bounds::new(4, 7 + parsed.host().len(), end, capacity)?;
75        let buffer = array::Plan::try_new(capacity).map_err(|_| Error::Length)?;
76        Ok(Self {
77            value,
78            prefix: true,
79            slash: Slash::Insert,
80            buffer,
81            bounds,
82        })
83    }
84
85    pub fn path(&self) -> &str {
86        match self.slash {
87            Slash::Insert => "/",
88            Slash::Present => {
89                let range = self.bounds.authority_end as usize..self.bounds.path_end as usize;
90                // SAFETY: parse retains this ASCII path range in the unchanged owned input.
91                unsafe { target::raw::slice(&self.value, range) }
92            }
93        }
94    }
95
96    /// Consumes the admitted offsets without parsing again. Exact capacity is
97    /// reused; other capacities require one allocation and never a later shrink.
98    pub fn try_build(self) -> Result<Target, alloc::Exhausted> {
99        let mut value = self.value;
100        if value.capacity() != *self.buffer.extent() {
101            let mut output = self.buffer.try_string()?;
102            if self.prefix {
103                output.push_str("http://");
104            }
105            let end = self.bounds.authority_end as usize - output.len();
106            // SAFETY: admission retains this authority split on an ASCII boundary of the input.
107            let (head, tail) = unsafe {
108                (
109                    target::raw::slice(&value, 0..end),
110                    target::raw::slice(&value, end..value.len()),
111                )
112            };
113            output.push_str(head);
114            if self.slash == Slash::Insert {
115                output.push('/');
116            }
117            output.push_str(tail);
118            value = output;
119        } else {
120            if self.prefix {
121                // SAFETY: this plan's exact capacity admits its complete ASCII prefix and origin.
122                unsafe { target::raw::insert(&mut value, 0, "http://") };
123            }
124            if self.slash == Slash::Insert {
125                // SAFETY: the admitted ASCII authority boundary and spare root byte are unchanged.
126                unsafe { target::raw::insert(&mut value, self.bounds.authority_end as usize, "/") };
127            }
128        }
129        // SAFETY: reused or freshly allocated storage now contains every planned byte.
130        Ok(self.bounds.build(unsafe { target::raw::boxed(value) }))
131    }
132}