Skip to main content

sark_core/http/request/
mod.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::marker::PhantomData;
4use std::ops::Range;
5use std::str::FromStr;
6
7use http::{HeaderMap, Method, Uri, Version};
8use o3::buffer::{Owned, Shared};
9
10#[derive(Clone)]
11enum LocalFrameOwnerRef<'req> {
12    Shared(Shared),
13    Borrowed(&'req [u8]),
14}
15
16pub struct LocalFrameBytesRef<'req> {
17    owner: LocalFrameOwnerRef<'req>,
18    range: Range<usize>,
19    _marker: PhantomData<&'req [u8]>,
20}
21
22pub type LocalFrameBytes = LocalFrameBytesRef<'static>;
23
24impl<'req> LocalFrameBytesRef<'req> {
25    pub fn len(&self) -> usize {
26        self.range.end.saturating_sub(self.range.start)
27    }
28
29    pub fn is_empty(&self) -> bool {
30        self.range.is_empty()
31    }
32
33    pub fn from_shared(owner: Shared) -> Self {
34        let len = owner.len();
35        Self::from_shared_range(owner, 0..len)
36    }
37
38    pub fn from_shared_range(owner: Shared, range: Range<usize>) -> Self {
39        let len = owner.len();
40        assert!(range.start <= range.end, "invalid local frame range");
41        assert!(range.end <= len, "local frame range exceeds owner length");
42        Self {
43            owner: LocalFrameOwnerRef::Shared(owner),
44            range,
45            _marker: PhantomData,
46        }
47    }
48
49    #[allow(clippy::missing_safety_doc)]
50    pub unsafe fn assume_static(self) -> LocalFrameBytes {
51        // SAFETY: caller guarantees the backing storage outlives every use of the returned frame.
52        unsafe { std::mem::transmute::<LocalFrameBytesRef<'req>, LocalFrameBytes>(self) }
53    }
54
55    pub fn from_slice(slice: &'req [u8]) -> Self {
56        Self {
57            owner: LocalFrameOwnerRef::Borrowed(slice),
58            range: 0..slice.len(),
59            _marker: PhantomData,
60        }
61    }
62
63    pub fn as_bytes(&self) -> &[u8] {
64        match &self.owner {
65            LocalFrameOwnerRef::Shared(bytes) => &bytes[self.range.clone()],
66            LocalFrameOwnerRef::Borrowed(slice) => &slice[self.range.clone()],
67        }
68    }
69
70    pub fn slice(mut self, range: Range<usize>) -> Self {
71        let new_start = self.range.start + range.start;
72        let new_end = self.range.start + range.end;
73        assert!(new_end <= self.range.end, "slice exceeds frame length");
74        self.range = new_start..new_end;
75        self
76    }
77
78    pub fn into_bytes(self) -> Shared {
79        match self.owner {
80            LocalFrameOwnerRef::Shared(bytes) => bytes.slice(self.range),
81            LocalFrameOwnerRef::Borrowed(slice) => Shared::copy_from_slice(&slice[self.range]),
82        }
83    }
84}
85
86impl<'req> Clone for LocalFrameBytesRef<'req> {
87    fn clone(&self) -> Self {
88        Self {
89            owner: self.owner.clone(),
90            range: self.range.clone(),
91            _marker: PhantomData,
92        }
93    }
94}
95
96impl<'req> AsRef<[u8]> for LocalFrameBytesRef<'req> {
97    fn as_ref(&self) -> &[u8] {
98        self.as_bytes()
99    }
100}
101
102const INLINE_PATH_PARAM_CAP: usize = 2;
103
104type PathParam = (Box<str>, Range<usize>);
105
106#[derive(Clone)]
107enum PathParamStorage {
108    Inline {
109        items: [Option<PathParam>; INLINE_PATH_PARAM_CAP],
110        len: u8,
111    },
112    Heap(Vec<PathParam>),
113}
114
115#[derive(Clone)]
116pub struct PathParamRanges(PathParamStorage);
117
118impl Default for PathParamRanges {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124impl PathParamRanges {
125    pub fn new() -> Self {
126        Self(PathParamStorage::Inline {
127            items: [const { None }; INLINE_PATH_PARAM_CAP],
128            len: 0,
129        })
130    }
131
132    pub fn with_capacity(cap: usize) -> Self {
133        if cap > INLINE_PATH_PARAM_CAP {
134            Self(PathParamStorage::Heap(Vec::with_capacity(cap)))
135        } else {
136            Self::new()
137        }
138    }
139
140    pub fn push(&mut self, key: Box<str>, range: Range<usize>) {
141        match &mut self.0 {
142            PathParamStorage::Heap(heap) => heap.push((key, range)),
143            PathParamStorage::Inline { items, len }
144                if usize::from(*len) < INLINE_PATH_PARAM_CAP =>
145            {
146                items[usize::from(*len)] = Some((key, range));
147                *len += 1;
148            }
149            PathParamStorage::Inline { items, len } => {
150                let mut heap = Vec::with_capacity(usize::from(*len).saturating_add(1));
151                for slot in items.iter_mut().take(usize::from(*len)) {
152                    if let Some(item) = slot.take() {
153                        heap.push(item);
154                    }
155                }
156                heap.push((key, range));
157                self.0 = PathParamStorage::Heap(heap);
158            }
159        }
160    }
161
162    pub fn find_last(&self, key: &str) -> Option<&Range<usize>> {
163        match &self.0 {
164            PathParamStorage::Heap(heap) => heap
165                .iter()
166                .rev()
167                .find(|(k, _)| k.as_ref() == key)
168                .map(|(_, r)| r),
169            PathParamStorage::Inline { items, len } => items
170                .iter()
171                .take(usize::from(*len))
172                .rev()
173                .find_map(|slot| slot.as_ref().filter(|(k, _)| k.as_ref() == key))
174                .map(|(_, r)| r),
175        }
176    }
177}
178
179pub struct Request {
180    method: Method,
181    uri: Uri,
182    version: Version,
183    headers: HeaderMap,
184    body: Owned,
185    path_params: Vec<(Box<str>, Shared)>,
186    query_cache: RefCell<QueryCache>,
187}
188
189#[derive(Clone, Debug)]
190enum QueryCache {
191    Unparsed,
192    NoQuery,
193    Invalid,
194    Parsed(HashMap<String, String>),
195}
196
197pub struct PathParamsIter<'a> {
198    req: &'a Request,
199    idx: usize,
200}
201
202impl<'a> Iterator for PathParamsIter<'a> {
203    type Item = (&'a str, &'a [u8]);
204
205    fn next(&mut self) -> Option<Self::Item> {
206        let (key, value) = self.req.path_params.get(self.idx)?;
207        self.idx += 1;
208        Some((key.as_ref(), value.as_ref()))
209    }
210
211    fn size_hint(&self) -> (usize, Option<usize>) {
212        let remaining = self.req.path_params.len().saturating_sub(self.idx);
213        (remaining, Some(remaining))
214    }
215}
216
217impl ExactSizeIterator for PathParamsIter<'_> {}
218
219impl Request {
220    pub fn new(method: Method, uri: Uri) -> Self {
221        Self {
222            method,
223            uri,
224            version: Version::HTTP_11,
225            headers: HeaderMap::new(),
226            body: Owned::new(),
227            path_params: Vec::new(),
228            query_cache: RefCell::new(QueryCache::Unparsed),
229        }
230    }
231
232    #[allow(clippy::should_implement_trait)]
233    pub fn default() -> Self {
234        Self::new(Method::GET, Uri::from_static("/"))
235    }
236
237    pub fn method(&self) -> &Method {
238        &self.method
239    }
240    pub fn uri(&self) -> &Uri {
241        &self.uri
242    }
243    pub fn headers(&self) -> &HeaderMap {
244        &self.headers
245    }
246    pub fn headers_mut(&mut self) -> &mut HeaderMap {
247        &mut self.headers
248    }
249
250    pub fn body(&self) -> &Owned {
251        &self.body
252    }
253
254    pub fn body_mut(&mut self) -> &mut Owned {
255        &mut self.body
256    }
257
258    pub fn set_body(&mut self, body: impl Into<Owned>) {
259        self.body = body.into();
260    }
261
262    pub fn clear_body(&mut self) {
263        self.body.clear();
264    }
265
266    pub fn set_body_str(&mut self, body: &str) -> &mut Self {
267        self.body = Owned::from(body.as_bytes());
268        self
269    }
270
271    pub fn body_str(&self) -> Option<&str> {
272        std::str::from_utf8(self.body.as_ref()).ok()
273    }
274
275    pub fn path_param<T: AsRef<str>>(&self, key: T) -> Option<&str> {
276        self.path_param_bytes(key)
277            .and_then(|v| std::str::from_utf8(v).ok())
278    }
279
280    pub fn path_param_bytes<T: AsRef<str>>(&self, key: T) -> Option<&[u8]> {
281        let k = key.as_ref();
282        self.path_params
283            .iter()
284            .rev()
285            .find(|(key, _)| key.as_ref() == k)
286            .map(|(_, v)| v.as_ref())
287    }
288
289    pub fn path_params(&self) -> PathParamsIter<'_> {
290        PathParamsIter { req: self, idx: 0 }
291    }
292
293    pub fn path_params_len(&self) -> usize {
294        self.path_params.len()
295    }
296
297    pub fn set_path_params(&mut self, params: Vec<(Box<str>, Shared)>) -> &mut Self {
298        self.path_params = params;
299        self
300    }
301
302    pub fn insert_path_param(
303        &mut self,
304        key: impl AsRef<str>,
305        value: impl Into<Shared>,
306    ) -> &mut Self {
307        let k = key.as_ref();
308        let v = value.into();
309        for (key, value) in self.path_params.iter_mut().rev() {
310            if key.as_ref() == k {
311                *value = v;
312                return self;
313            }
314        }
315
316        self.path_params.push((Box::<str>::from(k), v));
317        self
318    }
319
320    fn decode_query_component(input: &[u8]) -> Option<String> {
321        if !input.contains(&b'%') && !input.contains(&b'+') {
322            return std::str::from_utf8(input).ok().map(|s| s.to_string());
323        }
324
325        let mut out = Vec::with_capacity(input.len());
326        let mut i = 0usize;
327        while i < input.len() {
328            match input[i] {
329                b'+' => {
330                    out.push(b' ');
331                    i += 1;
332                }
333                b'%' => {
334                    if i + 2 >= input.len() {
335                        return None;
336                    }
337                    let hi = Self::from_hex(input[i + 1])?;
338                    let lo = Self::from_hex(input[i + 2])?;
339                    out.push((hi << 4) | lo);
340                    i += 3;
341                }
342                b => {
343                    out.push(b);
344                    i += 1;
345                }
346            }
347        }
348
349        String::from_utf8(out).ok()
350    }
351
352    fn from_hex(c: u8) -> Option<u8> {
353        match c {
354            b'0'..=b'9' => Some(c - b'0'),
355            b'a'..=b'f' => Some(c - b'a' + 10),
356            b'A'..=b'F' => Some(c - b'A' + 10),
357            _ => None,
358        }
359    }
360
361    fn query_kv_iter(&self) -> impl Iterator<Item = (&[u8], &[u8])> {
362        self.uri
363            .query()
364            .unwrap_or("")
365            .as_bytes()
366            .split(|b| *b == b'&')
367            .filter(|kv| !kv.is_empty())
368            .map(|kv| {
369                let mut it = kv.splitn(2, |b| *b == b'=');
370                let k = it.next().unwrap_or(&[]);
371                let v = it.next().unwrap_or(&[]);
372                (k, v)
373            })
374    }
375
376    pub fn query<T: AsRef<str>>(&self, key: T) -> Option<String> {
377        let key = key.as_ref().as_bytes();
378        for (k, v) in self.query_kv_iter() {
379            if k == key {
380                return Self::decode_query_component(v);
381            }
382            if (k.contains(&b'%') || k.contains(&b'+'))
383                && Self::decode_query_component(k).is_some_and(|decoded| decoded.as_bytes() == key)
384            {
385                return Self::decode_query_component(v);
386            }
387        }
388        None
389    }
390
391    pub fn query_bytes<T: AsRef<[u8]>>(&self, key: T) -> Option<&[u8]> {
392        let key = key.as_ref();
393        self.query_kv_iter()
394            .find(|(k, _)| *k == key)
395            .map(|(_, v)| v)
396    }
397
398    pub fn with_uri(&mut self, uri: Uri) -> &mut Self {
399        self.uri = uri;
400        *self.query_cache.get_mut() = QueryCache::Unparsed;
401        self
402    }
403
404    pub fn with_query<T: serde::Serialize>(
405        &mut self,
406        query: &T,
407    ) -> Result<&mut Self, serde_urlencoded::ser::Error> {
408        let query_string = serde_urlencoded::to_string(query)?;
409        let current_uri = self.uri.clone();
410
411        let mut parts = current_uri.into_parts();
412        parts.path_and_query = Some(match parts.path_and_query {
413            Some(path_and_query) => {
414                let path = path_and_query.path();
415                let new_path_and_query = format!("{}?{}", path, query_string);
416                http::uri::PathAndQuery::from_str(&new_path_and_query).map_err(|e| {
417                    <serde_urlencoded::ser::Error as serde::ser::Error>::custom(format!(
418                        "invalid path and query: {e}"
419                    ))
420                })?
421            }
422            None => {
423                let new_path_and_query = format!("/?{query_string}");
424                http::uri::PathAndQuery::from_str(&new_path_and_query).map_err(|e| {
425                    <serde_urlencoded::ser::Error as serde::ser::Error>::custom(format!(
426                        "invalid path and query: {e}"
427                    ))
428                })?
429            }
430        });
431
432        let new_uri = Uri::from_parts(parts).map_err(|e| {
433            <serde_urlencoded::ser::Error as serde::ser::Error>::custom(format!(
434                "invalid uri parts: {e}"
435            ))
436        })?;
437
438        self.uri = new_uri;
439        *self.query_cache.get_mut() = QueryCache::Unparsed;
440        Ok(self)
441    }
442
443    pub fn query_params_ref(&self) -> Option<std::cell::Ref<'_, HashMap<String, String>>> {
444        {
445            let mut cache = self.query_cache.borrow_mut();
446            if matches!(*cache, QueryCache::Unparsed) {
447                *cache = match self.uri.query() {
448                    None => QueryCache::NoQuery,
449                    Some(q) => match serde_urlencoded::from_str::<HashMap<String, String>>(q) {
450                        Ok(m) => QueryCache::Parsed(m),
451                        Err(_) => QueryCache::Invalid,
452                    },
453                };
454            }
455        }
456
457        std::cell::Ref::filter_map(self.query_cache.borrow(), |c| match c {
458            QueryCache::Parsed(m) => Some(m),
459            QueryCache::Invalid => None,
460            QueryCache::NoQuery => None,
461            QueryCache::Unparsed => None,
462        })
463        .ok()
464    }
465}
466
467impl std::fmt::Debug for Request {
468    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469        f.debug_struct("Request")
470            .field("method", &self.method)
471            .field("uri", &self.uri)
472            .field("version", &self.version)
473            .field("headers", self.headers())
474            .field("body_len", &self.body.len())
475            .field("path_params", &self.path_params.len())
476            .finish()
477    }
478}
479
480impl Clone for Request {
481    fn clone(&self) -> Self {
482        let query_cache = self.query_cache.borrow().clone();
483        Self {
484            method: self.method.clone(),
485            uri: self.uri.clone(),
486            version: self.version,
487            headers: self.headers.clone(),
488            body: self.body.clone(),
489            path_params: self.path_params.clone(),
490            query_cache: RefCell::new(query_cache),
491        }
492    }
493}
494
495#[cfg(test)]
496mod tests;