satex_core/extension/
url_params.rs

1use http::Extensions;
2use matchit::Params;
3use std::ops::Deref;
4use std::sync::Arc;
5
6#[derive(Clone, Debug)]
7pub enum UrlParams {
8    Params(Vec<(Arc<str>, PercentDecodedStr)>),
9    InvalidUtf8InPathParam { key: Arc<str> },
10}
11
12pub fn insert_url_params(extensions: &mut Extensions, params: Params<'_, '_>) {
13    let current_params = extensions.get_mut();
14
15    if let Some(UrlParams::InvalidUtf8InPathParam { .. }) = current_params {
16        // nothing to do here since an error was stored earlier
17        return;
18    }
19
20    let params = params
21        .iter()
22        .map(|(k, v)| {
23            if let Some(decoded) = PercentDecodedStr::new(v) {
24                Ok((Arc::from(k), decoded))
25            } else {
26                Err(Arc::from(k))
27            }
28        })
29        .collect::<Result<Vec<_>, _>>();
30
31    match (current_params, params) {
32        (Some(UrlParams::InvalidUtf8InPathParam { .. }), _) => {
33            unreachable!("we check for this state earlier in this method")
34        }
35        (_, Err(invalid_key)) => {
36            extensions.insert(UrlParams::InvalidUtf8InPathParam { key: invalid_key });
37        }
38        (Some(UrlParams::Params(current)), Ok(params)) => {
39            current.extend(params);
40        }
41        (None, Ok(params)) => {
42            extensions.insert(UrlParams::Params(params));
43        }
44    }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Hash)]
48pub struct PercentDecodedStr(Arc<str>);
49
50impl PercentDecodedStr {
51    pub(crate) fn new<S>(s: S) -> Option<Self>
52    where
53        S: AsRef<str>,
54    {
55        percent_encoding::percent_decode(s.as_ref().as_bytes())
56            .decode_utf8()
57            .ok()
58            .map(|decoded| Self(decoded.as_ref().into()))
59    }
60
61    pub(crate) fn as_str(&self) -> &str {
62        &self.0
63    }
64}
65
66impl Deref for PercentDecodedStr {
67    type Target = str;
68
69    #[inline]
70    fn deref(&self) -> &Self::Target {
71        self.as_str()
72    }
73}