racoon/forms/fields/
file_field.rs1use 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 return Some(Some(UploadedFile::from_core_file_field(file_field)));
102 }
103
104 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 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 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 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}