1 use webapi::blob::IBlob;
2 use webapi::dom_exception::InvalidStateError;
3 use webapi::element::IElement;
4 use webapi::error::TypeError;
5 use webapi::file::File;
6 use webcore::try_from::TryFrom;
7 use webcore::try_from::TryInto;
8 use webcore::value::ConversionError;
9 use webcore::value::Reference;
10 use webcore::value::Value;
11 
12 /// The `FormData` interface provides a way to easily construct a set of key/value pairs
13 /// representing form fields and their values, which can then be easily sent using the
14 /// `XMLHttpRequest.send()` method. It uses the same format a form would use if the encoding type
15 /// were set to `"multipart/form-data"`.
16 ///
17 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
18 // https://xhr.spec.whatwg.org/#formdata
19 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
20 #[reference(instance_of = "FormData")]
21 pub struct FormData( Reference );
22 
23 /// Represents a type of data stores in FormData.
24 #[derive(Clone, Debug, PartialEq)]
25 pub enum FormDataEntry {
26     /// File data
27     File( File ),
28     /// Text data
29     String( String )
30 }
31 
32 error_enum_boilerplate! {
33     FormDataFromElementError,
34     InvalidStateError, TypeError
35 }
36 
37 impl TryFrom< Value > for FormDataEntry {
38     type Error = ConversionError;
39 
try_from(value: Value) -> Result< Self, Self::Error >40     fn try_from(value: Value) -> Result< Self, Self::Error > {
41         let entry = match value {
42             Value::String(s) => FormDataEntry::String(s),
43             Value::Reference(r) => FormDataEntry::File(File(r)),
44             _ => return Err(ConversionError::type_mismatch(&value, "string or reference".into())),
45         };
46 
47         Ok(entry)
48     }
49 }
50 
51 impl TryFrom< Value > for Option< FormDataEntry > {
52     type Error = ConversionError;
53 
try_from(value: Value) -> Result< Self, Self::Error >54     fn try_from(value: Value) -> Result< Self, Self::Error > {
55         let entry = match value {
56             Value::Null|Value::Undefined => None,
57             Value::String(s) => Some(FormDataEntry::String(s)),
58             Value::Reference(r) => Some(FormDataEntry::File(File(r))),
59             _ => return Err(ConversionError::type_mismatch(&value, "null, string or reference".into())),
60         };
61 
62         Ok(entry)
63     }
64 }
65 
66 impl FormData {
67     /// Creates a new `FormData`.
68     ///
69     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData)
new() -> Self70     pub fn new() -> Self {
71         js! (
72             return new FormData();
73         ).try_into().unwrap()
74     }
75 
76     /// Creates a new `FormData` from a form element.
77     ///
78     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData)
from_element<T>( form: &T ) -> Result< Self, FormDataFromElementError > where T: IElement79     pub fn from_element<T>( form: &T ) -> Result< Self, FormDataFromElementError > where T: IElement {
80         js_try! (
81             let form = @{form.as_ref()};
82 
83             if ( ! (form instanceof HTMLFormElement) ) {
84                 throw new TypeError("Argument 1 of FormData::from_element does not implement interface HTMLFormElement.");
85             }
86 
87             return new FormData(form);
88         ).unwrap()
89     }
90 
91     /// Appends a new value onto an existing key, or adds the key if it does not already exist.
92     ///
93     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/append)
94     // https://xhr.spec.whatwg.org/#dom-formdata-append
append_string( &self, name: &str, value: &str )95     pub fn append_string( &self, name: &str, value: &str ) {
96         js! { @(no_return)
97             @{self}.append(@{name}, @{value});
98         }
99     }
100 
101     /// Appends a new blob onto an existing key, or adds the key if it does not already exist.
102     ///
103     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/append)
104     // https://xhr.spec.whatwg.org/#dom-formdata-append-blob
append_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob105     pub fn append_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob {
106         js! { @(no_return)
107             @{self}.append(@{name}, @{value.as_ref()}, @{filename});
108         }
109     }
110 
111     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete)
112     // https://xhr.spec.whatwg.org/#dom-formdata-delete
delete( &self, name: &str )113     pub fn delete( &self, name: &str ) {
114         js! { @(no_return)
115             @{self}.delete(@{name});
116         }
117     }
118 
119     /// Deletes a key and its value(s).
120     ///
121     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/get)
122     // https://xhr.spec.whatwg.org/#dom-formdata-get
get( &self, name: &str ) -> Option< FormDataEntry >123     pub fn get( &self, name: &str ) -> Option< FormDataEntry > {
124         js! (
125             return @{self}.get(@{name});
126         ).try_into().unwrap()
127     }
128 
129     /// Returns all the values associated with a given key.
130     ///
131     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/getAll)
132     // https://xhr.spec.whatwg.org/#dom-formdata-getall
get_all( &self, name: &str ) -> Vec< FormDataEntry >133     pub fn get_all( &self, name: &str ) -> Vec< FormDataEntry > {
134         js! (
135             return @{self}.getAll(@{name});
136         ).try_into().unwrap()
137     }
138 
139     /// Returns a boolean stating whether a `FormData` object contains a certain key.
140     ///
141     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/has)
142     // https://xhr.spec.whatwg.org/#dom-formdata-has
has( &self, name: &str ) -> bool143     pub fn has( &self, name: &str ) -> bool {
144         js! (
145             return @{self}.has(@{name});
146         ).try_into().unwrap()
147     }
148 
149     /// Sets a new value for an existing key, or adds the key/value if it does not already exist.
150     ///
151     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/set)
152     // https://xhr.spec.whatwg.org/#dom-formdata-set
set_string( &self, name: &str, value: &str )153     pub fn set_string( &self, name: &str, value: &str ) {
154         js! { @(no_return)
155             @{self}.set(@{name}, @{value});
156         }
157     }
158 
159     /// Sets a new blob for an existing key, or adds the key/value if it does not already exist.
160     ///
161     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/set)
162     // https://xhr.spec.whatwg.org/#dom-formdata-set-blob
set_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob163     pub fn set_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob {
164         js! { @(no_return)
165             @{self}.set(@{name}, @{value.as_ref()}, @{filename});
166         }
167     }
168 }
169 
170 #[cfg(all(test, feature = "web_test"))]
171 mod tests {
172     use stdweb::webapi::blob::Blob;
173     use stdweb::webapi::element::Element;
174     use stdweb::webcore::try_from::TryInto;
175     use super::*;
176 
form() -> Element177     fn form() -> Element {
178         js!(
179             let form = document.createElement("form");
180 
181             let inputs = [];
182 
183             for (let i = 1; i < 4; i++) {
184                 inputs[i] = document.createElement("input");
185                 inputs[i].name = "key" + i;
186                 inputs[i].value = "value" + i;
187                 form.appendChild(inputs[i]);
188             }
189 
190             inputs[3].name = "key2";
191 
192             return form;
193         ).try_into().unwrap()
194     }
195 
data() -> FormData196     fn data() -> FormData {
197         let form = form();
198 
199         FormData::from_element(&form)
200             .unwrap()
201     }
202 
203     #[test]
test_new()204     fn test_new() {
205         FormData::new();
206     }
207 
208     #[test]
test_from_invalid_element()209     fn test_from_invalid_element() {
210         use webapi::document::document;
211 
212         let div = document().create_element("div")
213             .unwrap();
214 
215         assert!(FormData::from_element(&div).is_err());
216     }
217 
218     #[test]
test_append_string()219     fn test_append_string() {
220         let data = data();
221         assert!(data.get("key0").is_none());
222 
223         data.append_string("key0", "value0");
224         assert_eq!(data.get("key0"), Some(FormDataEntry::String(String::from("value0"))));
225     }
226 
227     #[test]
test_append_blob()228     fn test_append_blob() {
229         let data = data();
230         assert!(data.get("key0").is_none());
231 
232         data.append_blob("blob", &Blob::new(), Some("file.jpg"));
233         assert!(data.get("blob").is_some());
234     }
235 
236     #[test]
test_delete()237     fn test_delete() {
238         let data = data();
239         assert!(data.get("key1").is_some());
240 
241         data.delete("key1");
242         assert!(data.get("key1").is_none());
243     }
244 
245     #[test]
test_get()246     fn test_get() {
247         let data = data();
248 
249         assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value1"))));
250     }
251 
252     #[test]
test_get_all()253     fn test_get_all() {
254         let data = data();
255 
256         assert_eq!(data.get_all("key2"), vec![
257             FormDataEntry::String(String::from("value2")),
258             FormDataEntry::String(String::from("value3"))
259         ]);
260         assert_eq!(data.get_all("unknow"), Vec::<FormDataEntry>::new());
261     }
262 
263     #[test]
test_has()264     fn test_has() {
265         let data = data();
266 
267         assert_eq!(data.has("key1"), true);
268     }
269 
270     #[test]
test_set_string()271     fn test_set_string() {
272         let data = data();
273         assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value1"))));
274 
275         data.set_string("key1", "value");
276         assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value"))));
277     }
278 
279     #[test]
test_set_blob()280     fn test_set_blob() {
281         let data = data();
282         assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value1"))));
283 
284         data.set_blob("key1", &Blob::new(), None);
285         assert!(data.get("key1").is_some());
286     }
287 }
288