nova_forms/
query_string.rs

1use std::fmt::{Debug, 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, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
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
23impl From<&str> for QueryStringPart {
24    fn from(value: &str) -> Self {
25        QueryStringPart::Key(Ustr::from(value))
26    }
27}
28
29impl From<String> for QueryStringPart {
30    fn from(value: String) -> Self {
31        QueryStringPart::from(value.as_str())
32    }
33}
34
35impl From<usize> for QueryStringPart {
36    fn from(value: usize) -> Self {
37        QueryStringPart::Index(value)
38    }
39}
40
41/// Used to bind a form input element to a form data element.
42/// Note that `QueryString` supports a maximal depth of 16.
43/// Creating query strings consisting of more than 16 parts will panic.
44#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)]
45pub struct QueryString {
46    parts: [Option<QueryStringPart>; 16],
47    len: usize,
48}
49
50impl FromIterator<QueryStringPart> for QueryString {
51    fn from_iter<T: IntoIterator<Item = QueryStringPart>>(iter: T) -> Self {
52        let mut qs = QueryString::default();
53        let mut len = 0;
54        for (i, part) in iter.into_iter().enumerate() {
55            qs.parts[i] = Some(part);
56            len += 1;
57        }
58        qs.len = len;
59        qs
60    }
61}
62
63impl<'a> FromIterator<&'a QueryStringPart> for QueryString {
64    fn from_iter<T: IntoIterator<Item = &'a QueryStringPart>>(iter: T) -> Self {
65        let mut qs = QueryString::default();
66        let mut len = 0;
67        for (i, part) in iter.into_iter().enumerate() {
68            qs.parts[i] = Some(*part);
69            len += 1;
70        }
71        qs.len = len;
72        qs
73    }
74}
75
76impl QueryString {
77    pub fn iter(&self) -> impl Iterator<Item = &QueryStringPart> {
78        self.parts.iter().flatten().fuse()
79    }
80
81    pub fn len(&self) -> usize {
82        self.iter().count()
83    }
84
85    pub fn is_empty(&self) -> bool {
86        self.len == 0
87    }
88
89    /// Checks whether the current query string extends the other query string.
90    pub fn extends(&self, other: &Self) -> Option<QueryString> {
91        debug_assert_eq!(self.parts.iter().filter(|p| p.is_some()).count(), self.len);
92        debug_assert_eq!(other.parts.iter().filter(|p| p.is_some()).count(), other.len);
93
94        if self.len() < other.len() {
95            return None;
96        }
97
98        if !self.iter().zip(other.iter()).all(|(s, o)| s == o) {
99            return None;
100        }
101
102        Some(self.iter().skip(other.len()).collect())
103    }
104
105    /// Joins two `QueryString`s.
106    pub fn join(self, other: Self) -> Self {
107        self.iter().chain(other.iter()).collect()
108    }
109
110    pub fn add(mut self, part: QueryStringPart) -> Self {
111        self.parts[self.len] = Some(part);
112        self.len += 1;
113        debug_assert_eq!(self.parts.iter().filter(|p| p.is_some()).count(), self.len);
114        self
115    }
116
117    pub fn add_index(self, index: usize) -> Self {
118        self.add(QueryStringPart::Index(index))
119    }
120
121    pub fn add_key<K: AsRef<str>>(self, key: K) -> Self {
122        self.add(QueryStringPart::Key(Ustr::from(key.as_ref())))
123    }
124
125    pub fn first(&self) -> Option<QueryStringPart> {
126        self.parts[0]
127    }
128
129    pub fn remove(mut self) -> Self {
130        self.parts[self.len - 1] = None;
131        self.len -= 1;
132        debug_assert_eq!(self.parts.iter().filter(|p| p.is_some()).count(), self.len);
133        self
134    }
135
136    pub fn remove_first(mut self) -> Self {
137        for i in 1..self.len {
138            self.parts[i - 1] = self.parts[i];
139        }
140        self.parts[self.len - 1] = None;
141        self.len -= 1;
142        debug_assert_eq!(self.parts.iter().filter(|p| p.is_some()).count(), self.len);
143        self
144    }
145}
146
147impl IntoAttribute for QueryString {
148    fn into_attribute(self) -> Attribute {
149        Attribute::String(Oco::Owned(format!("{self}")))
150    }
151
152    fn into_attribute_boxed(self: Box<Self>) -> Attribute {
153        Attribute::String(Oco::Owned(format!("{self}")))
154    }
155}
156
157impl From<&str> for QueryString {
158    fn from(value: &str) -> Self {
159        let mut chars = value.chars();
160        let mut parts = Vec::new();
161        while let Some(c) = chars.next() {
162            match c {
163                '[' => parts.push(String::new()),
164                ']' => {}
165                _ => {
166                    if let Some(last) = parts.last_mut() {
167                        last.push(c);
168                    } else {
169                        parts.push(String::from(c));
170                    }
171                }
172            }
173        }
174
175        parts
176            .into_iter()
177            .map(|p| {
178                p.parse::<usize>()
179                    .map(QueryStringPart::Index)
180                    .unwrap_or_else(|_| QueryStringPart::Key(Ustr::from(&p)))
181            })
182            .collect()
183    }
184}
185
186impl From<String> for QueryString {
187    fn from(value: String) -> Self {
188        QueryString::from(value.as_str())
189    }
190}
191
192impl Display for QueryString {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        let mut iter = self.iter();
195        if let Some(first) = iter.next() {
196            write!(f, "{}", first)?;
197        }
198        for part in iter {
199            write!(f, "[{}]", part)?;
200        }
201        Ok(())
202    }
203}
204
205/// Creates a `QueryString`.
206#[macro_export]
207macro_rules! qs {
208    ( $key:ident $($t:tt)* ) => {
209        qs!(@part($crate::QueryString::default().add_key(stringify!($key))) $($t)*)
210    };
211    ( .. $($t:tt)* ) => {
212        qs!(@part(leptos::expect_context::<$crate::GroupContext>().qs()) $($t)*)
213    };
214    () => {
215        $crate::QueryString::default()
216    };
217    ( @part($part:expr) [!] $($t:tt)* ) => {
218        qs!(@part($part.remove()) $($t)*)
219    };
220    ( @part($part:expr) [ $index:literal ] $($t:tt)* ) => {
221        qs!(@part($part.add_index($index)) $($t)*)
222    };
223    ( @part($part:expr) [ $key:ident ] $($t:tt)* ) => {
224        qs!(@part($part.add_key(stringify!($key))) $($t)*)
225    };
226    ( @part($part:expr) ) => {
227        $part
228    };
229}
230
231#[cfg(test)]
232mod tests {
233    use crate::{BaseGroupContext, GroupContext};
234
235    use super::*;
236
237    #[test]
238    fn test_extends() {
239        assert_eq!(QueryString::from("form_data[a][b]").extends(&QueryString::from("form_data[a]")), Some(QueryString::from("b")));
240    }
241
242    #[test]
243    fn test_join() {
244        assert_eq!(QueryString::from("a").join(QueryString::from("b")), QueryString::from("a[b]"));
245    }
246
247    #[test]
248    fn test_add() {
249        assert_eq!(QueryString::from("a").add_key("b"), QueryString::from("a[b]"));
250        assert_eq!(QueryString::from("a").add_index(0), QueryString::from("a[0]"));
251    }
252
253    #[test]
254    fn test_qs_context() {
255        let _ = leptos::create_runtime();
256        provide_context(BaseGroupContext::new().to_group_context());
257        provide_context(GroupContext::new(QueryStringPart::from("a")));
258        expect_context::<GroupContext>();
259        let qs = qs!(..[b][c]);
260        assert_eq!(qs, QueryString::from("a[b][c]"));
261    }
262
263    #[test]
264    fn test_qs_remove() {
265        let qs = qs!(a[b][d][!][c]);
266        assert_eq!(qs, QueryString::from("a[b][c]"));
267    }
268    
269    
270    #[test]
271    fn test_qs_macro() {
272        let qs = qs!(a[b][c]);
273        assert_eq!(qs, QueryString::from("a[b][c]"));
274        let qs = qs!(a[0][c]);
275        assert_eq!(qs, QueryString::from("a[0][c]"));
276        let qs = qs!(a);
277        assert_eq!(qs, QueryString::from("a"));
278        let qs = qs!();
279        assert_eq!(qs, QueryString::default());
280    }
281
282    #[test]
283    fn test_first() {
284        let qs = qs!(a[b][c]);
285        assert_eq!(qs.first(), Some(QueryStringPart::Key(Ustr::from("a"))));
286    }
287
288    #[test]
289    fn test_remove() {
290        let qs = qs!(a[b][c]);
291        assert_eq!(qs.remove(), QueryString::from("a[b]"));
292    }
293
294    #[test]
295    fn test_remove_first() {
296        let qs = qs!(a[b][c]);
297        assert_eq!(qs.remove_first(), QueryString::from("b[c]"));
298    }
299}