server_fn/
client.rs

1use crate::{error::ServerFnError, request::ClientReq, response::ClientRes};
2use std::{future::Future, sync::OnceLock};
3
4static ROOT_URL: OnceLock<&'static str> = OnceLock::new();
5
6/// Set the root server URL that all server function paths are relative to for the client.
7///
8/// If this is not set, it defaults to the origin.
9pub fn set_server_url(url: &'static str) {
10    ROOT_URL.set(url).unwrap();
11}
12
13/// Returns the root server URL for all server functions.
14pub fn get_server_url() -> &'static str {
15    ROOT_URL.get().copied().unwrap_or("")
16}
17
18/// A client defines a pair of request/response types and the logic to send
19/// and receive them.
20///
21/// This trait is implemented for things like a browser `fetch` request or for
22/// the `reqwest` trait. It should almost never be necessary to implement it
23/// yourself, unless you’re trying to use an alternative HTTP crate on the client side.
24pub trait Client<CustErr> {
25    /// The type of a request sent by this client.
26    type Request: ClientReq<CustErr> + Send;
27    /// The type of a response received by this client.
28    type Response: ClientRes<CustErr> + Send;
29
30    /// Sends the request and receives a response.
31    fn send(
32        req: Self::Request,
33    ) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>> + Send;
34}
35
36#[cfg(feature = "browser")]
37/// Implements [`Client`] for a `fetch` request in the browser.
38pub mod browser {
39    use super::Client;
40    use crate::{
41        error::ServerFnError,
42        request::browser::{BrowserRequest, RequestInner},
43        response::browser::BrowserResponse,
44    };
45    use send_wrapper::SendWrapper;
46    use std::future::Future;
47
48    /// Implements [`Client`] for a `fetch` request in the browser.    
49    pub struct BrowserClient;
50
51    impl<CustErr> Client<CustErr> for BrowserClient {
52        type Request = BrowserRequest;
53        type Response = BrowserResponse;
54
55        fn send(
56            req: Self::Request,
57        ) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
58               + Send {
59            SendWrapper::new(async move {
60                let req = req.0.take();
61                let RequestInner {
62                    request,
63                    mut abort_ctrl,
64                } = req;
65                let res = request
66                    .send()
67                    .await
68                    .map(|res| BrowserResponse(SendWrapper::new(res)))
69                    .map_err(|e| ServerFnError::Request(e.to_string()));
70
71                // at this point, the future has successfully resolved without being dropped, so we
72                // can prevent the `AbortController` from firing
73                if let Some(ctrl) = abort_ctrl.as_mut() {
74                    ctrl.prevent_cancellation();
75                }
76                res
77            })
78        }
79    }
80}
81
82#[cfg(feature = "reqwest")]
83/// Implements [`Client`] for a request made by [`reqwest`].
84pub mod reqwest {
85    use super::Client;
86    use crate::{error::ServerFnError, request::reqwest::CLIENT};
87    use futures::TryFutureExt;
88    use reqwest::{Request, Response};
89    use std::future::Future;
90
91    /// Implements [`Client`] for a request made by [`reqwest`].
92    pub struct ReqwestClient;
93
94    impl<CustErr> Client<CustErr> for ReqwestClient {
95        type Request = Request;
96        type Response = Response;
97
98        fn send(
99            req: Self::Request,
100        ) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
101               + Send {
102            CLIENT
103                .execute(req)
104                .map_err(|e| ServerFnError::Request(e.to_string()))
105        }
106    }
107}