reqwest_h3/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
185// Can use new methods in web-sys when requiring v0.2.93.
186// > `init.method(m)` to `init.set_method(m)`
187// For now, ignore their deprecation.
188#[allow(deprecated)]
189#[cfg(test)]
190mod tests {
191    use crate::Body;
192    use js_sys::Uint8Array;
193    use wasm_bindgen::prelude::*;
194    use wasm_bindgen_test::*;
195
196    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
197
198    #[wasm_bindgen]
199    extern "C" {
200        // Use `js_namespace` here to bind `console.log(..)` instead of just
201        // `log(..)`
202        #[wasm_bindgen(js_namespace = console)]
203        fn log(s: String);
204    }
205
206    #[wasm_bindgen_test]
207    async fn test_body() {
208        let body = Body::from("TEST");
209        assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
210    }
211
212    #[wasm_bindgen_test]
213    async fn test_body_js_static_str() {
214        let body_value = "TEST";
215        let body = Body::from(body_value);
216
217        let mut init = web_sys::RequestInit::new();
218        init.method("POST");
219        init.body(Some(
220            body.to_js_value()
221                .expect("could not convert body to JsValue")
222                .as_ref(),
223        ));
224
225        let js_req = web_sys::Request::new_with_str_and_init("", &init)
226            .expect("could not create JS request");
227        let text_promise = js_req.text().expect("could not get text promise");
228        let text = crate::wasm::promise::<JsValue>(text_promise)
229            .await
230            .expect("could not get request body as text");
231
232        assert_eq!(text.as_string().expect("text is not a string"), body_value);
233    }
234    #[wasm_bindgen_test]
235    async fn test_body_js_string() {
236        let body_value = "TEST".to_string();
237        let body = Body::from(body_value.clone());
238
239        let mut init = web_sys::RequestInit::new();
240        init.method("POST");
241        init.body(Some(
242            body.to_js_value()
243                .expect("could not convert body to JsValue")
244                .as_ref(),
245        ));
246
247        let js_req = web_sys::Request::new_with_str_and_init("", &init)
248            .expect("could not create JS request");
249        let text_promise = js_req.text().expect("could not get text promise");
250        let text = crate::wasm::promise::<JsValue>(text_promise)
251            .await
252            .expect("could not get request body as text");
253
254        assert_eq!(text.as_string().expect("text is not a string"), body_value);
255    }
256
257    #[wasm_bindgen_test]
258    async fn test_body_js_static_u8_slice() {
259        let body_value: &'static [u8] = b"\x00\x42";
260        let body = Body::from(body_value);
261
262        let mut init = web_sys::RequestInit::new();
263        init.method("POST");
264        init.body(Some(
265            body.to_js_value()
266                .expect("could not convert body to JsValue")
267                .as_ref(),
268        ));
269
270        let js_req = web_sys::Request::new_with_str_and_init("", &init)
271            .expect("could not create JS request");
272
273        let array_buffer_promise = js_req
274            .array_buffer()
275            .expect("could not get array_buffer promise");
276        let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
277            .await
278            .expect("could not get request body as array buffer");
279
280        let v = Uint8Array::new(&array_buffer).to_vec();
281
282        assert_eq!(v, body_value);
283    }
284
285    #[wasm_bindgen_test]
286    async fn test_body_js_vec_u8() {
287        let body_value = vec![0u8, 42];
288        let body = Body::from(body_value.clone());
289
290        let mut init = web_sys::RequestInit::new();
291        init.method("POST");
292        init.body(Some(
293            body.to_js_value()
294                .expect("could not convert body to JsValue")
295                .as_ref(),
296        ));
297
298        let js_req = web_sys::Request::new_with_str_and_init("", &init)
299            .expect("could not create JS request");
300
301        let array_buffer_promise = js_req
302            .array_buffer()
303            .expect("could not get array_buffer promise");
304        let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
305            .await
306            .expect("could not get request body as array buffer");
307
308        let v = Uint8Array::new(&array_buffer).to_vec();
309
310        assert_eq!(v, body_value);
311    }
312}