nova_forms/
form_data.rs

1use std::{collections::HashMap, str::FromStr};
2
3use leptos::*;
4use percent_encoding::{percent_decode, percent_encode, NON_ALPHANUMERIC};
5use serde::{de::DeserializeOwned, Deserialize, Serialize};
6
7use crate::{QueryString, QueryStringPart};
8
9/// Contains arbitrary form data in a serialized form.
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
11pub struct FormData {
12    data: RwSignal<HashMap<QueryString, String>>
13}
14
15impl FormData {
16    pub fn serialize<F>(form_data: &F) -> Self
17    where
18        F: Serialize,
19    {
20        let serialized = serde_qs::to_string(form_data).expect("must be serializable");
21        FormData::from_str(&serialized).unwrap()
22    }
23
24    pub fn deserialize<F>(&self) -> F
25    where
26        F: DeserializeOwned,
27    {
28        serde_qs::from_str(&self.to_string()).expect("must be deserializable")
29    }
30}
31
32impl FromStr for FormData {
33    type Err = ();
34
35    fn from_str(s: &str) -> Result<Self, ()> {
36        let map = s
37            .split("&")
38            .into_iter()
39            .map(|pair| {
40                pair.split_once("=")
41                    .map(|(k, v)| {
42                        (
43                            QueryString::from(k),
44                            percent_decode(v.as_bytes()).decode_utf8_lossy().to_string(),
45                        )
46                    })
47                    .unwrap_or_else(|| (QueryString::from(pair), String::new()))
48            })
49            .collect();
50
51        Ok(FormData {
52            data: RwSignal::new(map)
53        })
54    }
55}
56
57impl ToString for FormData {
58    fn to_string(&self) -> String {
59        self.data
60            .get()
61            .iter()
62            .map(|(k, v)| {
63                format!("{}={}", 
64                    k,
65                    percent_encode(v.as_bytes(), NON_ALPHANUMERIC)
66                )
67            })
68            .collect::<Vec<_>>()
69            .join("&")
70    }
71}
72
73impl FormData {
74    pub fn values<T: DeserializeOwned + PartialEq>(&self, qs: QueryString) -> Signal<Option<T>> {
75        #[derive(Deserialize)]
76        struct Value<T> {
77            value: T,
78        }
79
80        let signal = self.data;
81
82        Memo::new(move |_| {
83            let data = signal.get();
84
85            let form_data = data
86                .iter()
87                .filter_map(|(k, v)| {
88                    Some(format!("{}={}", 
89                        QueryString::default().add_key("value").join(k.extends(&qs)?),
90                        percent_encode(v.as_bytes(), NON_ALPHANUMERIC)
91                    ))
92                })
93                .collect::<Vec<_>>()
94                .join("&");
95    
96            Some(serde_qs::from_str::<Value<T>>(&form_data.to_string()).ok()?.value)
97        }).into()
98    }
99
100    pub fn set_values<T: Serialize>(&self, qs: QueryString, values: T) {
101        self.data.update(|data| {
102            let form_data = FormData::serialize(&values);
103            for (k, v) in form_data.data.get_untracked() {
104                data.insert(qs.join(k), v);
105            }
106        });
107    }
108
109    pub fn raw_value(&self, qs: QueryString) -> Signal<String> {
110        let signal = self.data;
111        
112        Memo::new(move |_| {
113            let data = signal.get();
114
115            let value = data
116                .iter()
117                .filter_map(|(k, v)| {
118                    k.extends(&qs)?;
119                    Some(v)
120                })
121                .next()
122                .cloned()
123                .unwrap_or_default();
124    
125            value
126        }).into()
127    }
128
129    pub fn set_raw_value<S: Into<String>>(&self, qs: QueryString, value: S) {
130        self.data.update(|data| {
131            data.insert(qs.clone(), value.into());
132        });
133    }
134
135    pub fn value<T: FromStr>(&self, qs: QueryString) -> Signal<Result<T, T::Err>> {
136        let signal = self.raw_value(qs);
137        Signal::derive(move || T::from_str(&signal.get()))
138    }
139
140    pub fn set_value<T: ToString>(&self, qs: QueryString, value: T) {
141        self.set_raw_value(qs, value.to_string());
142    }
143
144    pub fn len(&self, qs: QueryString) -> Option<usize> {
145        self.data
146            .get_untracked()
147            .keys()
148            .map(|k| {
149                if let Some(k) = k.extends(&qs) {
150                    k.iter().next().and_then(|&p| {
151                        if let QueryStringPart::Index(i) = p {
152                            Some(i)
153                        } else {
154                            None
155                        }
156                    })
157                } else {
158                    None
159                }
160            })
161            .reduce(|l1, l2| {
162                if let (Some(l1), Some(l2)) = (l1, l2) {
163                    Some(l1.max(l2))
164                } else {
165                    None
166                }
167            })
168            .flatten()
169            .map(|l| l + 1)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use crate::qs;
176    use super::*;
177
178    #[test]
179    fn test_value() {
180        let _ = leptos::create_runtime();
181
182        let form_data = FormData::from_str("a=1&b=2&c=3").unwrap();
183        assert_eq!(form_data.value::<i32>(qs!(a)).get_untracked().unwrap(), 1);
184        assert_eq!(form_data.value::<i32>(qs!(b)).get_untracked().unwrap(), 2);
185        assert_eq!(form_data.value::<i32>(qs!(c)).get_untracked().unwrap(), 3);
186    }
187
188    #[test]
189    fn test_set_value() {
190        let _ = leptos::create_runtime();
191
192        let form_data = FormData::from_str("a=1&b=2&c=3").unwrap();
193        form_data.set_value(QueryString::from("a"), 4);
194        assert_eq!(form_data.value::<i32>(qs!(a)).get_untracked().unwrap(), 4);
195        assert_eq!(form_data.value::<i32>(qs!(b)).get_untracked().unwrap(), 2);
196        assert_eq!(form_data.value::<i32>(qs!(c)).get_untracked().unwrap(), 3);
197    }
198
199    #[test]
200    fn test_values() {
201        let _ = leptos::create_runtime();
202
203        #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
204        struct Test {
205            a: i32,
206            b: i32,
207            c: i32,
208        }
209
210        let form_data = FormData::from_str("a=1&b=2&c=3").unwrap();
211        assert_eq!(form_data.values::<Test>(qs!()).get_untracked().unwrap(), Test { a: 1, b: 2, c: 3 });
212    }
213
214    #[test]
215    fn test_set_values() {
216        let _ = leptos::create_runtime();
217
218        #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
219        struct Test {
220            a: i32,
221            b: i32,
222            c: i32,
223        }
224
225        let form_data = FormData::from_str("a=1&b=2&c=3").unwrap();
226        form_data.set_values(qs!(), Test { a: 4, b: 5, c: 6 });
227        assert_eq!(form_data.values::<Test>(qs!()).get_untracked().unwrap(), Test { a: 4, b: 5, c: 6 });
228    }
229
230    #[test]
231    fn test_len() {
232        let _ = leptos::create_runtime();
233
234        let form_data = FormData::from_str("a[0]=1&a[3]=2&a[1]=3").unwrap();
235        assert_eq!(form_data.len(qs!(a)).unwrap(), 4);
236    }
237}