From 2da01937c1b7175e08b958a5b1658c9d0d50f723 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 11 Dec 2025 18:21:27 -0800 Subject: [PATCH] feat: add support for server-only extractors (#5086) --- examples/07-fullstack/server_functions.rs | 9 +++++-- packages/fullstack-macro/src/lib.rs | 31 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/examples/07-fullstack/server_functions.rs b/examples/07-fullstack/server_functions.rs index 52f47843e..4e7582d5d 100644 --- a/examples/07-fullstack/server_functions.rs +++ b/examples/07-fullstack/server_functions.rs @@ -282,7 +282,12 @@ async fn anonymous() -> Result { /// This is less preferred over the `#[get]`/`#[post]` syntax but is still functional for backwards /// compatibility. Previously, only the `#[server]` attribute was available, but as of Dioxus 0.7, /// the `#[get]`/`#[post]` attributes are preferred for new code. -#[server(prefix = "/api/custom", endpoint = "my_anonymous")] +/// +/// You can also use server-only extractors here as well, provided they come after the configuration. +#[server(prefix = "/api/custom", endpoint = "my_anonymous", headers: dioxus_fullstack::HeaderMap)] async fn custom_anonymous() -> Result { - Ok("Hello from a custom anonymous server function!".to_string()) + Ok(format!( + "Hello from a custom anonymous server function! -> {:#?}", + headers + )) } diff --git a/packages/fullstack-macro/src/lib.rs b/packages/fullstack-macro/src/lib.rs index a60e1aa5f..eb690033d 100644 --- a/packages/fullstack-macro/src/lib.rs +++ b/packages/fullstack-macro/src/lib.rs @@ -109,7 +109,7 @@ pub fn server(attr: proc_macro::TokenStream, mut item: TokenStream) -> TokenStre query_params: vec![], route_lit: args.fn_path, oapi_options: None, - server_args: Default::default(), + server_args: args.server_args, prefix: Some(prefix), _input_encoding: args.input, _output_encoding: args.output, @@ -1505,6 +1505,9 @@ struct ServerFnArgs { /// The protocol to use for the server function implementation. protocol: Option, builtin_encoding: bool, + /// Server-only extractors (e.g., headers: HeaderMap, cookies: Cookies). + /// These are arguments that exist purely on the server side. + server_args: Punctuated, } impl Parse for ServerFnArgs { @@ -1529,7 +1532,18 @@ impl Parse for ServerFnArgs { let mut use_key_and_value = false; let mut arg_pos = 0; + // Server-only extractors (key: Type pattern) + // These come after config options (key = value pattern) + // Example: #[server(endpoint = "/api/chat", headers: HeaderMap, cookies: Cookies)] + let mut server_args: Punctuated = Punctuated::new(); + while !stream.is_empty() { + // Check if this looks like an extractor (Ident : Type) + // If so, break out to parse extractors - they must come last + if stream.peek(Ident) && stream.peek2(Token![:]) { + break; + } + arg_pos += 1; let lookahead = stream.lookahead1(); if lookahead.peek(Ident) { @@ -1698,6 +1712,20 @@ impl Parse for ServerFnArgs { } } + // Now parse any remaining extractors (key: Type pattern) + while !stream.is_empty() { + if stream.peek(Ident) && stream.peek2(Token![:]) { + server_args.push_value(stream.parse::()?); + if stream.peek(Comma) { + server_args.push_punct(stream.parse::()?); + } else { + break; + } + } else { + break; + } + } + // parse legacy encoding into input/output let mut builtin_encoding = false; if let Some(encoding) = encoding { @@ -1740,6 +1768,7 @@ impl Parse for ServerFnArgs { impl_from, impl_deref, protocol, + server_args, }) } }