Files
dioxus/packages/server-macro/src/lib.rs
T
Evan Almloff 18fe6de9a0 Fix server function warning with default features (#4453)
* fix server function warning with default features

* document and hide mock client

* fix clippy

* more clippy fixes

* fix formatting
2025-07-22 11:48:47 -07:00

236 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// TODO: Create README, uncomment this: #![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
//! 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.
use proc_macro::TokenStream;
use server_fn_macro::ServerFnCall;
use syn::{__private::ToTokens, parse_quote};
/// 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.
///
/// ## Usage
/// ```rust,ignore
/// # use dioxus::prelude::*;
/// # #[derive(serde::Deserialize, serde::Serialize)]
/// # pub struct BlogPost;
/// # async fn load_posts(category: &str) -> ServerFnResult<Vec<BlogPost>> { unimplemented!() }
/// #[server]
/// pub async fn blog_posts(
/// category: String,
/// ) -> ServerFnResult<Vec<BlogPost>> {
/// let posts = load_posts(&category).await?;
/// // maybe do some other work
/// Ok(posts)
/// }
/// ```
///
/// ## Named Arguments
///
/// You can use 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).
/// Example: `name = MyServerFunction`.
/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`).
/// Example: `prefix = "/my_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).
/// Example: `endpoint = "my_fn"`.
/// - `input`: the encoding for the arguments (defaults to `PostUrl`).
/// - The `input` argument specifies how the function arguments are encoded for transmission.
/// - Acceptable values include:
/// - `PostUrl`: A `POST` request with URL-encoded arguments, suitable for form-like submissions.
/// - `Json`: A `POST` request where the arguments are encoded as JSON. This is a common choice for modern APIs.
/// - `Cbor`: A `POST` request with CBOR-encoded arguments, useful for binary data transmission with compact encoding.
/// - `GetUrl`: A `GET` request with URL-encoded arguments, suitable for simple queries or when data fits in the URL.
/// - `GetCbor`: A `GET` request with CBOR-encoded arguments, useful for query-style APIs when the payload is binary.
/// - `output`: the encoding for the response (defaults to `Json`).
/// - The `output` argument specifies how the server should encode the response data.
/// - Acceptable values include:
/// - `Json`: A response encoded as JSON (default). This is ideal for most web applications.
/// - `Cbor`: A response encoded in the CBOR format for efficient, binary-encoded data.
/// - `client`: a custom `Client` implementation that will be used for this server function. This allows
/// customization of the client-side behavior if needed.
/// - `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
/// are typically necessary if you are integrating with a custom server framework (other than Actix/Axum).
/// Example: `req = SomeRequestType`, `res = SomeResponseType`.
///
/// ## Advanced Usage of `input` and `output` Fields
///
/// The `input` and `output` fields allow you to customize how arguments and responses are encoded and decoded.
/// These fields impose specific trait bounds on the types you use. Here are detailed examples for different scenarios:
///
/// ### `output = StreamingJson`
///
/// Setting the `output` type to `StreamingJson` requires the return type to implement `From<JsonStream<T>>`,
/// where `T` implements `serde::Serialize` and `serde::de::DeserializeOwned`.
///
/// ```rust,ignore
/// #[server(output = StreamingJson)]
/// pub async fn json_stream_fn() -> Result<JsonStream<String>, ServerFnError> {
/// todo!()
/// }
/// ```
///
/// ### `output = StreamingText`
///
/// Setting the `output` type to `StreamingText` requires the return type to implement `From<TextStream>`.
///
/// ```rust,ignore
/// #[server(output = StreamingText)]
/// pub async fn text_stream_fn() -> Result<TextStream, ServerFnError> {
/// todo!()
/// }
/// ```
///
/// ### `output = PostUrl`
///
/// Setting the `output` type to `PostUrl` requires the return type to implement `Serialize` and `Deserialize`.
/// Note that this uses `serde_qs`, which imposes the following constraints:
/// - The structure must be less than 5 levels deep.
/// - The structure must not contain any `serde(flatten)` attributes.
///
/// ```rust,ignore
/// #[server(output = PostUrl)]
/// pub async fn form_fn() -> Result<TextStream, ServerFnError> {
/// todo!()
/// }
/// ```
///
/// These examples illustrate how the `output` type impacts the bounds and expectations for your server function. Ensure your return types comply with these requirements.
///
///
/// ```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>) -> ServerFnResult<usize> {
/// unimplemented!()
/// }
///
/// // expands to
/// #[derive(Deserialize, Serialize)]
/// struct SomeStructName {
/// input: Vec<String>
/// }
///
/// impl ServerFn for SomeStructName {
/// const PATH: &'static str = "/my_api/my_fn";
///
/// // etc.
/// }
/// ```
///
/// ## Adding layers to server functions
///
/// Layers allow you to transform the request and response of a server function. You can use layers
/// to add authentication, logging, or other functionality to your server functions. Server functions integrate
/// with the tower ecosystem, so you can use any layer that is compatible with tower.
///
/// Common layers include:
/// - [`tower_http::trace::TraceLayer`](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) for tracing requests and responses
/// - [`tower_http::compression::CompressionLayer`](https://docs.rs/tower-http/latest/tower_http/compression/struct.CompressionLayer.html) for compressing large responses
/// - [`tower_http::cors::CorsLayer`](https://docs.rs/tower-http/latest/tower_http/cors/struct.CorsLayer.html) for adding CORS headers to responses
/// - [`tower_http::timeout::TimeoutLayer`](https://docs.rs/tower-http/latest/tower_http/timeout/struct.TimeoutLayer.html) for adding timeouts to requests
/// - [`tower_sessions::service::SessionManagerLayer`](https://docs.rs/tower-sessions/0.13.0/tower_sessions/service/struct.SessionManagerLayer.html) for adding session management to requests
///
/// You can add a tower [`Layer`](https://docs.rs/tower/latest/tower/trait.Layer.html) to your server function with the middleware attribute:
///
/// ```rust,ignore
/// # use dioxus::prelude::*;
/// #[server]
/// // The TraceLayer will log all requests to the console
/// #[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(5)))]
/// pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<usize> {
/// unimplemented!()
/// }
/// ```
///
/// ## Extracting additional data from requests
///
/// Server functions automatically handle serialization and deserialization of arguments and responses.
/// However, you may want to extract additional data from the request, such as the user's session or
/// authentication information. You can do this with the `extract` function. This function returns any
/// type that implements the [`FromRequestParts`](https://docs.rs/axum/latest/axum/extract/trait.FromRequestParts.html)
/// trait:
///
/// ```rust,ignore
/// # use dioxus::prelude::*;
/// #[server]
/// pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<String> {
/// let headers: axum::http::header::HeaderMap = extract().await?;
/// Ok(format!("The server got a request with headers: {:?}", headers))
/// }
/// ```
///
/// ## Sharing data with server functions
///
/// You may need to share context with your server functions like a database pool. Server
/// functions can access any context provided through the launch builder. You can access
/// this context with the `FromContext` extractor:
///
/// ```rust,ignore
/// # use dioxus::prelude::*;
/// # fn app() -> Element { unimplemented!() }
/// #[derive(Clone, Copy, Debug)]
/// struct DatabasePool;
///
/// fn main() {
/// LaunchBuilder::new()
/// .with_context(server_only! {
/// DatabasePool
/// })
/// .launch(app);
/// }
///
/// #[server]
/// pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<String> {
/// let FromContext(pool): FromContext<DatabasePool> = extract().await?;
/// Ok(format!("The server read {:?} from the shared context", pool))
/// }
/// ```
#[proc_macro_attribute]
pub fn server(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {
// If there is no input codec, use json as the default
#[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<server_fn::codec::Json, server_fn::codec::Json>),
))
.default_input_encoding(Some(parse_quote!(server_fn::codec::Json)))
.default_output_encoding(Some(parse_quote!(server_fn::codec::Json)))
.default_server_fn_path(Some(parse_quote!(server_fn)))
.to_token_stream()
.into()
}