axum_test/multipart/
part.rs1use anyhow::Context;
2use bytes::Bytes;
3use http::HeaderName;
4use http::HeaderValue;
5use mime::Mime;
6use std::fmt::Debug;
7use std::fmt::Display;
8
9#[derive(Debug, Clone)]
16pub struct Part {
17 pub(crate) bytes: Bytes,
18 pub(crate) file_name: Option<String>,
19 pub(crate) mime_type: Mime,
20 pub(crate) headers: Vec<(HeaderName, HeaderValue)>,
21}
22
23impl Part {
24 pub fn text<T>(text: T) -> Self
28 where
29 T: Display,
30 {
31 let bytes = text.to_string().into_bytes().into();
32
33 Self::new(bytes, mime::TEXT_PLAIN)
34 }
35
36 pub fn bytes<B>(bytes: B) -> Self
40 where
41 B: Into<Bytes>,
42 {
43 Self::new(bytes.into(), mime::APPLICATION_OCTET_STREAM)
44 }
45
46 fn new(bytes: Bytes, mime_type: Mime) -> Self {
47 Self {
48 bytes,
49 file_name: None,
50 mime_type,
51 headers: Default::default(),
52 }
53 }
54
55 pub fn file_name<T>(mut self, file_name: T) -> Self
59 where
60 T: Display,
61 {
62 self.file_name = Some(file_name.to_string());
63 self
64 }
65
66 pub fn mime_type<M>(mut self, mime_type: M) -> Self
72 where
73 M: AsRef<str>,
74 {
75 let raw_mime_type = mime_type.as_ref();
76 let parsed_mime_type = raw_mime_type
77 .parse()
78 .with_context(|| format!("Failed to parse '{raw_mime_type}' as a Mime type"))
79 .unwrap();
80
81 self.mime_type = parsed_mime_type;
82
83 self
84 }
85
86 pub fn add_header<N, V>(mut self, name: N, value: V) -> Self
116 where
117 N: TryInto<HeaderName>,
118 N::Error: Debug,
119 V: TryInto<HeaderValue>,
120 V::Error: Debug,
121 {
122 let header_name: HeaderName = name
123 .try_into()
124 .expect("Failed to convert header name to HeaderName");
125 let header_value: HeaderValue = value
126 .try_into()
127 .expect("Failed to convert header vlue to HeaderValue");
128
129 self.headers.push((header_name, header_value));
130 self
131 }
132}
133
134#[cfg(test)]
135mod test_text {
136 use super::*;
137
138 #[test]
139 fn it_should_contain_text_given() {
140 let part = Part::text("some_text");
141
142 let output = String::from_utf8_lossy(&part.bytes);
143 assert_eq!(output, "some_text");
144 }
145
146 #[test]
147 fn it_should_use_mime_type_text() {
148 let part = Part::text("some_text");
149 assert_eq!(part.mime_type, mime::TEXT_PLAIN);
150 }
151}
152
153#[cfg(test)]
154mod test_byes {
155 use super::*;
156
157 #[test]
158 fn it_should_contain_bytes_given() {
159 let bytes = "some_text".as_bytes();
160 let part = Part::bytes(bytes);
161
162 let output = String::from_utf8_lossy(&part.bytes);
163 assert_eq!(output, "some_text");
164 }
165
166 #[test]
167 fn it_should_use_mime_type_octet_stream() {
168 let bytes = "some_text".as_bytes();
169 let part = Part::bytes(bytes);
170
171 assert_eq!(part.mime_type, mime::APPLICATION_OCTET_STREAM);
172 }
173}
174
175#[cfg(test)]
176mod test_file_name {
177 use super::*;
178
179 #[test]
180 fn it_should_use_file_name_given() {
181 let mut part = Part::text("some_text");
182
183 assert_eq!(part.file_name, None);
184 part = part.file_name("my-text.txt");
185 assert_eq!(part.file_name, Some("my-text.txt".to_string()));
186 }
187}
188
189#[cfg(test)]
190mod test_mime_type {
191 use super::*;
192
193 #[test]
194 fn it_should_use_mime_type_set() {
195 let mut part = Part::text("some_text");
196
197 assert_eq!(part.mime_type, mime::TEXT_PLAIN);
198 part = part.mime_type("application/json");
199 assert_eq!(part.mime_type, mime::APPLICATION_JSON);
200 }
201
202 #[test]
203 #[should_panic]
204 fn it_should_error_if_invalid_mime_type() {
205 let part = Part::text("some_text");
206 part.mime_type("🦊");
207
208 assert!(false);
209 }
210}