mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
bump server functions
This commit is contained in:
parent
6879507652
commit
e745212157
13 changed files with 297 additions and 616 deletions
134
Cargo.lock
generated
134
Cargo.lock
generated
|
@ -622,6 +622,7 @@ dependencies = [
|
|||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
|
@ -2692,7 +2693,7 @@ dependencies = [
|
|||
"dioxus-router-macro",
|
||||
"dioxus-ssr",
|
||||
"gloo",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -4086,7 +4087,7 @@ dependencies = [
|
|||
"gloo-render",
|
||||
"gloo-storage",
|
||||
"gloo-timers",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"gloo-worker",
|
||||
]
|
||||
|
||||
|
@ -4096,7 +4097,7 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
|
@ -4142,7 +4143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f"
|
||||
dependencies = [
|
||||
"gloo-events",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_urlencoded",
|
||||
|
@ -4151,26 +4152,6 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-net"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-net"
|
||||
version = "0.3.1"
|
||||
|
@ -4180,7 +4161,28 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"http 0.2.11",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-net"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"gloo-utils 0.2.0",
|
||||
"http 0.2.11",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
|
@ -4208,7 +4210,7 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -4240,6 +4242,19 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-worker"
|
||||
version = "0.2.1"
|
||||
|
@ -4249,7 +4264,7 @@ dependencies = [
|
|||
"anymap2",
|
||||
"bincode",
|
||||
"gloo-console",
|
||||
"gloo-utils",
|
||||
"gloo-utils 0.1.7",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
|
@ -6008,6 +6023,24 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin 0.9.8",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "names"
|
||||
version = "0.14.0"
|
||||
|
@ -7608,6 +7641,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
|
@ -8097,6 +8131,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "send_wrapper"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "separator"
|
||||
version = "0.4.1"
|
||||
|
@ -8260,49 +8303,58 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "server_fn"
|
||||
version = "0.5.7"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c265de965fe48e09ad8899d0ab1ffebdfa1a9914e4de5ff107b07bd94cf7541"
|
||||
checksum = "97fab54d9dd2d7e9eba4efccac41d2ec3e7c6e9973d14c0486d662a32662320c"
|
||||
dependencies = [
|
||||
"ciborium",
|
||||
"axum",
|
||||
"bytes",
|
||||
"const_format",
|
||||
"gloo-net 0.2.6",
|
||||
"dashmap",
|
||||
"futures",
|
||||
"gloo-net 0.5.0",
|
||||
"http 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.1.0",
|
||||
"inventory",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"reqwest",
|
||||
"send_wrapper",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"server_fn_macro_default",
|
||||
"syn 2.0.49",
|
||||
"thiserror",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro"
|
||||
version = "0.5.7"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f77000541a62ceeec01eef3ee0f86c155c33dac5fae750ad04a40852c6d5469a"
|
||||
checksum = "3be6011b586a0665546b7ced372b0be690d9e005d3f8524795da2843274d7720"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"proc-macro-error",
|
||||
"convert_case 0.6.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.49",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro_default"
|
||||
version = "0.5.7"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a3353f22e2bcc451074d4feaa37317d9d17dff11d4311928384734ea17ab9ca"
|
||||
checksum = "752ed78ec49132d154b922cf5ab6485680cab039a75740c48ea2db621ad481da"
|
||||
dependencies = [
|
||||
"server_fn_macro",
|
||||
"syn 2.0.49",
|
||||
|
|
|
@ -12,9 +12,8 @@ resolver = "2"
|
|||
|
||||
[dependencies]
|
||||
# server functions
|
||||
server_fn = { version = "0.5.2", default-features = false }
|
||||
dioxus_server_macro = { workspace = true }
|
||||
|
||||
server_fn = { version = "0.6.5", features = ["json", "url", "browser"], default-features = false }
|
||||
dioxus_server_macro = { workspace = true, version = "0.6.5", default-features = false }
|
||||
|
||||
# axum
|
||||
axum = { workspace = true, features = ["ws", "macros"], default-features = false, optional = true }
|
||||
|
@ -71,7 +70,7 @@ desktop = ["dioxus-desktop"]
|
|||
mobile = ["dioxus-mobile"]
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
axum = ["dep:axum", "tower-http", "server"]
|
||||
axum = ["dep:axum", "tower-http", "server", "server_fn/axum", "dioxus_server_macro/axum"]
|
||||
server = [
|
||||
"server_fn/ssr",
|
||||
"dioxus_server_macro/server",
|
||||
|
|
|
@ -57,61 +57,25 @@
|
|||
use axum::{
|
||||
body::{self, Body},
|
||||
extract::State,
|
||||
handler::Handler,
|
||||
http::{Request, Response, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use dioxus_lib::prelude::VirtualDom;
|
||||
use server_fn::{Encoding, ServerFunctionRegistry};
|
||||
use server_fn::error::NoCustomError;
|
||||
use server_fn::error::ServerFnErrorSerde;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use axum::routing::*;
|
||||
use http::header::*;
|
||||
|
||||
use crate::{
|
||||
prelude::*, render::SSRState, serve_config::ServeConfig, server_context::DioxusServerContext,
|
||||
server_fn::collection::DioxusServerFnRegistry,
|
||||
};
|
||||
|
||||
/// A extension trait with utilities for integrating Dioxus with your Axum router.
|
||||
pub trait DioxusRouterExt<S> {
|
||||
/// Registers server functions with a custom handler function. This allows you to pass custom context to your server functions by generating a [`DioxusServerContext`] from the request.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use dioxus_lib::prelude::*;
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
/// axum::Server::bind(&addr)
|
||||
/// .serve(
|
||||
/// axum::Router::new()
|
||||
/// .register_server_fns_with_handler("", |func| {
|
||||
/// move |req: Request<Body>| async move {
|
||||
/// let (parts, body) = req.into_parts();
|
||||
/// let parts: Arc<http::request::Parts> = Arc::new(parts.into());
|
||||
/// let server_context = DioxusServerContext::new(parts.clone());
|
||||
/// server_fn_handler(server_context, func.clone(), parts, body).await
|
||||
/// }
|
||||
/// })
|
||||
/// .into_make_service(),
|
||||
/// )
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
fn register_server_fns_with_handler<H, T>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler<T, S>,
|
||||
T: 'static,
|
||||
S: Clone + Send + Sync + 'static;
|
||||
|
||||
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -133,7 +97,7 @@ pub trait DioxusRouterExt<S> {
|
|||
/// .unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self;
|
||||
fn register_server_fns(self) -> Self;
|
||||
|
||||
/// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
|
||||
///
|
||||
|
@ -218,7 +182,6 @@ pub trait DioxusRouterExt<S> {
|
|||
/// ```
|
||||
fn serve_dioxus_application(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
cfg: impl Into<ServeConfig>,
|
||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> Self;
|
||||
|
@ -228,51 +191,20 @@ impl<S> DioxusRouterExt<S> for Router<S>
|
|||
where
|
||||
S: Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn register_server_fns_with_handler<H, T>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler<T, S>,
|
||||
T: 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for server_fn_path in DioxusServerFnRegistry::paths_registered() {
|
||||
let func = DioxusServerFnRegistry::get(server_fn_path).unwrap();
|
||||
let full_route = format!("{server_fn_route}/{server_fn_path}");
|
||||
match func.encoding() {
|
||||
Encoding::Url | Encoding::Cbor => {
|
||||
router = router.route(&full_route, post(handler(func)));
|
||||
}
|
||||
Encoding::GetJSON | Encoding::GetCBOR => {
|
||||
router = router.route(&full_route, get(handler(func)));
|
||||
}
|
||||
}
|
||||
}
|
||||
router
|
||||
}
|
||||
fn register_server_fns(mut self) -> Self {
|
||||
use http::method::Method;
|
||||
|
||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self {
|
||||
self.register_server_fns_with_handler(server_fn_route, |func| {
|
||||
move |req: Request<Body>| {
|
||||
let mut service = crate::server_fn_service(Default::default(), func);
|
||||
async move {
|
||||
let (req, body) = req.into_parts();
|
||||
let req = Request::from_parts(req, body);
|
||||
let res = service.run(req);
|
||||
match res.await {
|
||||
Ok(res) => Ok::<_, std::convert::Infallible>(res.map(|b| b.into())),
|
||||
Err(e) => {
|
||||
let mut res = Response::new(Body::from(e.to_string()));
|
||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
for (path, method) in server_fn::axum::server_fn_paths() {
|
||||
let handler = move |req| handle_server_fns_inner(path, ||{}, req);
|
||||
self = match method {
|
||||
Method::GET => self.route(path, get(handler)),
|
||||
Method::POST => self.route(path, post(handler)),
|
||||
Method::PUT => self.route(path, put(handler)),
|
||||
_ => todo!()
|
||||
};
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn serve_static_assets(mut self, assets_path: impl Into<std::path::PathBuf>) -> Self {
|
||||
|
@ -317,7 +249,6 @@ where
|
|||
|
||||
fn serve_dioxus_application(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
cfg: impl Into<ServeConfig>,
|
||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
|
@ -327,7 +258,7 @@ where
|
|||
// Add server functions and render index.html
|
||||
self.serve_static_assets(cfg.assets_path.clone())
|
||||
.connect_hot_reload()
|
||||
.register_server_fns(server_fn_route)
|
||||
.register_server_fns()
|
||||
.fallback(get(render_handler).with_state((cfg, Arc::new(build_virtual_dom), ssr_state)))
|
||||
}
|
||||
|
||||
|
@ -516,3 +447,104 @@ pub async fn hot_reload_handler(ws: axum::extract::WebSocketUpgrade) -> impl Int
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
|
||||
use once_cell::sync::OnceCell;
|
||||
static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
|
||||
LOCAL_POOL
|
||||
.get_or_init(|| {
|
||||
tokio_util::task::LocalPoolHandle::new(
|
||||
std::thread::available_parallelism()
|
||||
.map(Into::into)
|
||||
.unwrap_or(1),
|
||||
)
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// A handler for Dioxus server functions. This will run the server function and return the result.
|
||||
async fn handle_server_fns_inner(
|
||||
path: &str,
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
req: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
use server_fn::middleware::Service;
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let path_string = path.to_string();
|
||||
|
||||
get_local_pool().spawn_pinned(move || async move {
|
||||
let (parts, body) = req.into_parts();
|
||||
let req = Request::from_parts(parts.clone(), body);
|
||||
|
||||
|
||||
let res = if let Some(mut service) =
|
||||
server_fn::axum::get_server_fn_service(&path_string)
|
||||
{
|
||||
|
||||
let server_context = DioxusServerContext::new(Arc::new(RwLock::new(parts)));
|
||||
additional_context();
|
||||
|
||||
// store Accepts and Referrer in case we need them for redirect (below)
|
||||
let accepts_html = req
|
||||
.headers()
|
||||
.get(ACCEPT)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(|v| v.contains("text/html"))
|
||||
.unwrap_or(false);
|
||||
let referrer = req.headers().get(REFERER).cloned();
|
||||
|
||||
// actually run the server fn
|
||||
let mut res = service.run(req).await;
|
||||
|
||||
|
||||
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to to Referer
|
||||
if accepts_html {
|
||||
if let Some(referrer) = referrer {
|
||||
let has_location = res.headers().get(LOCATION).is_some();
|
||||
if !has_location {
|
||||
*res.status_mut() = StatusCode::FOUND;
|
||||
res.headers_mut().insert(LOCATION, referrer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply the response parts from the server context to the response
|
||||
let mut res_options = server_context.response_parts_mut().unwrap();
|
||||
res.headers_mut().extend(res_options.headers.drain());
|
||||
|
||||
Ok(res)
|
||||
} else {
|
||||
Response::builder().status(StatusCode::BAD_REQUEST).body(
|
||||
{
|
||||
#[cfg(target_family = "wasm")]
|
||||
{
|
||||
Body::from(format!(
|
||||
"No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
|
||||
))
|
||||
}
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
Body::from(format!(
|
||||
"No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.",
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.expect("could not build Response");
|
||||
|
||||
_ = tx.send(res);
|
||||
});
|
||||
|
||||
rx.await.unwrap_or_else(|e| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ServerFnError::<NoCustomError>::ServerError(e.to_string())
|
||||
.ser()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.into_response()
|
||||
})
|
||||
}
|
|
@ -129,7 +129,7 @@ impl Config {
|
|||
use tower::ServiceBuilder;
|
||||
|
||||
let ssr_state = SSRState::new(&cfg);
|
||||
let router = axum::Router::new().register_server_fns(server_fn_route);
|
||||
let router = axum::Router::new().register_server_fns();
|
||||
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
||||
let router = router
|
||||
.serve_static_assets(cfg.assets_path.clone())
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
use std::pin::Pin;
|
||||
use tracing_futures::Instrument;
|
||||
|
||||
use http::{Request, Response};
|
||||
|
||||
/// A layer that wraps a service. This can be used to add additional information to the request, or response on top of some other service
|
||||
pub trait Layer: Send + Sync + 'static {
|
||||
/// Wrap a boxed service with this layer
|
||||
fn layer(&self, inner: BoxedService) -> BoxedService;
|
||||
}
|
||||
|
||||
impl<L> Layer for L
|
||||
where
|
||||
L: tower_layer::Layer<BoxedService> + Sync + Send + 'static,
|
||||
L::Service: Service + Send + 'static,
|
||||
{
|
||||
fn layer(&self, inner: BoxedService) -> BoxedService {
|
||||
BoxedService(Box::new(self.layer(inner)))
|
||||
}
|
||||
}
|
||||
|
||||
type MyBody = axum::body::Body;
|
||||
|
||||
/// A service is a function that takes a request and returns an async response
|
||||
pub trait Service {
|
||||
/// Run the service and produce a future that resolves to a response
|
||||
fn run(
|
||||
&mut self,
|
||||
req: http::Request<MyBody>,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
|
||||
+ Send,
|
||||
>,
|
||||
>;
|
||||
}
|
||||
|
||||
impl<S> Service for S
|
||||
where
|
||||
S: tower::Service<http::Request<MyBody>, Response = Response<MyBody>>,
|
||||
S::Future: Send + 'static,
|
||||
S::Error: Into<server_fn::ServerFnError>,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
req: http::Request<MyBody>,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
|
||||
+ Send,
|
||||
>,
|
||||
> {
|
||||
let fut = self.call(req).instrument(tracing::trace_span!(
|
||||
"service",
|
||||
"{}",
|
||||
std::any::type_name::<S>()
|
||||
));
|
||||
Box::pin(async move { fut.await.map_err(|err| err.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
/// A boxed service is a type-erased service that can be used without knowing the underlying type
|
||||
pub struct BoxedService(pub Box<dyn Service + Send>);
|
||||
|
||||
impl tower::Service<http::Request<MyBody>> for BoxedService {
|
||||
type Response = http::Response<MyBody>;
|
||||
type Error = server_fn::ServerFnError;
|
||||
type Future = Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<http::Response<MyBody>, server_fn::ServerFnError>,
|
||||
> + Send,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<MyBody>) -> Self::Future {
|
||||
self.0.run(req)
|
||||
}
|
||||
}
|
|
@ -12,21 +12,14 @@ mod html_storage;
|
|||
#[cfg(feature = "axum")]
|
||||
mod axum_adapter;
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
#[cfg(feature = "server")]
|
||||
pub use server_fn::service::{server_fn_service, ServerFnHandler};
|
||||
|
||||
mod config;
|
||||
mod hooks;
|
||||
pub mod launch;
|
||||
mod server_fn;
|
||||
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "server"))]
|
||||
mod hot_reload;
|
||||
pub use config::*;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
mod layer;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
mod render;
|
||||
|
@ -50,10 +43,6 @@ pub mod prelude {
|
|||
#[cfg_attr(docsrs, doc(cfg(not(feature = "server"))))]
|
||||
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::layer::{Layer, Service};
|
||||
|
||||
#[cfg(all(feature = "server", feature = "router"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "router"))))]
|
||||
pub use crate::render::pre_cache_static_routes_with_props;
|
||||
|
@ -79,17 +68,13 @@ pub mod prelude {
|
|||
pub use crate::server_context::{
|
||||
extract, server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
|
||||
};
|
||||
pub use crate::server_fn::DioxusServerFn;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::server_fn::collection::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction};
|
||||
pub use dioxus_server_macro::*;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||
|
||||
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
||||
pub use dioxus_server_macro::*;
|
||||
}
|
||||
|
||||
// // Warn users about overlapping features
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
#[cfg(feature = "server")]
|
||||
pub(crate)mod collection;
|
||||
#[cfg(feature = "server")]
|
||||
pub mod service;
|
||||
|
||||
/// Defines a "server function." A server function can be called from the server or the client,
|
||||
/// but the body of its code will only be run on the server, i.e., if a crate feature `ssr` is enabled.
|
||||
///
|
||||
/// Server functions are created using the `server` macro.
|
||||
///
|
||||
/// The set of server functions
|
||||
/// can be queried on the server for routing purposes by calling [server_fn::ServerFunctionRegistry::get].
|
||||
///
|
||||
/// Technically, the trait is implemented on a type that describes the server function's arguments, not the function itself.
|
||||
pub trait DioxusServerFn: server_fn::ServerFn<()> {
|
||||
/// Registers the server function, allowing the client to query it by URL.
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
fn register_explicit() -> Result<(), server_fn::ServerFnError> {
|
||||
Self::register_in_explicit::<crate::server_fn::collection::DioxusServerFnRegistry>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DioxusServerFn for T where T: server_fn::ServerFn<()> {}
|
|
@ -1,154 +0,0 @@
|
|||
//! # Server function Service
|
||||
|
||||
//! This module defines a service that can be used to handle server functions.
|
||||
|
||||
use http::StatusCode;
|
||||
use server_fn::{Encoding, Payload};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::server_fn::collection::MIDDLEWARE;
|
||||
use crate::{
|
||||
layer::{BoxedService, Service},
|
||||
prelude::{DioxusServerContext, ProvideServerContext},
|
||||
};
|
||||
|
||||
type AxumBody = axum::body::Body;
|
||||
|
||||
/// Create a server function handler with the given server context and server function.
|
||||
pub fn server_fn_service(
|
||||
context: DioxusServerContext,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
) -> crate::layer::BoxedService {
|
||||
let prefix = function.prefix().to_string();
|
||||
let url = function.url().to_string();
|
||||
if let Some(middleware) = MIDDLEWARE.get(&(&prefix, &url)) {
|
||||
let mut service = BoxedService(Box::new(ServerFnHandler::new(context, function)));
|
||||
for middleware in middleware {
|
||||
service = middleware.layer(service);
|
||||
}
|
||||
service
|
||||
} else {
|
||||
BoxedService(Box::new(ServerFnHandler::new(context, function)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response.
|
||||
pub struct ServerFnHandler {
|
||||
server_context: DioxusServerContext,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
}
|
||||
|
||||
impl ServerFnHandler {
|
||||
/// Create a new server function handler with the given server context and server function.
|
||||
pub fn new(
|
||||
server_context: impl Into<DioxusServerContext>,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
) -> Self {
|
||||
let server_context = server_context.into();
|
||||
Self {
|
||||
server_context,
|
||||
function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for ServerFnHandler {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: http::Request<AxumBody>,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<http::Response<AxumBody>, server_fn::ServerFnError>,
|
||||
> + Send,
|
||||
>,
|
||||
> {
|
||||
let Self {
|
||||
server_context,
|
||||
function,
|
||||
} = self.clone();
|
||||
Box::pin(async move {
|
||||
let query = req.uri().query().unwrap_or_default().as_bytes().to_vec();
|
||||
let (parts, body) = req.into_parts();
|
||||
let body = axum::body::to_bytes(body, usize::MAX).await?.to_vec();
|
||||
let headers = &parts.headers;
|
||||
let accept_header = headers.get("Accept").cloned();
|
||||
let parts = Arc::new(RwLock::new(parts));
|
||||
|
||||
// Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime
|
||||
let pool = get_local_pool();
|
||||
let result = pool
|
||||
.spawn_pinned({
|
||||
let function = function.clone();
|
||||
let mut server_context = server_context.clone();
|
||||
server_context.parts = parts;
|
||||
move || async move {
|
||||
let data = match function.encoding() {
|
||||
Encoding::Url | Encoding::Cbor => &body,
|
||||
Encoding::GetJSON | Encoding::GetCBOR => &query,
|
||||
};
|
||||
let server_function_future = function.call((), data);
|
||||
let server_function_future = ProvideServerContext::new(
|
||||
server_function_future,
|
||||
server_context.clone(),
|
||||
);
|
||||
server_function_future.await
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
let mut res = http::Response::builder();
|
||||
|
||||
// Set the headers from the server context
|
||||
let parts = server_context.response_parts().unwrap();
|
||||
*res.headers_mut().expect("empty headers should be valid") = parts.headers.clone();
|
||||
|
||||
let serialized = result?;
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
let accept_header = accept_header.as_ref().and_then(|value| value.to_str().ok());
|
||||
if accept_header == Some("application/json")
|
||||
|| accept_header
|
||||
== Some(
|
||||
"application/\
|
||||
x-www-form-urlencoded",
|
||||
)
|
||||
|| accept_header == Some("application/cbor")
|
||||
{
|
||||
res = res.status(StatusCode::OK);
|
||||
}
|
||||
|
||||
Ok(match serialized {
|
||||
Payload::Binary(data) => {
|
||||
res = res.header("Content-Type", "application/cbor");
|
||||
res.body(data.into())?
|
||||
}
|
||||
Payload::Url(data) => {
|
||||
res = res.header(
|
||||
"Content-Type",
|
||||
"application/\
|
||||
x-www-form-urlencoded",
|
||||
);
|
||||
res.body(data.into())?
|
||||
}
|
||||
Payload::Json(data) => {
|
||||
res = res.header("Content-Type", "application/json");
|
||||
res.body(data.into())?
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
|
||||
use once_cell::sync::OnceCell;
|
||||
static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
|
||||
LOCAL_POOL
|
||||
.get_or_init(|| {
|
||||
tokio_util::task::LocalPoolHandle::new(
|
||||
std::thread::available_parallelism()
|
||||
.map(Into::into)
|
||||
.unwrap_or(1),
|
||||
)
|
||||
})
|
||||
.clone()
|
||||
}
|
|
@ -43,6 +43,7 @@ dioxus = { workspace = true }
|
|||
|
||||
[features]
|
||||
default = ["hot-reload"]
|
||||
axum = ["dep:axum"]
|
||||
hot-reload = ["dioxus-hot-reload"]
|
||||
|
||||
[[example]]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use dioxus_core::*;
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
pub type Config = crate::Config<axum::Router>;
|
||||
|
||||
/// Launches the WebView and runs the event loop, with configuration and root props.
|
||||
|
|
|
@ -15,6 +15,7 @@ mod config;
|
|||
mod eval;
|
||||
mod events;
|
||||
pub use config::*;
|
||||
#[cfg(feature = "axum")]
|
||||
pub mod launch;
|
||||
|
||||
pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}
|
||||
|
|
|
@ -17,11 +17,13 @@ proc-macro2 = "^1.0.63"
|
|||
quote = "^1.0.26"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
convert_case = "^0.6.0"
|
||||
server_fn_macro = "^0.5.2"
|
||||
server_fn_macro = "^0.6.5"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
actix = ["server_fn_macro/actix"]
|
||||
axum = ["server_fn_macro/axum"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
server = ["server_fn_macro/ssr"]
|
||||
|
|
|
@ -2,209 +2,83 @@
|
|||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
use convert_case::{Case, Converter};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Literal;
|
||||
use quote::{ToTokens, __private::TokenStream as TokenStream2};
|
||||
use server_fn_macro::*;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Ident, ItemFn, Token,
|
||||
};
|
||||
//! This crate contains the dioxus implementation of the #[macro@crate::server] macro without additional context from the server.
|
||||
//! See the [server_fn_macro] crate for more information.
|
||||
|
||||
/// Declares that a function is a [server function](https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions). This means that
|
||||
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
|
||||
use proc_macro::TokenStream;
|
||||
use server_fn_macro::server_macro_impl;
|
||||
use syn::__private::ToTokens;
|
||||
|
||||
/// Declares that a function is a [server function](https://docs.rs/server_fn/).
|
||||
/// This means that its body will only run on the server, i.e., when the `ssr`
|
||||
/// feature is enabled on this crate.
|
||||
///
|
||||
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
|
||||
/// are enabled), it will instead make a network request to the server.
|
||||
///
|
||||
/// You can specify one, two, or three arguments to the server function:
|
||||
/// 1. *Optional*: A type name that will be used to identify and register the server function
|
||||
/// (e.g., `MyServerFn`).
|
||||
/// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
|
||||
/// (e.g., `"/api"`). Defaults to `"/"`.
|
||||
/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
|
||||
/// serialization), `"Url"` (specifying that it should be use a URL-encoded form-data string).
|
||||
/// Defaults to `"Url"`. If you want to use this server function
|
||||
/// using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`.
|
||||
///
|
||||
/// The server function itself can take any number of arguments, each of which should be serializable
|
||||
/// and deserializable with `serde`. Optionally, its first argument can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html),
|
||||
/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
|
||||
/// server-side context into the server function.
|
||||
///
|
||||
/// ```ignore
|
||||
/// # use dioxus_fullstack::prelude::*; use serde::{Serialize, Deserialize};
|
||||
/// # #[derive(Serialize, Deserialize)]
|
||||
/// # pub struct Post { }
|
||||
/// #[server(ReadPosts, "/api")]
|
||||
/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
|
||||
/// // do some work on the server to access the database
|
||||
/// # unimplemented!()
|
||||
/// ## Usage
|
||||
/// ```rust,ignore
|
||||
/// #[server]
|
||||
/// pub async fn blog_posts(
|
||||
/// category: String,
|
||||
/// ) -> Result<Vec<BlogPost>, ServerFnError> {
|
||||
/// let posts = load_posts(&category).await?;
|
||||
/// // maybe do some other work
|
||||
/// Ok(posts)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note the following:
|
||||
/// - **Server functions must be `async`.** Even if the work being done inside the function body
|
||||
/// can run synchronously on the server, from the client’s perspective it involves an asynchronous
|
||||
/// function call.
|
||||
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
|
||||
/// inside the function body can’t fail, the processes of serialization/deserialization and the
|
||||
/// network call are fallible.
|
||||
/// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
|
||||
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
|
||||
/// need to deserialize the result to return it to the client.
|
||||
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
|
||||
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
|
||||
/// They are serialized as an `application/x-www-form-urlencoded`
|
||||
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
|
||||
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
|
||||
/// - **The [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html) comes from the server.** Optionally, the first argument of a server function
|
||||
/// can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html). This scope can be used to inject dependencies like the HTTP request
|
||||
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
|
||||
/// ## Named Arguments
|
||||
///
|
||||
/// You can any combination of the following named arguments:
|
||||
/// - `name`: sets the identifier for the server function’s type, which is a struct created
|
||||
/// to hold the arguments (defaults to the function identifier in PascalCase)
|
||||
/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`)
|
||||
/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
|
||||
/// relative to the prefix (defaults to the function name followed by unique hash)
|
||||
/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
|
||||
/// - `output`: the encoding for the response (defaults to `Json`)
|
||||
/// - `client`: a custom `Client` implementation that will be used for this server fn
|
||||
/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
|
||||
/// of the following (not case sensitive)
|
||||
/// - `"Url"`: `POST` request with URL-encoded arguments and JSON response
|
||||
/// - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
|
||||
/// - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
|
||||
/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
|
||||
/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
|
||||
/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
|
||||
/// ```rust,ignore
|
||||
/// #[server(
|
||||
/// name = SomeStructName,
|
||||
/// prefix = "/my_api",
|
||||
/// endpoint = "my_fn",
|
||||
/// input = Cbor,
|
||||
/// output = Json
|
||||
/// )]
|
||||
/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
|
||||
/// todo!()
|
||||
/// }
|
||||
///
|
||||
/// // expands to
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct SomeStructName {
|
||||
/// input: Vec<String>
|
||||
/// }
|
||||
///
|
||||
/// impl ServerFn for SomeStructName {
|
||||
/// const PATH: &'static str = "/my_api/my_fn";
|
||||
///
|
||||
/// // etc.
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
// before we pass this off to the server function macro, we apply extractors and middleware
|
||||
let mut function: syn::ItemFn = match syn::parse(s).map_err(|e| e.to_compile_error()) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e.into(),
|
||||
};
|
||||
|
||||
// extract all #[middleware] attributes
|
||||
let mut middlewares: Vec<Middleware> = vec![];
|
||||
function.attrs.retain(|attr| {
|
||||
if attr.meta.path().is_ident("middleware") {
|
||||
if let Ok(middleware) = attr.parse_args() {
|
||||
middlewares.push(middleware);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
let ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
sig,
|
||||
block,
|
||||
} = function;
|
||||
let mapped_body = quote::quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
#block
|
||||
}
|
||||
};
|
||||
|
||||
let server_fn_path: syn::Path = syn::parse_quote!(::dioxus::fullstack::prelude::server_fn);
|
||||
let trait_obj_wrapper: syn::Type =
|
||||
syn::parse_quote!(::dioxus::fullstack::prelude::ServerFnTraitObj);
|
||||
let mut args: ServerFnArgs = match syn::parse(args) {
|
||||
Ok(args) => args,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
if args.struct_name.is_none() {
|
||||
let upper_cammel_case_name = Converter::new()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::UpperCamel)
|
||||
.convert(sig.ident.to_string());
|
||||
args.struct_name = Some(Ident::new(&upper_cammel_case_name, sig.ident.span()));
|
||||
}
|
||||
let struct_name = args.struct_name.as_ref().unwrap();
|
||||
match server_macro_impl(
|
||||
quote::quote!(#args),
|
||||
mapped_body,
|
||||
trait_obj_wrapper,
|
||||
None,
|
||||
Some(server_fn_path.clone()),
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(tokens) => quote::quote! {
|
||||
#tokens
|
||||
#[cfg(feature = "server")]
|
||||
#server_fn_path::inventory::submit! {
|
||||
::dioxus::fullstack::prelude::ServerFnMiddleware {
|
||||
prefix: #struct_name::PREFIX,
|
||||
url: #struct_name::URL,
|
||||
middleware: || vec![
|
||||
#(
|
||||
std::sync::Arc::new(#middlewares),
|
||||
),*
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_token_stream()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Middleware {
|
||||
expr: syn::Expr,
|
||||
}
|
||||
|
||||
impl ToTokens for Middleware {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let expr = &self.expr;
|
||||
tokens.extend(quote::quote! {
|
||||
#expr
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Middleware {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let arg: syn::Expr = input.parse()?;
|
||||
Ok(Middleware { expr: arg })
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerFnArgs {
|
||||
struct_name: Option<Ident>,
|
||||
_comma: Option<Token![,]>,
|
||||
prefix: Option<Literal>,
|
||||
_comma2: Option<Token![,]>,
|
||||
encoding: Option<Literal>,
|
||||
_comma3: Option<Token![,]>,
|
||||
fn_path: Option<Literal>,
|
||||
}
|
||||
|
||||
impl ToTokens for ServerFnArgs {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let struct_name = self.struct_name.as_ref().map(|s| quote::quote! { #s, });
|
||||
let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, });
|
||||
let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, });
|
||||
let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, });
|
||||
tokens.extend(quote::quote! {
|
||||
#struct_name
|
||||
#prefix
|
||||
#encoding
|
||||
#fn_path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ServerFnArgs {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let struct_name = input.parse()?;
|
||||
let _comma = input.parse()?;
|
||||
let prefix = input.parse()?;
|
||||
let _comma2 = input.parse()?;
|
||||
let encoding = input.parse()?;
|
||||
let _comma3 = input.parse()?;
|
||||
let fn_path = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
struct_name,
|
||||
_comma,
|
||||
prefix,
|
||||
_comma2,
|
||||
encoding,
|
||||
_comma3,
|
||||
fn_path,
|
||||
})
|
||||
}
|
||||
match server_macro_impl(
|
||||
args.into(),
|
||||
s.into(),
|
||||
Some(syn::parse_quote!(server_fn)),
|
||||
"/api",
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue