nova_forms/
query_string.rs

1use std::fmt::Display;
2
3use leptos::*;
4use ustr::Ustr;
5
6/// A part of a query string.
7/// Either an index for arrays or a key to access a value.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum QueryStringPart {
10    Index(usize),
11    Key(Ustr),
12}
13
14impl Display for QueryStringPart {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            QueryStringPart::Index(i) => write!(f, "{}", i),
18            QueryStringPart::Key(k) => write!(f, "{}", k),
19        }
20    }
21}
22
23/// Used to bind a form input element to a form data element.
24/// Note that `QueryString` supports a maximal depth of 16.
25/// Creating query strings consisting of more than 16 parts will panic.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
27pub struct QueryString {
28    parts: [Option<QueryStringPart>; 16],
29    len: usize,
30}
31
32impl FromIterator<QueryStringPart> for QueryString {
33    fn from_iter<T: IntoIterator<Item = QueryStringPart>>(iter: T) -> Self {
34        let mut qs = QueryString::default();
35        let mut len = 0;
36        for (i, part) in iter.into_iter().enumerate() {
37            qs.parts[i] = Some(part);
38            len += 1;
39        }
40        qs.len = len;
41        qs
42    }
43}
44
45impl<'a> FromIterator<&'a QueryStringPart> for QueryString {
46    fn from_iter<T: IntoIterator<Item = &'a QueryStringPart>>(iter: T) -> Self {
47        let mut qs = QueryString::default();
48        let mut len = 0;
49        for (i, part) in iter.into_iter().enumerate() {
50            qs.parts[i] = Some(*part);
51            len += 1;
52        }
53        qs.len = len;
54        qs
55    }
56}
57
58impl QueryString {
59    pub fn iter(&self) -> impl Iterator<Item = &QueryStringPart> {
60        self.parts.iter().flatten().fuse()
61    }
62
63    pub fn len(&self) -> usize {
64        self.iter().count()
65    }
66
67    /// Checks whether the current query string extends the other query string.
68    pub fn extends(&self, other: &Self) -> Option<QueryString> {
69        if self.len() < other.len() {
70            return None;
71        }
72
73        if !self.iter().zip(other.iter()).all(|(s, o)| s == o) {
74            return None;
75        }
76
77        Some(self.iter().skip(other.len()).collect())
78    }
79
80    /// Joins two `QueryString`s.
81    pub fn join(self, other: Self) -> Self {
82        self.iter().chain(other.iter()).collect()
83    }
84
85    pub fn add(mut self, part: QueryStringPart) -> Self {
86        self.parts[self.len] = Some(part);
87        self.len += 1;
88        self
89    }
90
91    pub fn add_index(self, index: usize) -> Self {
92        self.add(QueryStringPart::Index(index))
93    }
94
95    pub fn add_key<K: AsRef<str>>(self, key: K) -> Self {
96        self.add(QueryStringPart::Key(Ustr::from(key.as_ref())))
97    }
98}
99
100impl IntoAttribute for QueryString {
101    fn into_attribute(self) -> Attribute {
102        Attribute::String(Oco::Owned(format!("{self}")))
103    }
104
105    fn into_attribute_boxed(self: Box<Self>) -> Attribute {
106        Attribute::String(Oco::Owned(format!("{self}")))
107    }
108}
109
110impl From<&str> for QueryString {
111    fn from(value: &str) -> Self {
112        let mut chars = value.chars();
113        let mut parts = Vec::new();
114        while let Some(c) = chars.next() {
115            match c {
116                '[' => parts.push(String::new()),
117                ']' => {}
118                _ => {
119                    if let Some(last) = parts.last_mut() {
120                        last.push(c);
121                    } else {
122                        parts.push(String::from(c));
123                    }
124                }
125            }
126        }
127
128        parts
129            .into_iter()
130            .map(|p| {
131                p.parse::<usize>()
132                    .map(QueryStringPart::Index)
133                    .unwrap_or_else(|_| QueryStringPart::Key(Ustr::from(&p)))
134            })
135            .collect()
136    }
137}
138
139impl From<String> for QueryString {
140    fn from(value: String) -> Self {
141        QueryString::from(value.as_str())
142    }
143}
144
145impl Display for QueryString {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        let mut iter = self.iter();
148        if let Some(first) = iter.next() {
149            write!(f, "{}", first)?;
150        }
151        for part in iter {
152            write!(f, "[{}]", part)?;
153        }
154        Ok(())
155    }
156}
157
158/// Creates a `QueryString`.
159#[macro_export]
160macro_rules! qs {
161    ( $key:ident $($t:tt)* ) => {
162        qs!(@part($crate::QueryString::default().add_key(stringify!($key))) $($t)*)
163    };
164    () => {
165        $crate::QueryString::default()
166    };
167    ( @part($part:expr) [ $index:literal ] $($t:tt)* ) => {
168        qs!(@part($part.add_index($index)) $($t)*)
169    };
170    ( @part($part:expr) [ $key:ident ] $($t:tt)* ) => {
171        qs!(@part($part.add_key(stringify!($key))) $($t)*)
172    };
173    (@part($part:expr) ) => {
174        $part
175    };
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_extends() {
184        assert_eq!(QueryString::from("form_data[a][b]").extends(&QueryString::from("form_data[a]")), Some(QueryString::from("b")));
185    }
186
187    #[test]
188    fn test_join() {
189        assert_eq!(QueryString::from("a").join(QueryString::from("b")), QueryString::from("a[b]"));
190    }
191
192    #[test]
193    fn test_add() {
194        assert_eq!(QueryString::from("a").add_key("b"), QueryString::from("a[b]"));
195        assert_eq!(QueryString::from("a").add_index(0), QueryString::from("a[0]"));
196    }
197    
198    #[test]
199    fn test_qs_macro() {
200        let qs = qs!(a[b][c]);
201        assert_eq!(qs, QueryString::from("a[b][c]"));
202        let qs = qs!(a[0][c]);
203        assert_eq!(qs, QueryString::from("a[0][c]"));
204        let qs = qs!(a);
205        assert_eq!(qs, QueryString::from("a"));
206        let qs = qs!();
207        assert_eq!(qs, QueryString::default());
208    }
209}