racoon/forms/fields/
file_field.rs

1use std::any::Any;
2use std::marker::PhantomData;
3use std::path::PathBuf;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::Arc;
6
7use async_tempfile::TempFile;
8use tokio::sync::Mutex;
9
10use crate::core::forms::{Files, FormData};
11use crate::forms::AbstractFields;
12
13use crate::forms::fields::FieldResult;
14
15pub struct UploadedFile {
16    pub filename: String,
17    core_file_field: crate::core::forms::FileField,
18    pub temp_path: PathBuf,
19}
20
21impl UploadedFile {
22    pub fn from_core_file_field(core_file_field: crate::core::forms::FileField) -> Self {
23        let temp_path = core_file_field.temp_path.clone();
24        let filename = core_file_field.name.clone();
25
26        Self {
27            filename,
28            core_file_field,
29            temp_path,
30        }
31    }
32
33    pub fn core_file_field(&mut self) -> &crate::core::forms::FileField {
34        &mut self.core_file_field
35    }
36
37    pub fn from_temp_file<S: AsRef<str>>(filename: S, temp_file: TempFile) -> Self {
38        let filename = filename.as_ref().to_string();
39        let core_file_field = crate::core::forms::FileField::from(&filename, temp_file);
40        let temp_path = core_file_field.temp_path.clone();
41
42        Self {
43            filename,
44            core_file_field,
45            temp_path,
46        }
47    }
48}
49
50pub type PostValidator<T> = Box<fn(T) -> Result<T, Vec<String>>>;
51type BoxResult = Box<dyn Any + Sync + Send + 'static>;
52
53pub struct FileField<T> {
54    field_name: String,
55    result: Arc<Mutex<Option<BoxResult>>>,
56    post_validator: Option<PostValidator<T>>,
57    validated: Arc<AtomicBool>,
58    phantom: PhantomData<T>,
59}
60
61impl<T> Clone for FileField<T> {
62    fn clone(&self) -> Self {
63        Self {
64            field_name: self.field_name.clone(),
65            result: self.result.clone(),
66            post_validator: self.post_validator.clone(),
67            validated: self.validated.clone(),
68            phantom: self.phantom.clone(),
69        }
70    }
71}
72
73pub trait ToOptionT {
74    fn from_vec(files: &mut Vec<crate::core::forms::FileField>) -> Option<Self>
75    where
76        Self: Sized;
77
78    fn is_optional() -> bool;
79}
80
81impl ToOptionT for UploadedFile {
82    fn from_vec(files: &mut Vec<crate::core::forms::FileField>) -> Option<Self> {
83        if files.len() > 0 {
84            let file_field = files.remove(0);
85            return Some(UploadedFile::from_core_file_field(file_field));
86        }
87
88        None
89    }
90
91    fn is_optional() -> bool {
92        false
93    }
94}
95
96impl ToOptionT for Option<UploadedFile> {
97    fn from_vec(files: &mut Vec<crate::core::forms::FileField>) -> Option<Self> {
98        if files.len() > 0 {
99            let file_field = files.remove(0);
100            // Outer Some denotes successful conversion.
101            return Some(Some(UploadedFile::from_core_file_field(file_field)));
102        }
103
104        // Return successful conversion but no files are present. So returns actual value as None.
105        Some(None)
106    }
107
108    fn is_optional() -> bool {
109        true
110    }
111}
112
113impl ToOptionT for Vec<UploadedFile> {
114    fn from_vec(files: &mut Vec<crate::core::forms::FileField>) -> Option<Self>
115    where
116        Self: Sized,
117    {
118        if files.len() > 0 {
119            let mut owned_files = vec![];
120
121            for i in (0..files.len()).rev() {
122                let uploaded_file = UploadedFile::from_core_file_field(files.remove(i));
123                owned_files.insert(0, uploaded_file);
124            }
125
126            return Some(owned_files);
127        }
128
129        // Conversion to type T failed.
130        None
131    }
132
133    fn is_optional() -> bool {
134        false
135    }
136}
137
138impl ToOptionT for Option<Vec<UploadedFile>> {
139    fn from_vec(files: &mut Vec<crate::core::forms::FileField>) -> Option<Self>
140    where
141        Self: Sized,
142    {
143        if files.len() > 0 {
144            let mut owned_files = vec![];
145
146            for i in (0..files.len()).rev() {
147                let uploaded_file = UploadedFile::from_core_file_field(files.remove(i));
148                owned_files.insert(0, uploaded_file);
149            }
150
151            return Some(Some(owned_files));
152        }
153
154        // Conversion to type T successful because of optional field. So returns None as result.
155        Some(None)
156    }
157
158    fn is_optional() -> bool {
159        true
160    }
161}
162
163impl<T: Sync + Send + 'static> FileField<T> {
164    pub fn new<S: AsRef<str>>(field_name: S) -> Self {
165        let field_name = field_name.as_ref().to_string();
166        Self {
167            field_name,
168            result: Arc::new(Mutex::new(None)),
169            post_validator: None,
170            validated: Arc::new(AtomicBool::from(false)),
171            phantom: PhantomData,
172        }
173    }
174
175    pub fn post_validate(mut self, callback: fn(T) -> Result<T, Vec<String>>) -> Self {
176        self.post_validator = Some(Box::new(callback));
177        self
178    }
179
180    pub async fn value(self) -> T {
181        if !self.validated.load(Ordering::Relaxed) {
182            panic!("This field is not validated. Please call form.validate() method before accessing value.");
183        }
184
185        let mut result_ref = self.result.lock().await;
186        let result = result_ref.take();
187
188        if let Some(result) = result {
189            match result.downcast::<T>() {
190                Ok(t) => {
191                    return *t;
192                }
193
194                _ => {}
195            };
196        }
197
198        panic!("Unexpected error. Bug in file_field.rs file.");
199    }
200}
201
202impl<T: ToOptionT + Sync + Send + 'static> AbstractFields for FileField<T> {
203    fn field_name(&self) -> FieldResult<String> {
204        let field_name = self.field_name.clone();
205        Box::new(Box::pin(async move { field_name }))
206    }
207
208    fn validate(
209        &mut self,
210        _: &mut FormData,
211        files: &mut Files,
212    ) -> FieldResult<Result<(), Vec<String>>> {
213        let files = files.remove(&self.field_name);
214        let result_ref = self.result.clone();
215        let validated = self.validated.clone();
216        let post_validator = self.post_validator.clone();
217
218        Box::new(Box::pin(async move {
219            let mut errors = vec![];
220
221            let is_optional = T::is_optional();
222
223            let is_empty;
224
225            if let Some(mut files) = files {
226                let mut result = result_ref.lock().await;
227                is_empty = files.is_empty();
228
229                if let Some(t) = T::from_vec(&mut files) {
230                    if let Some(post_validator) = post_validator {
231                        match post_validator(t) {
232                            Ok(t) => {
233                                *result = Some(Box::new(t));
234                            }
235                            Err(custom_errors) => {
236                                errors.extend_from_slice(&custom_errors);
237                            }
238                        }
239                    } else {
240                        *result = Some(Box::new(t));
241                    }
242                }
243            } else {
244                is_empty = true;
245            }
246
247            if !is_optional && is_empty {
248                errors.push("This field is required.".to_string());
249            }
250
251            if errors.len() > 0 {
252                return Err(errors);
253            }
254
255            if is_optional && is_empty {
256                let value_t = T::from_vec(&mut vec![]);
257                if let Some(t) = value_t {
258                    let mut result = result_ref.lock().await;
259                    *result = Some(Box::new(t));
260                }
261            }
262
263            validated.store(true, Ordering::Relaxed);
264            Ok(())
265        }))
266    }
267
268    fn wrap(&self) -> Box<dyn AbstractFields> {
269        Box::new(self.clone())
270    }
271}
272
273#[cfg(test)]
274pub mod tests {
275    use async_tempfile::TempFile;
276    use tokio::io::{AsyncReadExt, AsyncWriteExt};
277
278    use crate::core::forms::{Files, FormData};
279    use crate::forms::fields::AbstractFields;
280
281    use super::{FileField, UploadedFile};
282
283    #[tokio::test]
284    async fn test_file_optional() {
285        let mut form_data = FormData::new();
286        let mut files = Files::new();
287
288        let mut file_field: FileField<Option<UploadedFile>> = FileField::new("file");
289        let result = file_field.validate(&mut form_data, &mut files).await;
290
291        assert_eq!(true, result.is_ok());
292    }
293
294    #[tokio::test]
295    async fn test_file_empty() {
296        let mut form_data = FormData::new();
297        let mut files = Files::new();
298
299        let mut file_field: FileField<UploadedFile> = FileField::new("file");
300        let result = file_field.validate(&mut form_data, &mut files).await;
301
302        assert_eq!(false, result.is_ok());
303    }
304
305    #[tokio::test]
306    async fn test_file_validate() {
307        let mut form_data = FormData::new();
308        let mut files = Files::new();
309
310        let mut temp_file = TempFile::new().await.unwrap();
311        let _ = temp_file.write_all(b"Hello World").await;
312
313        let core_file_field = crate::core::forms::FileField::from(
314            "file.txt".to_string(),
315            temp_file,
316        );
317
318        let mut file_field: FileField<UploadedFile> = FileField::new("file");
319        files.insert("file".to_string(), vec![core_file_field]);
320        let result = file_field.validate(&mut form_data, &mut files).await;
321
322        let path_field = file_field.value().await;
323        let path_buf = path_field.temp_path;
324
325        assert_eq!(true, path_buf.exists());
326        assert_eq!(true, result.is_ok());
327
328        let mut file = tokio::fs::File::open(&path_buf).await.unwrap();
329        let mut content = String::new();
330        let _ = file.read_to_string(&mut content).await;
331        assert_eq!("Hello World".to_string(), content);
332    }
333
334    #[tokio::test]
335    async fn test_file_validate_vec() {
336        let mut form_data = FormData::new();
337        let mut files = Files::new();
338
339        let temp_file = TempFile::new().await.unwrap();
340        let core_file_field = crate::core::forms::FileField::from("file.txt", temp_file);
341
342        let mut file_field: FileField<Vec<UploadedFile>> = FileField::new("file");
343        files.insert("file".to_string(), vec![core_file_field]);
344        let result = file_field.validate(&mut form_data, &mut files).await;
345        assert_eq!(true, result.is_ok());
346
347        let sent_files = file_field.value().await;
348        assert_eq!(1, sent_files.len());
349    }
350
351    #[tokio::test]
352    async fn test_file_validate_vec_optional() {
353        let mut form_data = FormData::new();
354        let mut files = Files::new();
355
356        let temp_file = TempFile::new().await.unwrap();
357        let core_file_field = crate::core::forms::FileField::from(
358            "file.txt".to_string(),
359            temp_file,
360        );
361
362        let mut file_field: FileField<Option<Vec<UploadedFile>>> = FileField::new("file");
363        files.insert("file".to_string(), vec![core_file_field]);
364        let result = file_field.validate(&mut form_data, &mut files).await;
365        assert_eq!(true, result.is_ok());
366
367        let sent_files = file_field.value().await;
368        assert_eq!(true, sent_files.is_some());
369        assert_eq!(1, sent_files.unwrap().len());
370
371        // Empty test
372
373        let mut form_data = FormData::new();
374        let mut files = Files::new();
375
376        let mut file_field: FileField<Option<Vec<UploadedFile>>> = FileField::new("file");
377        let result = file_field.validate(&mut form_data, &mut files).await;
378        assert_eq!(true, result.is_ok());
379        assert_eq!(false, file_field.value().await.is_some());
380    }
381
382    #[tokio::test]
383    async fn test_post_validate() {
384        let mut form_data = FormData::new();
385        let mut files = Files::new();
386
387        let temp_file = TempFile::new().await.unwrap();
388        let core_file_field = crate::core::forms::FileField::from(
389            "file.txt".to_string(),
390            temp_file,
391        );
392
393        let mut file_field: FileField<UploadedFile> =
394            FileField::new("file").post_validate(|file| {
395                if !file.filename.eq("file2.txt") {
396                    return Err(vec!["File name does not equal file2.txt".to_string()]);
397                }
398
399                Ok(file)
400            });
401        files.insert("file".to_string(), vec![core_file_field]);
402        let result = file_field.validate(&mut form_data, &mut files).await;
403        assert_eq!(false, result.is_ok());
404    }
405}