Skip to main content

reqwest/wasm/
body.rs

1#[cfg(feature = "multipart")]
2use super::multipart::Form;
3/// dox
4use bytes::Bytes;
5use js_sys::Uint8Array;
6use std::{borrow::Cow, fmt};
7use wasm_bindgen::JsValue;
8
9/// The body of a `Request`.
10///
11/// In most cases, this is not needed directly, as the
12/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
13/// passing many things (like a string or vector of bytes).
14///
15/// [builder]: ./struct.RequestBuilder.html#method.body
16pub struct Body {
17    inner: Inner,
18}
19
20enum Inner {
21    Single(Single),
22    /// MultipartForm holds a multipart/form-data body.
23    #[cfg(feature = "multipart")]
24    MultipartForm(Form),
25}
26
27#[derive(Clone)]
28pub(crate) enum Single {
29    Bytes(Bytes),
30    Text(Cow<'static, str>),
31}
32
33impl Single {
34    fn as_bytes(&self) -> &[u8] {
35        match self {
36            Single::Bytes(bytes) => bytes.as_ref(),
37            Single::Text(text) => text.as_bytes(),
38        }
39    }
40
41    pub(crate) fn to_js_value(&self) -> JsValue {
42        match self {
43            Single::Bytes(bytes) => {
44                let body_bytes: &[u8] = bytes.as_ref();
45                let body_uint8_array: Uint8Array = body_bytes.into();
46                let js_value: &JsValue = body_uint8_array.as_ref();
47                js_value.to_owned()
48            }
49            Single::Text(text) => JsValue::from_str(text),
50        }
51    }
52
53    fn is_empty(&self) -> bool {
54        match self {
55            Single::Bytes(bytes) => bytes.is_empty(),
56            Single::Text(text) => text.is_empty(),
57        }
58    }
59}
60
61impl Body {
62    /// Returns a reference to the internal data of the `Body`.
63    ///
64    /// `None` is returned, if the underlying data is a multipart form.
65    #[inline]
66    pub fn as_bytes(&self) -> Option<&[u8]> {
67        match &self.inner {
68            Inner::Single(single) => Some(single.as_bytes()),
69            #[cfg(feature = "multipart")]
70            Inner::MultipartForm(_) => None,
71        }
72    }
73
74    pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> {
75        match &self.inner {
76            Inner::Single(single) => Ok(single.to_js_value()),
77            #[cfg(feature = "multipart")]
78            Inner::MultipartForm(form) => {
79                let form_data = form.to_form_data()?;
80                let js_value: &JsValue = form_data.as_ref();
81                Ok(js_value.to_owned())
82            }
83        }
84    }
85
86    #[cfg(feature = "multipart")]
87    pub(crate) fn as_single(&self) -> Option<&Single> {
88        match &self.inner {
89            Inner::Single(single) => Some(single),
90            Inner::MultipartForm(_) => None,
91        }
92    }
93
94    #[inline]
95    #[cfg(feature = "multipart")]
96    pub(crate) fn from_form(f: Form) -> Body {
97        Self {
98            inner: Inner::MultipartForm(f),
99        }
100    }
101
102    /// into_part turns a regular body into the body of a multipart/form-data part.
103    #[cfg(feature = "multipart")]
104    pub(crate) fn into_part(self) -> Body {
105        match self.inner {
106            Inner::Single(single) => Self {
107                inner: Inner::Single(single),
108            },
109            Inner::MultipartForm(form) => Self {
110                inner: Inner::MultipartForm(form),
111            },
112        }
113    }
114
115    pub(crate) fn is_empty(&self) -> bool {
116        match &self.inner {
117            Inner::Single(single) => single.is_empty(),
118            #[cfg(feature = "multipart")]
119            Inner::MultipartForm(form) => form.is_empty(),
120        }
121    }
122
123    pub(crate) fn try_clone(&self) -> Option<Body> {
124        match &self.inner {
125            Inner::Single(single) => Some(Self {
126                inner: Inner::Single(single.clone()),
127            }),
128            #[cfg(feature = "multipart")]
129            Inner::MultipartForm(_) => None,
130        }
131    }
132}
133
134impl From<Bytes> for Body {
135    #[inline]
136    fn from(bytes: Bytes) -> Body {
137        Body {
138            inner: Inner::Single(Single::Bytes(bytes)),
139        }
140    }
141}
142
143impl From<Vec<u8>> for Body {
144    #[inline]
145    fn from(vec: Vec<u8>) -> Body {
146        Body {
147            inner: Inner::Single(Single::Bytes(vec.into())),
148        }
149    }
150}
151
152impl From<&'static [u8]> for Body {
153    #[inline]
154    fn from(s: &'static [u8]) -> Body {
155        Body {
156            inner: Inner::Single(Single::Bytes(Bytes::from_static(s))),
157        }
158    }
159}
160
161impl From<String> for Body {
162    #[inline]
163    fn from(s: String) -> Body {
164        Body {
165            inner: Inner::Single(Single::Text(s.into())),
166        }
167    }
168}
169
170impl From<&'static str> for Body {
171    #[inline]
172    fn from(s: &'static str) -> Body {
173        Body {
174            inner: Inner::Single(Single::Text(s.into())),
175        }
176    }
177}
178
179impl fmt::Debug for Body {
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        f.debug_struct("Body").finish()
182    }
183}
184
185impl Default for Body {
186    fn default() -> Body {
187        Body {
188            inner: Inner::Single(Single::Bytes(Bytes::new())),
189        }
190    }
191}
192
193// Can use new methods in web-sys when requiring v0.2.93.
194// > `init.method(m)` to `init.set_method(m)`
195// For now, ignore their deprecation.
196#[allow(deprecated)]
197#[cfg(test)]
198mod tests {
199    use crate::Body;
200    use js_sys::Uint8Array;
201    use wasm_bindgen::prelude::*;
202    use wasm_bindgen_test::*;
203
204    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
205
206    #[wasm_bindgen]
207    extern "C" {
208        // Use `js_namespace` here to bind `console.log(..)` instead of just
209        // `log(..)`
210        #[wasm_bindgen(js_namespace = console)]
211        fn log(s: String);
212    }
213
214    #[wasm_bindgen_test]
215    async fn test_body() {
216        let body = Body::from("TEST");
217        assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
218    }
219
220    #[wasm_bindgen_test]
221    async fn test_body_js_static_str() {
222        let body_value = "TEST";
223        let body = Body::from(body_value);
224
225        let mut init = web_sys::RequestInit::new();
226        init.method("POST");
227        init.body(Some(
228            body.to_js_value()
229                .expect("could not convert body to JsValue")
230                .as_ref(),
231        ));
232
233        let js_req = web_sys::Request::new_with_str_and_init("", &init)
234            .expect("could not create JS request");
235        let text_promise = js_req.text().expect("could not get text promise");
236        let text = crate::wasm::promise::<JsValue>(text_promise)
237            .await
238            .expect("could not get request body as text");
239
240        assert_eq!(text.as_string().expect("text is not a string"), body_value);
241    }
242    #[wasm_bindgen_test]
243    async fn test_body_js_string() {
244        let body_value = "TEST".to_string();
245        let body = Body::from(body_value.clone());
246
247        let mut init = web_sys::RequestInit::new();
248        init.method("POST");
249        init.body(Some(
250            body.to_js_value()
251                .expect("could not convert body to JsValue")
252                .as_ref(),
253        ));
254
255        let js_req = web_sys::Request::new_with_str_and_init("", &init)
256            .expect("could not create JS request");
257        let text_promise = js_req.text().expect("could not get text promise");
258        let text = crate::wasm::promise::<JsValue>(text_promise)
259            .await
260            .expect("could not get request body as text");
261
262        assert_eq!(text.as_string().expect("text is not a string"), body_value);
263    }
264
265    #[wasm_bindgen_test]
266    async fn test_body_js_static_u8_slice() {
267        let body_value: &'static [u8] = b"\x00\x42";
268        let body = Body::from(body_value);
269
270        let mut init = web_sys::RequestInit::new();
271        init.method("POST");
272        init.body(Some(
273            body.to_js_value()
274                .expect("could not convert body to JsValue")
275                .as_ref(),
276        ));
277
278        let js_req = web_sys::Request::new_with_str_and_init("", &init)
279            .expect("could not create JS request");
280
281        let array_buffer_promise = js_req
282            .array_buffer()
283            .expect("could not get array_buffer promise");
284        let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
285            .await
286            .expect("could not get request body as array buffer");
287
288        let v = Uint8Array::new(&array_buffer).to_vec();
289
290        assert_eq!(v, body_value);
291    }
292
293    #[wasm_bindgen_test]
294    async fn test_body_js_vec_u8() {
295        let body_value = vec![0u8, 42];
296        let body = Body::from(body_value.clone());
297
298        let mut init = web_sys::RequestInit::new();
299        init.method("POST");
300        init.body(Some(
301            body.to_js_value()
302                .expect("could not convert body to JsValue")
303                .as_ref(),
304        ));
305
306        let js_req = web_sys::Request::new_with_str_and_init("", &init)
307            .expect("could not create JS request");
308
309        let array_buffer_promise = js_req
310            .array_buffer()
311            .expect("could not get array_buffer promise");
312        let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
313            .await
314            .expect("could not get request body as array buffer");
315
316        let v = Uint8Array::new(&array_buffer).to_vec();
317
318        assert_eq!(v, body_value);
319    }
320}