Skip to main content

server_fn/response/
browser.rs

1use super::ClientRes;
2use crate::{
3    error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
4    redirect::REDIRECT_HEADER,
5};
6use bytes::Bytes;
7use futures::{Stream, StreamExt};
8pub use gloo_net::http::Response;
9use http::{HeaderMap, HeaderName, HeaderValue};
10use js_sys::Uint8Array;
11use send_wrapper::SendWrapper;
12use std::{future::Future, str::FromStr};
13use wasm_bindgen::JsCast;
14use wasm_streams::ReadableStream;
15
16/// The response to a `fetch` request made in the browser.
17pub struct BrowserResponse(pub(crate) SendWrapper<Response>);
18
19impl BrowserResponse {
20    /// Generate the headers from the internal [`Response`] object.
21    /// This is a workaround for the fact that the `Response` object does not
22    /// have a [`HeaderMap`] directly. This function will iterate over the
23    /// headers and convert them to a [`HeaderMap`].
24    pub fn generate_headers(&self) -> HeaderMap {
25        self.0
26            .headers()
27            .entries()
28            .filter_map(|(key, value)| {
29                let key = HeaderName::from_str(&key).ok()?;
30                let value = HeaderValue::from_str(&value).ok()?;
31                Some((key, value))
32            })
33            .collect()
34    }
35}
36
37impl<E: FromServerFnError> ClientRes<E> for BrowserResponse {
38    fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send {
39        // the browser won't send this async work between threads (because it's single-threaded)
40        // so we can safely wrap this
41        SendWrapper::new(async move {
42            self.0.text().await.map_err(|e| {
43                ServerFnErrorErr::Deserialization(e.to_string())
44                    .into_app_error()
45            })
46        })
47    }
48
49    fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send {
50        // the browser won't send this async work between threads (because it's single-threaded)
51        // so we can safely wrap this
52        SendWrapper::new(async move {
53            self.0.binary().await.map(Bytes::from).map_err(|e| {
54                ServerFnErrorErr::Deserialization(e.to_string())
55                    .into_app_error()
56            })
57        })
58    }
59
60    fn try_into_stream(
61        self,
62    ) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static, E>
63    {
64        let stream = ReadableStream::from_raw(self.0.body().unwrap())
65            .into_stream()
66            .map(|data| match data {
67                Err(e) => {
68                    web_sys::console::error_1(&e);
69                    Err(E::from_server_fn_error(ServerFnErrorErr::Request(
70                        format!("{e:?}"),
71                    ))
72                    .ser())
73                }
74                Ok(data) => {
75                    let data = data.unchecked_into::<Uint8Array>();
76                    let mut buf = Vec::new();
77                    let length = data.length();
78                    buf.resize(length as usize, 0);
79                    data.copy_to(&mut buf);
80                    Ok(Bytes::from(buf))
81                }
82            });
83        Ok(SendWrapper::new(stream))
84    }
85
86    fn status(&self) -> u16 {
87        self.0.status()
88    }
89
90    fn status_text(&self) -> String {
91        self.0.status_text()
92    }
93
94    fn location(&self) -> String {
95        self.0
96            .headers()
97            .get("Location")
98            .unwrap_or_else(|| self.0.url())
99    }
100
101    fn has_redirect(&self) -> bool {
102        self.0.headers().get(REDIRECT_HEADER).is_some()
103    }
104}