use std::collections::HashMap; use crate::error::Error; use crate::Date; use crate::DateInit; use crate::Result; use js_sys::Array; use js_sys::Uint8Array; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; /// Representing the options any FormData value can be, a field or a file. #[derive(Debug)] pub enum FormEntry { Field(String), File(File), } /// A [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) representation of the /// request body, providing access to form encoded fields and files. #[derive(Debug)] pub struct FormData(web_sys::FormData); impl FormData { pub fn new() -> Self { Self(web_sys::FormData::new().unwrap()) } /// Returns the first value associated with a given key from within a `FormData` object. pub fn get(&self, name: &str) -> Option { let val = self.0.get(name); if val.is_undefined() { return None; } if val.is_instance_of::() { return Some(FormEntry::File(File(val.into()))); } if let Some(field) = val.as_string() { return Some(FormEntry::Field(field)); } None } /// Returns the first Field value associated with a given key from within a `FormData` object. pub fn get_field(&self, name: &str) -> Option { let val = self.0.get(name); if val.is_undefined() { return None; } if val.is_instance_of::() { return None; } if let Some(field) = val.as_string() { return Some(field); } None } /// Returns a vec of all the values associated with a given key from within a `FormData` object. pub fn get_all(&self, name: &str) -> Option> { let val = self.0.get_all(name); if val.is_undefined() { return None; } if Array::is_array(&val) { return Some( val.to_vec() .into_iter() .map(|val| { if val.is_instance_of::() { return FormEntry::File(File(val.into())); } FormEntry::Field(val.as_string().unwrap_or_default()) }) .collect(), ); } None } /// Returns a boolean stating whether a `FormData` object contains a certain key. pub fn has(&self, name: &str) -> bool { self.0.has(name) } /// Appends a new value onto an existing key inside a `FormData` object, or adds the key if it /// does not already exist. pub fn append(&self, name: &str, value: &str) -> Result<()> { self.0.append_with_str(name, value).map_err(Error::from) } /// Sets a new value for an existing key inside a `FormData` object, or adds the key/value if it /// does not already exist. pub fn set(&self, name: &str, value: &str) -> Result<()> { self.0.set_with_str(name, value).map_err(Error::from) } /// Deletes a key/value pair from a `FormData` object. pub fn delete(&self, name: &str) { self.0.delete(name) } } impl From for FormData { fn from(val: JsValue) -> Self { FormData(val.into()) } } impl From, &dyn AsRef<&str>>> for FormData { fn from(m: HashMap<&dyn AsRef<&str>, &dyn AsRef<&str>>) -> Self { let formdata = FormData::new(); for (k, v) in m { // TODO: determine error case and consider how to handle formdata.set(k.as_ref(), v.as_ref()).unwrap(); } formdata } } impl From for wasm_bindgen::JsValue { fn from(val: FormData) -> Self { val.0.into() } } /// A [File](https://developer.mozilla.org/en-US/docs/Web/API/File) representation used with /// `FormData`. #[derive(Debug)] pub struct File(web_sys::File); impl File { /// Construct a new named file from a buffer. pub fn new(data: impl AsRef<[u8]>, name: &str) -> Self { let data = data.as_ref(); let arr = Uint8Array::new_with_length(data.len() as u32); arr.copy_from(data); // The first parameter of File's constructor must be an ArrayBuffer or similar types // https://developer.mozilla.org/en-US/docs/Web/API/File/File let buffer = arr.buffer(); let file = web_sys::File::new_with_u8_array_sequence(&Array::of1(&buffer), name).unwrap(); Self(file) } /// Get the file name. pub fn name(&self) -> String { self.0.name() } /// Get the file size. pub fn size(&self) -> usize { self.0.size() as usize } /// Get the file type. pub fn type_(&self) -> String { self.0.type_() } /// Read the file from an internal buffer and get the resulting bytes. pub async fn bytes(&self) -> Result> { JsFuture::from(self.0.array_buffer()) .await .map(|val| js_sys::Uint8Array::new(&val).to_vec()) .map_err(|e| { Error::JsError( e.as_string() .unwrap_or_else(|| "failed to read array buffer from file".into()), ) }) } /// Get the last_modified metadata property of the file. pub fn last_modified(&self) -> Date { DateInit::Millis(self.0.last_modified() as u64).into() } } impl From for File { fn from(file: web_sys::File) -> Self { Self(file) } }