feat: make server functions work outside of WASM (#643)

This commit is contained in:
ealmloff 2023-03-09 17:03:57 -06:00 committed by GitHub
parent b085a6c38e
commit 29fb1842a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 7 deletions

View file

@ -60,6 +60,7 @@ features = [
"HashChangeEvent", "HashChangeEvent",
"InputEvent", "InputEvent",
"KeyboardEvent", "KeyboardEvent",
"MessageEvent",
"MouseEvent", "MouseEvent",
"PageTransitionEvent", "PageTransitionEvent",
"PointerEvent", "PointerEvent",

View file

@ -11,9 +11,6 @@ readme = "../README.md"
[dependencies] [dependencies]
server_fn_macro_default = { path = "./server_fn_macro_default", version = "0.2.0" } server_fn_macro_default = { path = "./server_fn_macro_default", version = "0.2.0" }
form_urlencoded = "1" form_urlencoded = "1"
gloo-net = "0.2"
js-sys = "0.3"
lazy_static = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
thiserror = "1" thiserror = "1"
@ -25,5 +22,14 @@ cfg-if = "1"
ciborium = "0.2.0" ciborium = "0.2.0"
xxhash-rust = { version = "0.8.6", features = ["const_xxh64"] } xxhash-rust = { version = "0.8.6", features = ["const_xxh64"] }
const_format = "0.2.30" const_format = "0.2.30"
[target.'cfg(target_arch = "wasm32")'.dependencies]
gloo-net = "0.2"
js-sys = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
reqwest = "0.11.14"
once_cell = "1.17.1"
[features] [features]
ssr = [] ssr = []

View file

@ -34,6 +34,7 @@
//! //!
//! **Important**: All server functions must be registered by calling [ServerFn::register_in] //! **Important**: All server functions must be registered by calling [ServerFn::register_in]
//! somewhere within your `main` function. //! somewhere within your `main` function.
//! **Important**: Before calling a server function on a non-web platform, you must set the server URL by calling [`set_server_url`].
//! //!
//! ```rust,ignore //! ```rust,ignore
//! #[server(ReadFromDB)] //! #[server(ReadFromDB)]
@ -54,6 +55,8 @@
//! //!
//! // make sure you've registered it somewhere in main //! // make sure you've registered it somewhere in main
//! fn main() { //! fn main() {
//! // for non-web apps, you must set the server URL manually
//! server_fn::set_server_url("http://localhost:3000");
//! _ = ReadFromDB::register(); //! _ = ReadFromDB::register();
//! } //! }
//! ``` //! ```
@ -353,8 +356,9 @@ where
T: serde::Serialize + serde::de::DeserializeOwned + Sized, T: serde::Serialize + serde::de::DeserializeOwned + Sized,
{ {
use ciborium::ser::into_writer; use ciborium::ser::into_writer;
use js_sys::Uint8Array;
use serde_json::Deserializer as JSONDeserializer; use serde_json::Deserializer as JSONDeserializer;
#[cfg(not(target_arch = "wasm32"))]
let url = format!("{}{}", get_server_url(), url);
#[derive(Debug)] #[derive(Debug)]
enum Payload { enum Payload {
@ -384,10 +388,11 @@ where
Encoding::Cbor => "application/cbor", Encoding::Cbor => "application/cbor",
}; };
#[cfg(target_arch = "wasm32")]
let resp = match args_encoded { let resp = match args_encoded {
Payload::Binary(b) => { Payload::Binary(b) => {
let slice_ref: &[u8] = &b; let slice_ref: &[u8] = &b;
let js_array = Uint8Array::from(slice_ref).buffer(); let js_array = js_sys::Uint8Array::from(slice_ref).buffer();
gloo_net::http::Request::post(url) gloo_net::http::Request::post(url)
.header("Content-Type", content_type_header) .header("Content-Type", content_type_header)
.header("Accept", accept_header) .header("Accept", accept_header)
@ -404,20 +409,55 @@ where
.await .await
.map_err(|e| ServerFnError::Request(e.to_string()))?, .map_err(|e| ServerFnError::Request(e.to_string()))?,
}; };
#[cfg(not(target_arch = "wasm32"))]
let resp = match args_encoded {
Payload::Binary(b) => CLIENT
.post(url)
.header("Content-Type", content_type_header)
.header("Accept", accept_header)
.body(b)
.send()
.await
.map_err(|e| ServerFnError::Request(e.to_string()))?,
Payload::Url(s) => CLIENT
.post(url)
.header("Content-Type", content_type_header)
.header("Accept", accept_header)
.body(s)
.send()
.await
.map_err(|e| ServerFnError::Request(e.to_string()))?,
};
// check for error status // check for error status
let status = resp.status(); let status = resp.status();
#[cfg(not(target_arch = "wasm32"))]
let status = status.as_u16();
if (500..=599).contains(&status) { if (500..=599).contains(&status) {
return Err(ServerFnError::ServerError(resp.status_text())); #[cfg(target_arch = "wasm32")]
let status_text = resp.status_text();
#[cfg(not(target_arch = "wasm32"))]
let status_text = status.to_string();
return Err(ServerFnError::ServerError(status_text));
} }
if enc == Encoding::Cbor { if enc == Encoding::Cbor {
#[cfg(target_arch = "wasm32")]
let binary = resp let binary = resp
.binary() .binary()
.await .await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?; .map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
#[cfg(target_arch = "wasm32")]
let binary = binary.as_slice();
#[cfg(not(target_arch = "wasm32"))]
let binary = resp
.bytes()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
#[cfg(not(target_arch = "wasm32"))]
let binary = binary.as_ref();
ciborium::de::from_reader(binary.as_slice()) ciborium::de::from_reader(binary)
.map_err(|e| ServerFnError::Deserialization(e.to_string())) .map_err(|e| ServerFnError::Deserialization(e.to_string()))
} else { } else {
let text = resp let text = resp
@ -430,3 +470,25 @@ where
.map_err(|e| ServerFnError::Deserialization(e.to_string())) .map_err(|e| ServerFnError::Deserialization(e.to_string()))
} }
} }
// Lazily initialize the client to be reused for all server function calls.
#[cfg(any(all(not(feature = "ssr"), not(target_arch = "wasm32")), doc))]
static CLIENT: once_cell::sync::Lazy<reqwest::Client> =
once_cell::sync::Lazy::new(|| reqwest::Client::new());
#[cfg(any(all(not(feature = "ssr"), not(target_arch = "wasm32")), doc))]
static ROOT_URL: once_cell::sync::OnceCell<&'static str> =
once_cell::sync::OnceCell::new();
#[cfg(any(all(not(feature = "ssr"), not(target_arch = "wasm32")), doc))]
/// Set the root server url that all server function paths are relative to for the client. On WASM this will default to the origin.
pub fn set_server_url(url: &'static str) {
ROOT_URL.set(url).unwrap();
}
#[cfg(all(not(feature = "ssr"), not(target_arch = "wasm32")))]
fn get_server_url() -> &'static str {
ROOT_URL
.get()
.expect("Call set_root_url before calling a server function.")
}