auri/
lib.rs

1//! I couldn't find any URI library that does all of these things:
2
3//! - construct URI from parts and serialize it
4//! - construct querystring from parts and make it part of URI
5
6//! Also, I may want relative paths, too?
7
8pub mod path;
9pub mod ppath;
10pub mod url_encoding;
11
12use kstring::KString;
13
14use ppath::PPath;
15use url_encoding::{url_decode, url_encode, UrlDecodingError};
16
17// ------------------------------------------------------------------
18
19/// Simple representation of query strings (are nested representations
20/// even supported by browsers?). So far just for constructing the
21/// serialized representation.
22#[derive(Debug)]
23pub struct QueryString(Vec<(KString, KString)>);
24
25impl From<&QueryString> for String {
26    fn from(q: &QueryString) -> Self {
27        let mut s = String::new();
28        let mut is_first = true;
29        for (k, v) in &q.0 {
30            if is_first {
31                is_first = false;
32            } else {
33                s.push('&');
34            }
35            s.push_str(&url_encode(&k));
36            s.push('=');
37            s.push_str(&url_encode(&v));
38        }
39        s
40    }
41}
42
43pub trait ToVecKeyVal {
44    fn to_vec_key_val(self) -> Vec<(KString, KString)>;
45}
46
47impl ToVecKeyVal for Vec<(String, String)> {
48    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
49        self.into_iter()
50            .map(|(k, v)| (KString::from(k), KString::from(v)))
51            .collect()
52    }
53}
54impl ToVecKeyVal for Vec<(&str, &str)> {
55    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
56        self.into_iter()
57            .map(|(k, v)| (KString::from_ref(k), KString::from_ref(v)))
58            .collect()
59    }
60}
61impl<const N: usize> ToVecKeyVal for [(KString, KString); N] {
62    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
63        self.into_iter().collect()
64    }
65}
66impl ToVecKeyVal for &[(KString, KString)] {
67    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
68        self.into_iter().cloned().collect()
69    }
70}
71impl ToVecKeyVal for &[(String, String)] {
72    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
73        self.into_iter()
74            .map(|(k, v)| (KString::from_ref(k), KString::from_ref(v)))
75            .collect()
76    }
77}
78impl ToVecKeyVal for &[(&str, &str)] {
79    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
80        self.into_iter()
81            .map(|(k, v)| (KString::from_ref(k), KString::from_ref(v)))
82            .collect()
83    }
84}
85impl<const N: usize> ToVecKeyVal for [(&str, &str); N] {
86    fn to_vec_key_val(self) -> Vec<(KString, KString)> {
87        self.into_iter()
88            .map(|(k, v)| (KString::from_ref(k), KString::from_ref(v)))
89            .collect()
90    }
91}
92
93impl QueryString {
94    pub fn new(keyvals: impl ToVecKeyVal) -> Self {
95        Self(keyvals.to_vec_key_val())
96    }
97
98    pub fn from_str(s: &str) -> Result<Self, UrlDecodingError> {
99        let mut v = Vec::new();
100        for partraw in s.split('&') {
101            if !partraw.is_empty() {
102                if let Some((key, val)) = partraw.split_once('=') {
103                    v.push((url_decode(key)?.into(), url_decode(val)?.into()));
104                } else {
105                    // XX accept as value-less thing?
106                    v.push((url_decode(partraw)?.into(), "".into()));
107                }
108            }
109        }
110        Ok(QueryString(v))
111    }
112
113    pub fn push(&mut self, keyval: (KString, KString)) {
114        self.0.push(keyval);
115    }
116}
117
118#[cfg(test)]
119mod tests1 {
120    use super::*;
121
122    #[test]
123    fn t_querystring_from_str() {
124        fn t(s: &str, q: &[(&str, &str)]) {
125            let q1 = QueryString::from_str(s).expect("not to fail");
126            let q11: Vec<(&str, &str)> =
127                q1.0.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
128            assert_eq!(&q11, q);
129        }
130        t("", &[]);
131        t("foo", &[("foo", "")]); // XX not sure
132        t("foo=1", &[("foo", "1")]);
133        t("=1&&2=", &[("", "1"), ("2", "")]);
134        t("foo=1&bar=2", &[("foo", "1"), ("bar", "2")]);
135        t("foo=1%26&ba%72=%202", &[("foo", "1&"), ("bar", " 2")]);
136        // ^ XX is it ok to decode keys, but doing it after & and = splitting?
137        // XXX test unicode
138    }
139}
140
141// ------------------------------------------------------------------
142
143/// A URI without a scheme or authority part; i.e. relative or absolute path
144#[derive(Debug)]
145pub struct AUriLocal {
146    path: PPath<KString>,
147    query: Option<QueryString>,
148    // todo: fragment
149}
150
151impl AUriLocal {
152    pub fn new(path: PPath<KString>, query: Option<QueryString>) -> Self {
153        Self { path, query }
154    }
155    pub fn from_str(path: &str, query: Option<QueryString>) -> Self {
156        Self {
157            path: PPath::from_str(path),
158            query,
159        }
160    }
161}
162
163impl From<&AUriLocal> for String {
164    fn from(a: &AUriLocal) -> Self {
165        let mut pathstring = a.path.to_string();
166        if let Some(query) = &a.query {
167            pathstring.push('?');
168            pathstring.push_str(&String::from(query));
169        }
170        pathstring
171    }
172}
173
174impl From<AUriLocal> for String {
175    fn from(a: AUriLocal) -> Self {
176        Self::from(&a)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn t_1() {
186        let uri = AUriLocal::new(PPath::from_str("/foo/bar"), None);
187        assert_eq!(String::from(&uri).as_str(), "/foo/bar");
188    }
189
190    #[test]
191    fn t_2() {
192        let q = QueryString::new([
193            ("fun", "1"),
194            ("Motörhead", "C'est bien ça & méchanique = plus!"),
195        ]);
196        let uri = AUriLocal::from_str("/foo///bar/", Some(q));
197        assert_eq!(
198            String::from(&uri).as_str(),
199            "/foo/bar/?fun=1&Mot%C3%B6rhead=C%27est%20bien%20%C3%A7a%20%26%20m%C3%A9chanique%20%3D%20plus%21");
200    }
201}
202
203pub enum AUri {
204    Local(AUriLocal),
205    // [todo: full URIs with scheme & authority]
206}