[go: up one dir, main page]

worker/
formdata.rs

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/// Representing the options any FormData value can be, a field or a file.
15#[derive(Debug)]
16pub enum FormEntry {
17    Field(String),
18    File(File),
19}
20
21/// A [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) representation of the
22/// request body, providing access to form encoded fields and files.
23#[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    /// Returns the first value associated with a given key from within a `FormData` object.
32    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    /// Returns the first Field value associated with a given key from within a `FormData` object.
50    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    /// Returns a vec of all the values associated with a given key from within a `FormData` object.
68    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    /// Returns a boolean stating whether a `FormData` object contains a certain key.
93    pub fn has(&self, name: &str) -> bool {
94        self.0.has(name)
95    }
96
97    /// Appends a new value onto an existing key inside a `FormData` object, or adds the key if it
98    /// does not already exist.
99    pub fn append(&self, name: &str, value: &str) -> Result<()> {
100        self.0.append_with_str(name, value).map_err(Error::from)
101    }
102
103    /// Sets a new value for an existing key inside a `FormData` object, or adds the key/value if it
104    /// does not already exist.
105    pub fn set(&self, name: &str, value: &str) -> Result<()> {
106        self.0.set_with_str(name, value).map_err(Error::from)
107    }
108
109    /// Deletes a key/value pair from a `FormData` object.
110    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            // TODO: determine error case and consider how to handle
126            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/// A [File](https://developer.mozilla.org/en-US/docs/Web/API/File) representation used with
139/// `FormData`.
140#[derive(Debug)]
141pub struct File(web_sys::File);
142
143impl File {
144    /// Construct a new named file from a buffer.
145    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        // The first parameter of File's constructor must be an ArrayBuffer or similar types
151        // https://developer.mozilla.org/en-US/docs/Web/API/File/File
152        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    /// Get the file name.
159    pub fn name(&self) -> String {
160        self.0.name()
161    }
162
163    /// Get the file size.
164    pub fn size(&self) -> usize {
165        self.0.size() as usize
166    }
167
168    /// Get the file type.
169    pub fn type_(&self) -> String {
170        self.0.type_()
171    }
172
173    /// Read the file from an internal buffer and get the resulting bytes.
174    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    /// Get the last_modified metadata property of the file.
187    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}