1 //! multipart/form-data 2 use std::borrow::Cow; 3 use std::fmt; 4 5 use http::HeaderMap; 6 use mime_guess::Mime; 7 use web_sys::FormData; 8 9 use super::Body; 10 11 /// An async multipart/form-data request. 12 pub struct Form { 13 inner: FormParts<Part>, 14 } 15 16 impl Form { is_empty(&self) -> bool17 pub(crate) fn is_empty(&self) -> bool { 18 self.inner.fields.is_empty() 19 } 20 } 21 22 /// A field in a multipart form. 23 pub struct Part { 24 meta: PartMetadata, 25 value: Body, 26 } 27 28 pub(crate) struct FormParts<P> { 29 pub(crate) fields: Vec<(Cow<'static, str>, P)>, 30 } 31 32 pub(crate) struct PartMetadata { 33 mime: Option<Mime>, 34 file_name: Option<Cow<'static, str>>, 35 pub(crate) headers: HeaderMap, 36 } 37 38 pub(crate) trait PartProps { metadata(&self) -> &PartMetadata39 fn metadata(&self) -> &PartMetadata; 40 } 41 42 // ===== impl Form ===== 43 44 impl Default for Form { default() -> Self45 fn default() -> Self { 46 Self::new() 47 } 48 } 49 50 impl Form { 51 /// Creates a new async Form without any content. new() -> Form52 pub fn new() -> Form { 53 Form { 54 inner: FormParts::new(), 55 } 56 } 57 58 /// Add a data field with supplied name and value. 59 /// 60 /// # Examples 61 /// 62 /// ``` 63 /// let form = reqwest::multipart::Form::new() 64 /// .text("username", "seanmonstar") 65 /// .text("password", "secret"); 66 /// ``` text<T, U>(self, name: T, value: U) -> Form where T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>,67 pub fn text<T, U>(self, name: T, value: U) -> Form 68 where 69 T: Into<Cow<'static, str>>, 70 U: Into<Cow<'static, str>>, 71 { 72 self.part(name, Part::text(value)) 73 } 74 75 /// Adds a customized Part. part<T>(self, name: T, part: Part) -> Form where T: Into<Cow<'static, str>>,76 pub fn part<T>(self, name: T, part: Part) -> Form 77 where 78 T: Into<Cow<'static, str>>, 79 { 80 self.with_inner(move |inner| inner.part(name, part)) 81 } 82 with_inner<F>(self, func: F) -> Self where F: FnOnce(FormParts<Part>) -> FormParts<Part>,83 fn with_inner<F>(self, func: F) -> Self 84 where 85 F: FnOnce(FormParts<Part>) -> FormParts<Part>, 86 { 87 Form { 88 inner: func(self.inner), 89 } 90 } 91 to_form_data(&self) -> crate::Result<FormData>92 pub(crate) fn to_form_data(&self) -> crate::Result<FormData> { 93 let form = FormData::new() 94 .map_err(crate::error::wasm) 95 .map_err(crate::error::builder)?; 96 97 for (name, part) in self.inner.fields.iter() { 98 let blob = part.blob()?; 99 100 if let Some(file_name) = &part.metadata().file_name { 101 form.append_with_blob_and_filename(name, &blob, &file_name) 102 } else { 103 form.append_with_blob(name, &blob) 104 } 105 .map_err(crate::error::wasm) 106 .map_err(crate::error::builder)?; 107 } 108 Ok(form) 109 } 110 } 111 112 impl fmt::Debug for Form { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result113 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 114 self.inner.fmt_fields("Form", f) 115 } 116 } 117 118 // ===== impl Part ===== 119 120 impl Part { 121 /// Makes a text parameter. text<T>(value: T) -> Part where T: Into<Cow<'static, str>>,122 pub fn text<T>(value: T) -> Part 123 where 124 T: Into<Cow<'static, str>>, 125 { 126 let body = match value.into() { 127 Cow::Borrowed(slice) => Body::from(slice), 128 Cow::Owned(string) => Body::from(string), 129 }; 130 Part::new(body) 131 } 132 133 /// Makes a new parameter from arbitrary bytes. bytes<T>(value: T) -> Part where T: Into<Cow<'static, [u8]>>,134 pub fn bytes<T>(value: T) -> Part 135 where 136 T: Into<Cow<'static, [u8]>>, 137 { 138 let body = match value.into() { 139 Cow::Borrowed(slice) => Body::from(slice), 140 Cow::Owned(vec) => Body::from(vec), 141 }; 142 Part::new(body) 143 } 144 145 /// Makes a new parameter from an arbitrary stream. stream<T: Into<Body>>(value: T) -> Part146 pub fn stream<T: Into<Body>>(value: T) -> Part { 147 Part::new(value.into()) 148 } 149 new(value: Body) -> Part150 fn new(value: Body) -> Part { 151 Part { 152 meta: PartMetadata::new(), 153 value, 154 } 155 } 156 157 /// Tries to set the mime of this part. mime_str(self, mime: &str) -> crate::Result<Part>158 pub fn mime_str(self, mime: &str) -> crate::Result<Part> { 159 Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) 160 } 161 162 // Re-export when mime 0.4 is available, with split MediaType/MediaRange. mime(self, mime: Mime) -> Part163 fn mime(self, mime: Mime) -> Part { 164 self.with_inner(move |inner| inner.mime(mime)) 165 } 166 167 /// Sets the filename, builder style. file_name<T>(self, filename: T) -> Part where T: Into<Cow<'static, str>>,168 pub fn file_name<T>(self, filename: T) -> Part 169 where 170 T: Into<Cow<'static, str>>, 171 { 172 self.with_inner(move |inner| inner.file_name(filename)) 173 } 174 with_inner<F>(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata,175 fn with_inner<F>(self, func: F) -> Self 176 where 177 F: FnOnce(PartMetadata) -> PartMetadata, 178 { 179 Part { 180 meta: func(self.meta), 181 value: self.value, 182 } 183 } 184 blob(&self) -> crate::Result<web_sys::Blob>185 fn blob(&self) -> crate::Result<web_sys::Blob> { 186 use web_sys::Blob; 187 use web_sys::BlobPropertyBag; 188 let mut properties = BlobPropertyBag::new(); 189 if let Some(mime) = &self.meta.mime { 190 properties.type_(mime.as_ref()); 191 } 192 193 // BUG: the return value of to_js_value() is not valid if 194 // it is a Multipart variant. 195 let js_value = self.value.to_js_value()?; 196 Blob::new_with_u8_array_sequence_and_options(&js_value, &properties) 197 .map_err(crate::error::wasm) 198 .map_err(crate::error::builder) 199 } 200 } 201 202 impl fmt::Debug for Part { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result203 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 204 let mut dbg = f.debug_struct("Part"); 205 dbg.field("value", &self.value); 206 self.meta.fmt_fields(&mut dbg); 207 dbg.finish() 208 } 209 } 210 211 impl PartProps for Part { metadata(&self) -> &PartMetadata212 fn metadata(&self) -> &PartMetadata { 213 &self.meta 214 } 215 } 216 217 // ===== impl FormParts ===== 218 219 impl<P: PartProps> FormParts<P> { new() -> Self220 pub(crate) fn new() -> Self { 221 FormParts { fields: Vec::new() } 222 } 223 224 /// Adds a customized Part. part<T>(mut self, name: T, part: P) -> Self where T: Into<Cow<'static, str>>,225 pub(crate) fn part<T>(mut self, name: T, part: P) -> Self 226 where 227 T: Into<Cow<'static, str>>, 228 { 229 self.fields.push((name.into(), part)); 230 self 231 } 232 } 233 234 impl<P: fmt::Debug> FormParts<P> { fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result235 pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result { 236 f.debug_struct(ty_name) 237 .field("parts", &self.fields) 238 .finish() 239 } 240 } 241 242 // ===== impl PartMetadata ===== 243 244 impl PartMetadata { new() -> Self245 pub(crate) fn new() -> Self { 246 PartMetadata { 247 mime: None, 248 file_name: None, 249 headers: HeaderMap::default(), 250 } 251 } 252 mime(mut self, mime: Mime) -> Self253 pub(crate) fn mime(mut self, mime: Mime) -> Self { 254 self.mime = Some(mime); 255 self 256 } 257 file_name<T>(mut self, filename: T) -> Self where T: Into<Cow<'static, str>>,258 pub(crate) fn file_name<T>(mut self, filename: T) -> Self 259 where 260 T: Into<Cow<'static, str>>, 261 { 262 self.file_name = Some(filename.into()); 263 self 264 } 265 } 266 267 impl PartMetadata { fmt_fields<'f, 'fa, 'fb>( &self, debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, ) -> &'f mut fmt::DebugStruct<'fa, 'fb>268 pub(crate) fn fmt_fields<'f, 'fa, 'fb>( 269 &self, 270 debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, 271 ) -> &'f mut fmt::DebugStruct<'fa, 'fb> { 272 debug_struct 273 .field("mime", &self.mime) 274 .field("file_name", &self.file_name) 275 .field("headers", &self.headers) 276 } 277 } 278 279 #[cfg(test)] 280 mod tests {} 281