nova_forms/
query_string.rs1use std::fmt::{Debug, Display};
2
3use leptos::*;
4use ustr::Ustr;
5
6#[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#[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 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 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#[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}