gloo_net/http/
response.rs

1use std::{convert::From, fmt};
2
3use crate::{js_to_error, Error};
4use js_sys::{ArrayBuffer, Uint8Array};
5use wasm_bindgen::{JsCast, JsValue};
6use wasm_bindgen_futures::JsFuture;
7use web_sys::ResponseInit;
8
9use crate::http::Headers;
10#[cfg(feature = "json")]
11#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
12use serde::de::DeserializeOwned;
13
14/// The [`Request`]'s response
15pub struct Response(web_sys::Response);
16
17impl Response {
18    /// Returns an instance of response builder
19    pub fn builder() -> ResponseBuilder {
20        ResponseBuilder::new()
21    }
22    /// The type read-only property of the Response interface contains the type of the response.
23    ///
24    /// It can be one of the following:
25    ///
26    ///  - basic: Normal, same origin response, with all headers exposed except “Set-Cookie” and
27    ///    “Set-Cookie2″.
28    ///  - cors: Response was received from a valid cross-origin request. Certain headers and the
29    ///    body may be accessed.
30    ///  - error: Network error. No useful information describing the error is available. The
31    ///    Response’s status is 0, headers are empty and immutable. This is the type for a Response
32    ///    obtained from Response.error().
33    ///  - opaque: Response for “no-cors” request to cross-origin resource. Severely restricted.
34    ///  - opaqueredirect: The fetch request was made with redirect: "manual". The Response's
35    ///    status is 0, headers are empty, body is null and trailer is empty.
36    pub fn type_(&self) -> web_sys::ResponseType {
37        self.0.type_()
38    }
39
40    /// The URL of the response.
41    ///
42    /// The returned value will be the final URL obtained after any redirects.
43    pub fn url(&self) -> String {
44        self.0.url()
45    }
46
47    /// Whether or not this response is the result of a request you made which was redirected.
48    pub fn redirected(&self) -> bool {
49        self.0.redirected()
50    }
51
52    /// the [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) of the
53    /// response.
54    pub fn status(&self) -> u16 {
55        self.0.status()
56    }
57
58    /// Whether the [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
59    /// was a success code (in the range `200 - 299`).
60    pub fn ok(&self) -> bool {
61        self.0.ok()
62    }
63
64    /// The status message corresponding to the
65    /// [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) from
66    /// `Response::status`.
67    ///
68    /// For example, this would be 'OK' for a status code 200, 'Continue' for 100, or 'Not Found'
69    /// for 404.
70    pub fn status_text(&self) -> String {
71        self.0.status_text()
72    }
73
74    /// Gets the headers.
75    pub fn headers(&self) -> Headers {
76        Headers::from_raw(self.0.headers())
77    }
78
79    /// Has the response body been consumed?
80    ///
81    /// If true, then any future attempts to consume the body will error.
82    pub fn body_used(&self) -> bool {
83        self.0.body_used()
84    }
85
86    /// Gets the body.
87    pub fn body(&self) -> Option<web_sys::ReadableStream> {
88        self.0.body()
89    }
90
91    /// Reads the response to completion, returning it as `FormData`.
92    pub async fn form_data(&self) -> Result<web_sys::FormData, Error> {
93        let promise = self.0.form_data().map_err(js_to_error)?;
94        let val = JsFuture::from(promise).await.map_err(js_to_error)?;
95        Ok(web_sys::FormData::from(val))
96    }
97
98    /// Reads the response to completion, parsing it as JSON.
99    #[cfg(feature = "json")]
100    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
101    pub async fn json<T: DeserializeOwned>(&self) -> Result<T, Error> {
102        serde_json::from_str::<T>(&self.text().await?).map_err(Error::from)
103    }
104
105    /// Reads the response as a String.
106    pub async fn text(&self) -> Result<String, Error> {
107        let promise = self.0.text().unwrap();
108        let val = JsFuture::from(promise).await.map_err(js_to_error)?;
109        let string = js_sys::JsString::from(val);
110        Ok(String::from(&string))
111    }
112
113    /// Gets the binary response
114    ///
115    /// This works by obtaining the response as an `ArrayBuffer`, creating a `Uint8Array` from it
116    /// and then converting it to `Vec<u8>`
117    pub async fn binary(&self) -> Result<Vec<u8>, Error> {
118        let promise = self.0.array_buffer().map_err(js_to_error)?;
119        let array_buffer: ArrayBuffer = JsFuture::from(promise)
120            .await
121            .map_err(js_to_error)?
122            .unchecked_into();
123        let typed_buff: Uint8Array = Uint8Array::new(&array_buffer);
124        let mut body = vec![0; typed_buff.length() as usize];
125        typed_buff.copy_to(&mut body);
126        Ok(body)
127    }
128}
129
130impl From<web_sys::Response> for Response {
131    fn from(raw: web_sys::Response) -> Self {
132        Self(raw)
133    }
134}
135
136impl From<Response> for web_sys::Response {
137    fn from(res: Response) -> Self {
138        res.0
139    }
140}
141
142impl fmt::Debug for Response {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        f.debug_struct("Response")
145            .field("url", &self.url())
146            .field("redirected", &self.redirected())
147            .field("status", &self.status())
148            .field("headers", &self.headers())
149            .field("body_used", &self.body_used())
150            .finish_non_exhaustive()
151    }
152}
153
154/// A writable wrapper around `web_sys::Reponse`: an http response to be used with the `fetch` API
155/// on a server side javascript runtime
156pub struct ResponseBuilder {
157    headers: Headers,
158    options: web_sys::ResponseInit,
159}
160
161impl ResponseBuilder {
162    /// Creates a new response object which defaults to status 200
163    /// for other status codes, call Self.status(400)
164    pub fn new() -> Self {
165        Self::default()
166    }
167
168    /// Replace _all_ the headers.
169    pub fn headers(mut self, headers: Headers) -> Self {
170        self.headers = headers;
171        self
172    }
173
174    /// Sets a header.
175    pub fn header(self, key: &str, value: &str) -> Self {
176        self.headers.set(key, value);
177        self
178    }
179
180    /// Set the status code
181    pub fn status(mut self, status: u16) -> Self {
182        self.options.status(status);
183        self
184    }
185
186    /// Set the status text
187    pub fn status_text(mut self, status_text: &str) -> Self {
188        self.options.status_text(status_text);
189        self
190    }
191
192    /// A convenience method to set JSON as response body
193    ///
194    /// # Note
195    ///
196    /// This method also sets the `Content-Type` header to `application/json`
197    #[cfg(feature = "json")]
198    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
199    pub fn json<T: serde::Serialize + ?Sized>(self, value: &T) -> Result<Response, Error> {
200        let json = serde_json::to_string(value)?;
201        self.header("Content-Type", "application/json")
202            .body(Some(json.as_str()))
203    }
204
205    /// Set the response body and return the response
206    pub fn body<T>(mut self, data: T) -> Result<Response, Error>
207    where
208        T: IntoRawResponse,
209    {
210        self.options.headers(&self.headers.into_raw());
211        let init = self.options;
212
213        data.into_raw(init).map(Response).map_err(js_to_error)
214    }
215}
216
217impl IntoRawResponse for Option<&web_sys::Blob> {
218    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
219        web_sys::Response::new_with_opt_blob_and_init(self, &init)
220    }
221}
222
223impl IntoRawResponse for Option<&js_sys::Object> {
224    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
225        web_sys::Response::new_with_opt_buffer_source_and_init(self, &init)
226    }
227}
228
229impl IntoRawResponse for Option<&mut [u8]> {
230    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
231        web_sys::Response::new_with_opt_u8_array_and_init(self, &init)
232    }
233}
234
235impl IntoRawResponse for Option<&web_sys::FormData> {
236    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
237        web_sys::Response::new_with_opt_form_data_and_init(self, &init)
238    }
239}
240
241impl IntoRawResponse for Option<&web_sys::UrlSearchParams> {
242    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
243        web_sys::Response::new_with_opt_url_search_params_and_init(self, &init)
244    }
245}
246
247impl IntoRawResponse for Option<&str> {
248    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
249        web_sys::Response::new_with_opt_str_and_init(self, &init)
250    }
251}
252
253impl IntoRawResponse for Option<&web_sys::ReadableStream> {
254    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
255        web_sys::Response::new_with_opt_readable_stream_and_init(self, &init)
256    }
257}
258
259/// trait which allow consuming self into a raw web_sys::Response
260pub trait IntoRawResponse {
261    /// A method which converts `self` and a [`web_sys::ResponseInit`] into a result to a
262    /// [`web_sys::Response`].
263    fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue>;
264}
265
266impl Default for ResponseBuilder {
267    fn default() -> Self {
268        Self {
269            headers: Headers::new(),
270            options: web_sys::ResponseInit::new(),
271        }
272    }
273}
274
275impl fmt::Debug for ResponseBuilder {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        f.debug_struct("ResponseBuilder")
278            .field("headers", &self.headers)
279            .finish_non_exhaustive()
280    }
281}