diff --git a/Cargo.lock b/Cargo.lock index 5fa77fedb..acb7a3e1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5459,6 +5459,7 @@ dependencies = [ "futures-channel", "futures-util", "generational-box", + "http 1.3.1", "hyper-rustls 0.27.7", "parking_lot", "pin-project", diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index 487c2c351..ce4175e78 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -71,6 +71,7 @@ dioxus-cli-config = { workspace = true, optional = true } dioxus-devtools = { workspace = true, optional = true } aws-lc-rs = { version = "1.13.1", optional = true } dioxus-history = { workspace = true } +http.workspace = true [dev-dependencies] dioxus = { workspace = true, features = ["fullstack"] } @@ -82,7 +83,7 @@ devtools = ["dioxus-web?/devtools", "dep:dioxus-devtools"] mounted = ["dioxus-web?/mounted"] file_engine = ["dioxus-web?/file_engine"] document = ["dioxus-web?/document"] -web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web", "server_fn/browser"] +web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web", "server_fn/browser", "dioxus_server_macro/browser"] desktop = ["server_fn/reqwest", "dioxus_server_macro/reqwest"] mobile = ["server_fn/reqwest", "dioxus_server_macro/reqwest"] default-tls = ["server_fn/default-tls"] diff --git a/packages/fullstack/src/error.rs b/packages/fullstack/src/error.rs index 8d745b7da..0bc0a25d6 100644 --- a/packages/fullstack/src/error.rs +++ b/packages/fullstack/src/error.rs @@ -197,8 +197,8 @@ impl FromStr for ServerFnError { impl Display for ServerFnError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ServerFnError::ServerError(err) => write!(f, "Server error: {}", err), - ServerFnError::CommunicationError(err) => write!(f, "Communication error: {}", err), + ServerFnError::ServerError(err) => write!(f, "Server error: {err}"), + ServerFnError::CommunicationError(err) => write!(f, "Communication error: {err}"), } } } diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index d36bae7d5..9156d839c 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -8,6 +8,8 @@ mod web; mod error; +#[doc(hidden)] +pub mod mock_client; #[cfg(all(feature = "web", feature = "document"))] pub use web::FullstackWebDocument; @@ -15,13 +17,18 @@ pub use web::FullstackWebDocument; pub use dioxus_fullstack_hooks::history::FullstackHistory; pub use crate::error::{ServerFnError, ServerFnResult}; +#[doc(inline)] pub use dioxus_fullstack_hooks::*; #[cfg(feature = "server")] +#[doc(inline)] pub use dioxus_server::*; +#[doc(inline)] pub use dioxus_server_macro::*; +pub use server_fn::ServerFn as _; +#[doc(inline)] pub use server_fn::{ self, client, client::{get_server_url, set_server_url}, - codec, server, BoxedStream, ContentType, Decodes, Encodes, Format, FormatType, ServerFn as _, - ServerFn, Websocket, + codec, server, BoxedStream, ContentType, Decodes, Encodes, Format, FormatType, ServerFn, + Websocket, }; diff --git a/packages/fullstack/src/mock_client.rs b/packages/fullstack/src/mock_client.rs new file mode 100644 index 000000000..baeed189d --- /dev/null +++ b/packages/fullstack/src/mock_client.rs @@ -0,0 +1,153 @@ +//! A mock [`server_fn::client::Client`] implementation used when no client feature is enabled. + +use std::future::Future; + +use futures_util::Stream; +use server_fn::{request::ClientReq, response::ClientRes}; + +/// A placeholder [`server_fn::client::Client`] used when no client feature is enabled. The +/// [`server_fn::client::browser::BrowserClient`] is used on web clients, and [`server_fn::client::reqwest::ReqwestClient`] +/// is used on native clients +#[non_exhaustive] +pub struct MockServerFnClient {} + +impl Default for MockServerFnClient { + fn default() -> Self { + Self::new() + } +} + +impl MockServerFnClient { + /// Create a new mock server function client + pub fn new() -> Self { + Self {} + } +} + +impl + server_fn::client::Client for MockServerFnClient +{ + type Request = MockServerFnClientRequest; + + type Response = MockServerFnClientResponse; + + async fn send(_: Self::Request) -> Result { + unimplemented!() + } + + #[allow(unreachable_code)] + async fn open_websocket( + _: &str, + ) -> Result< + ( + impl Stream> + std::marker::Send + 'static, + impl futures_util::Sink + std::marker::Send + 'static, + ), + Error, + > { + unimplemented!() + as Result< + ( + futures_util::stream::Once>, + futures_util::sink::Drain, + ), + _, + > + } + + fn spawn(_: impl Future + Send + 'static) { + unimplemented!() + } +} + +/// A placeholder [`ClientReq`] used when no client feature is enabled. +#[non_exhaustive] +pub struct MockServerFnClientRequest {} + +impl ClientReq for MockServerFnClientRequest { + type FormData = (); + + fn try_new_req_query(_: &str, _: &str, _: &str, _: &str, _: http::Method) -> Result { + unimplemented!() + } + + fn try_new_req_text(_: &str, _: &str, _: &str, _: String, _: http::Method) -> Result { + unimplemented!() + } + + fn try_new_req_bytes( + _: &str, + _: &str, + _: &str, + _: bytes::Bytes, + _: http::Method, + ) -> Result { + unimplemented!() + } + + fn try_new_req_form_data( + _: &str, + _: &str, + _: &str, + _: Self::FormData, + _: http::Method, + ) -> Result { + unimplemented!() + } + + fn try_new_req_multipart( + _: &str, + _: &str, + _: Self::FormData, + _: http::Method, + ) -> Result { + unimplemented!() + } + + fn try_new_req_streaming( + _: &str, + _: &str, + _: &str, + _: impl Stream + Send + 'static, + _: http::Method, + ) -> Result { + unimplemented!() + } +} + +/// A placeholder [`ClientRes`] used when no client feature is enabled. +pub struct MockServerFnClientResponse; + +impl ClientRes for MockServerFnClientResponse { + async fn try_into_string(self) -> Result { + unimplemented!() + } + + async fn try_into_bytes(self) -> Result { + unimplemented!() + } + + #[allow(unreachable_code)] + fn try_into_stream( + self, + ) -> Result> + Send + Sync + 'static, E> + { + unimplemented!() as Result>, _> + } + + fn status(&self) -> u16 { + unimplemented!() + } + + fn status_text(&self) -> String { + unimplemented!() + } + + fn location(&self) -> String { + unimplemented!() + } + + fn has_redirect(&self) -> bool { + unimplemented!() + } +} diff --git a/packages/server-macro/Cargo.toml b/packages/server-macro/Cargo.toml index 98300630b..e1174e0b7 100644 --- a/packages/server-macro/Cargo.toml +++ b/packages/server-macro/Cargo.toml @@ -28,6 +28,7 @@ proc-macro = true [features] axum = ["server_fn_macro/axum"] server = ["server_fn_macro/ssr"] +browser = [] reqwest = ["server_fn_macro/reqwest"] generic = ["server_fn_macro/generic"] diff --git a/packages/server-macro/src/lib.rs b/packages/server-macro/src/lib.rs index 03e9516d3..7440ce845 100644 --- a/packages/server-macro/src/lib.rs +++ b/packages/server-macro/src/lib.rs @@ -206,11 +206,23 @@ use syn::{__private::ToTokens, parse_quote}; #[proc_macro_attribute] pub fn server(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream { // If there is no input codec, use json as the default - let parsed = match ServerFnCall::parse("/api", args.into(), body.into()) { + #[allow(unused_mut)] + let mut parsed = match ServerFnCall::parse("/api", args.into(), body.into()) { Ok(parsed) => parsed, Err(e) => return e.to_compile_error().into(), }; + #[cfg(not(any(feature = "browser", feature = "reqwest")))] + { + let client = &mut parsed.get_args_mut().client; + // If no client is enabled, use the mock client + if client.is_none() { + *client = Some(parse_quote!( + dioxus::fullstack::mock_client::MockServerFnClient + )); + } + } + parsed .default_protocol(Some( parse_quote!(server_fn::Http),