1use std::collections::HashMap;
2
3use crate::error::Error;
4use crate::Date;
5use crate::DateInit;
6use crate::Result;
7
8use js_sys::Array;
9use js_sys::Uint8Array;
10use wasm_bindgen::prelude::*;
11use wasm_bindgen::JsCast;
12use wasm_bindgen_futures::JsFuture;
13
14#[derive(Debug)]
16pub enum FormEntry {
17 Field(String),
18 File(File),
19}
20
21#[derive(Debug)]
24pub struct FormData(web_sys::FormData);
25
26impl FormData {
27 pub fn new() -> Self {
28 Self(web_sys::FormData::new().unwrap())
29 }
30
31 pub fn get(&self, name: &str) -> Option<FormEntry> {
33 let val = self.0.get(name);
34 if val.is_undefined() {
35 return None;
36 }
37
38 if val.is_instance_of::<web_sys::File>() {
39 return Some(FormEntry::File(File(val.into())));
40 }
41
42 if let Some(field) = val.as_string() {
43 return Some(FormEntry::Field(field));
44 }
45
46 None
47 }
48
49 pub fn get_field(&self, name: &str) -> Option<String> {
51 let val = self.0.get(name);
52 if val.is_undefined() {
53 return None;
54 }
55
56 if val.is_instance_of::<web_sys::File>() {
57 return None;
58 }
59
60 if let Some(field) = val.as_string() {
61 return Some(field);
62 }
63
64 None
65 }
66
67 pub fn get_all(&self, name: &str) -> Option<Vec<FormEntry>> {
69 let val = self.0.get_all(name);
70 if val.is_undefined() {
71 return None;
72 }
73
74 if Array::is_array(&val) {
75 return Some(
76 val.to_vec()
77 .into_iter()
78 .map(|val| {
79 if val.is_instance_of::<web_sys::File>() {
80 return FormEntry::File(File(val.into()));
81 }
82
83 FormEntry::Field(val.as_string().unwrap_or_default())
84 })
85 .collect(),
86 );
87 }
88
89 None
90 }
91
92 pub fn has(&self, name: &str) -> bool {
94 self.0.has(name)
95 }
96
97 pub fn append(&self, name: &str, value: &str) -> Result<()> {
100 self.0.append_with_str(name, value).map_err(Error::from)
101 }
102
103 pub fn set(&self, name: &str, value: &str) -> Result<()> {
106 self.0.set_with_str(name, value).map_err(Error::from)
107 }
108
109 pub fn delete(&self, name: &str) {
111 self.0.delete(name)
112 }
113}
114
115impl From<JsValue> for FormData {
116 fn from(val: JsValue) -> Self {
117 FormData(val.into())
118 }
119}
120
121impl From<HashMap<&dyn AsRef<&str>, &dyn AsRef<&str>>> for FormData {
122 fn from(m: HashMap<&dyn AsRef<&str>, &dyn AsRef<&str>>) -> Self {
123 let formdata = FormData::new();
124 for (k, v) in m {
125 formdata.set(k.as_ref(), v.as_ref()).unwrap();
127 }
128 formdata
129 }
130}
131
132impl From<FormData> for wasm_bindgen::JsValue {
133 fn from(val: FormData) -> Self {
134 val.0.into()
135 }
136}
137
138#[derive(Debug)]
141pub struct File(web_sys::File);
142
143impl File {
144 pub fn new(data: impl AsRef<[u8]>, name: &str) -> Self {
146 let data = data.as_ref();
147 let arr = Uint8Array::new_with_length(data.len() as u32);
148 arr.copy_from(data);
149
150 let buffer = arr.buffer();
153 let file = web_sys::File::new_with_u8_array_sequence(&Array::of1(&buffer), name).unwrap();
154
155 Self(file)
156 }
157
158 pub fn name(&self) -> String {
160 self.0.name()
161 }
162
163 pub fn size(&self) -> usize {
165 self.0.size() as usize
166 }
167
168 pub fn type_(&self) -> String {
170 self.0.type_()
171 }
172
173 pub async fn bytes(&self) -> Result<Vec<u8>> {
175 JsFuture::from(self.0.array_buffer())
176 .await
177 .map(|val| js_sys::Uint8Array::new(&val).to_vec())
178 .map_err(|e| {
179 Error::JsError(
180 e.as_string()
181 .unwrap_or_else(|| "failed to read array buffer from file".into()),
182 )
183 })
184 }
185
186 pub fn last_modified(&self) -> Date {
188 DateInit::Millis(self.0.last_modified() as u64).into()
189 }
190}
191
192impl From<web_sys::File> for File {
193 fn from(file: web_sys::File) -> Self {
194 Self(file)
195 }
196}