medullah_multipart/
file_input.rs1use crate::content_disposition::ContentDisposition;
2use crate::file_validator::Validator;
3use crate::result::{MultipartError, MultipartResult};
4use crate::{FileRules, Multipart};
5use ntex::http::HeaderMap;
6use ntex::util::Bytes;
7use std::collections::HashMap;
8use std::path::Path;
9
10#[derive(Debug, Default, Clone)]
11pub struct FileInput {
12 pub file_name: String,
13 pub field_name: String,
14 pub size: usize, pub content_type: String,
16 pub bytes: Vec<Bytes>,
17 pub extension: Option<String>,
18 pub content_disposition: ContentDisposition,
19}
20
21impl FileInput {
22 pub fn create(headers: &HeaderMap, cd: ContentDisposition) -> MultipartResult<Self> {
24 let content_type = Self::get_content_type(headers)?;
25
26 let variables = cd.get_variables();
27 let field = variables.get("name").cloned().unwrap();
28 let name = variables.get("filename").cloned().unwrap();
29
30 let binding = name.clone();
31 let split_name: Vec<&str> = binding.split('.').collect();
32
33 Ok(Self {
34 content_type,
35 size: 0,
36 bytes: vec![],
37 file_name: name,
38 field_name: field,
39 extension: split_name.last().map(|e| e.to_string()),
40 content_disposition: cd,
41 })
42 }
43
44 pub async fn save(&self, path: impl AsRef<Path>) -> MultipartResult<()> {
46 Multipart::save_file(self, path).await
47 }
48
49 pub fn validate(&self, rules: FileRules) -> MultipartResult<()> {
50 let mut files = HashMap::new();
51 files.insert(self.field_name.clone(), vec![self.clone()]);
52
53 Validator::new()
54 .add_rule(&self.field_name, rules)
55 .validate(&files)
56 }
57
58 pub fn calculate_size(&self) -> usize {
60 self.bytes.iter().map(|b| b.len()).sum()
61 }
62
63 pub fn human_size(&self) -> String {
65 let size_in_bytes = self.calculate_size();
66 Self::format_size(size_in_bytes)
67 }
68
69 fn get_content_type(headers: &HeaderMap) -> MultipartResult<String> {
71 match headers.get("content-type") {
72 None => Err(MultipartError::NoContentType(
73 "Empty content type".to_string(),
74 )),
75 Some(header) => header
76 .to_str()
77 .map(|v| v.to_string())
78 .map_err(|err| MultipartError::NoContentType(err.to_string())),
79 }
80 }
81
82 pub fn format_size(size_in_bytes: usize) -> String {
84 const KILOBYTE: usize = 1024;
85 const MEGABYTE: usize = KILOBYTE * 1024;
86 const GIGABYTE: usize = MEGABYTE * 1024;
87
88 if size_in_bytes >= GIGABYTE {
89 format!("{:.2} GB", size_in_bytes as f64 / GIGABYTE as f64)
90 } else if size_in_bytes >= MEGABYTE {
91 format!("{:.2} MB", size_in_bytes as f64 / MEGABYTE as f64)
92 } else if size_in_bytes >= KILOBYTE {
93 format!("{:.2} KB", size_in_bytes as f64 / KILOBYTE as f64)
94 } else {
95 format!("{} bytes", size_in_bytes)
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
106 fn test_human_readable_size() {
107 let file_input = FileInput {
108 size: 1048576, bytes: vec![Bytes::from_static(&[0; 1048576])], ..Default::default()
111 };
112
113 assert_eq!(file_input.human_size(), "1.00 MB");
115
116 let file_input = FileInput {
117 size: 1572864, bytes: vec![Bytes::from_static(&[0; 1572864])], ..Default::default()
120 };
121
122 assert_eq!(file_input.human_size(), "1.50 MB");
124
125 let file_input = FileInput {
126 size: 102400, bytes: vec![Bytes::from_static(&[0; 102400])], ..Default::default()
129 };
130
131 assert_eq!(file_input.human_size(), "100.00 KB");
133
134 let file_input = FileInput {
135 size: 1014, bytes: vec![Bytes::from_static(&[0; 1014])], ..Default::default()
138 };
139
140 assert_eq!(file_input.human_size(), "1014 bytes");
142 }
143
144 #[test]
146 fn test_calculate_size() {
147 let file_input = FileInput {
148 bytes: vec![
149 Bytes::from_static(&[0; 1024]), Bytes::from_static(&[0; 2048]), Bytes::from_static(&[0; 4096]), ],
153 ..Default::default()
154 };
155
156 assert_eq!(file_input.calculate_size(), 1024 + 2048 + 4096);
157 }
158}