dioxus_fullstack_macro/
lib.rs

1// TODO: Create README, uncomment this: #![doc = include_str!("../README.md")]
2#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
3#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
4
5use core::panic;
6use proc_macro::TokenStream;
7use proc_macro2::{Span, TokenStream as TokenStream2};
8use quote::ToTokens;
9use quote::{format_ident, quote};
10use std::collections::HashMap;
11use syn::{
12    braced, bracketed,
13    parse::ParseStream,
14    punctuated::Punctuated,
15    token::{Comma, Slash},
16    Error, ExprTuple, FnArg, GenericArgument, Meta, PathArguments, PathSegment, Signature, Token,
17    Type, TypePath,
18};
19use syn::{parse::Parse, parse_quote, Ident, ItemFn, LitStr, Path};
20use syn::{spanned::Spanned, LitBool, LitInt, Pat, PatType};
21use syn::{
22    token::{Brace, Star},
23    Attribute, Expr, ExprClosure, Lit, Result,
24};
25
26/// ## Usage
27///
28/// ```rust,ignore
29/// # use dioxus::prelude::*;
30/// # #[derive(serde::Deserialize, serde::Serialize)]
31/// # struct BlogPost;
32/// # async fn load_posts(category: &str) -> Result<Vec<BlogPost>> { unimplemented!() }
33///
34/// #[server]
35/// async fn blog_posts(category: String) -> Result<Vec<BlogPost>> {
36///     let posts = load_posts(&category).await?;
37///     // maybe do some other work
38///     Ok(posts)
39/// }
40/// ```
41///
42/// ## Named Arguments
43///
44/// You can use any combination of the following named arguments:
45/// - `endpoint`: a prefix at which the server function handler will be mounted (defaults to `/api`).
46///   Example: `endpoint = "/my_api/my_serverfn"`.
47/// - `input`: the encoding for the arguments, defaults to `Json<T>`
48///     - You may customize the encoding of the arguments by specifying a different type for `input`.
49///     - Any axum `IntoRequest` extractor can be used here, and dioxus provides
50///       - `Json<T>`: The default axum `Json` extractor that decodes JSON-encoded request bodies.
51///       - `Cbor<T>`: A custom axum `Cbor` extractor that decodes CBOR-encoded request bodies.
52///       - `MessagePack<T>`: A custom axum `MessagePack` extractor that decodes MessagePack-encoded request bodies.
53/// - `output`: the encoding for the response (defaults to `Json`).
54///     - The `output` argument specifies how the server should encode the response data.
55///     - Acceptable values include:
56///       - `Json`: A response encoded as JSON (default). This is ideal for most web applications.
57///       - `Cbor`: A response encoded in the CBOR format for efficient, binary-encoded data.
58/// - `client`: a custom `Client` implementation that will be used for this server function. This allows
59///   customization of the client-side behavior if needed.
60///
61/// ## Advanced Usage of `input` and `output` Fields
62///
63/// The `input` and `output` fields allow you to customize how arguments and responses are encoded and decoded.
64/// These fields impose specific trait bounds on the types you use. Here are detailed examples for different scenarios:
65///
66/// ## Adding layers to server functions
67///
68/// Layers allow you to transform the request and response of a server function. You can use layers
69/// to add authentication, logging, or other functionality to your server functions. Server functions integrate
70/// with the tower ecosystem, so you can use any layer that is compatible with tower.
71///
72/// Common layers include:
73/// - [`tower_http::trace::TraceLayer`](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) for tracing requests and responses
74/// - [`tower_http::compression::CompressionLayer`](https://docs.rs/tower-http/latest/tower_http/compression/struct.CompressionLayer.html) for compressing large responses
75/// - [`tower_http::cors::CorsLayer`](https://docs.rs/tower-http/latest/tower_http/cors/struct.CorsLayer.html) for adding CORS headers to responses
76/// - [`tower_http::timeout::TimeoutLayer`](https://docs.rs/tower-http/latest/tower_http/timeout/struct.TimeoutLayer.html) for adding timeouts to requests
77/// - [`tower_sessions::service::SessionManagerLayer`](https://docs.rs/tower-sessions/0.13.0/tower_sessions/service/struct.SessionManagerLayer.html) for adding session management to requests
78///
79/// You can add a tower [`Layer`](https://docs.rs/tower/latest/tower/trait.Layer.html) to your server function with the middleware attribute:
80///
81/// ```rust,ignore
82/// # use dioxus::prelude::*;
83/// #[server]
84/// // The TraceLayer will log all requests to the console
85/// #[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(5)))]
86/// pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<usize> {
87///     unimplemented!()
88/// }
89/// ```
90#[proc_macro_attribute]
91pub fn server(attr: proc_macro::TokenStream, mut item: TokenStream) -> TokenStream {
92    // Parse the attribute list using the old server_fn arg parser.
93    let args = match syn::parse::<ServerFnArgs>(attr) {
94        Ok(args) => args,
95        Err(err) => {
96            let err: TokenStream = err.to_compile_error().into();
97            item.extend(err);
98            return item;
99        }
100    };
101
102    let method = Method::Post(Ident::new("POST", proc_macro2::Span::call_site()));
103    let route: Route = Route {
104        method: None,
105        path_params: vec![],
106        query_params: vec![],
107        state: None,
108        route_lit: args.fn_path,
109        oapi_options: None,
110        server_args: Default::default(),
111        prefix: Some(
112            args.prefix
113                .unwrap_or_else(|| LitStr::new("/api", Span::call_site())),
114        ),
115        _input_encoding: args.input,
116        _output_encoding: args.output,
117    };
118
119    match route_impl_with_route(route, item.clone(), Some(method)) {
120        Ok(mut tokens) => {
121            // Let's add some deprecated warnings to the various fields from `args` if the user is using them...
122            // We don't generate structs anymore, don't use various protocols, etc
123            if let Some(name) = args.struct_name {
124                tokens.extend(quote! {
125                    const _: () = {
126                        #[deprecated(note = "Dioxus server functions no longer generate a struct for the server function. The function itself is used directly.")]
127                        struct #name;
128                        fn ___assert_deprecated() {
129                            let _ = #name;
130                        }
131
132                        ()
133                    };
134                });
135            }
136
137            //
138            tokens.into()
139        }
140
141        // Retain the original function item and append the error to it. Better for autocomplete.
142        Err(err) => {
143            let err: TokenStream = err.to_compile_error().into();
144            item.extend(err);
145            item
146        }
147    }
148}
149
150#[proc_macro_attribute]
151pub fn get(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {
152    wrapped_route_impl(args, body, Some(Method::new_from_string("GET")))
153}
154
155#[proc_macro_attribute]
156pub fn post(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {
157    wrapped_route_impl(args, body, Some(Method::new_from_string("POST")))
158}
159
160#[proc_macro_attribute]
161pub fn put(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {
162    wrapped_route_impl(args, body, Some(Method::new_from_string("PUT")))
163}
164
165#[proc_macro_attribute]
166pub fn delete(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {
167    wrapped_route_impl(args, body, Some(Method::new_from_string("DELETE")))
168}
169
170#[proc_macro_attribute]
171pub fn patch(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {
172    wrapped_route_impl(args, body, Some(Method::new_from_string("PATCH")))
173}
174
175fn wrapped_route_impl(
176    attr: TokenStream,
177    mut item: TokenStream,
178    method: Option<Method>,
179) -> TokenStream {
180    match route_impl(attr, item.clone(), method) {
181        Ok(tokens) => tokens.into(),
182        Err(err) => {
183            let err: TokenStream = err.to_compile_error().into();
184            item.extend(err);
185            item
186        }
187    }
188}
189
190fn route_impl(
191    attr: TokenStream,
192    item: TokenStream,
193    method_from_macro: Option<Method>,
194) -> syn::Result<TokenStream2> {
195    let route = syn::parse::<Route>(attr)?;
196    route_impl_with_route(route, item, method_from_macro)
197}
198
199fn route_impl_with_route(
200    route: Route,
201    item: TokenStream,
202    method_from_macro: Option<Method>,
203) -> syn::Result<TokenStream2> {
204    // Parse the route and function
205    let mut function = syn::parse::<ItemFn>(item)?;
206
207    let middleware_attrs = function
208        .attrs
209        .iter()
210        .filter(|attr| attr.path().is_ident("middleware"))
211        .cloned()
212        .collect::<Vec<_>>();
213
214    let middleware_inits = middleware_attrs
215        .into_iter()
216        .map(|f| match f.meta {
217            Meta::List(meta_list) => Ok(meta_list.tokens),
218            _ => Err(Error::new(
219                f.span(),
220                "Expected middleware attribute to be a list, e.g. #[middleware(MyLayer::new())]",
221            )),
222        })
223        .collect::<Result<Vec<_>>>()?;
224
225    // don't re-emit the middleware attribute on the inner
226    function
227        .attrs
228        .retain(|attr| !attr.path().is_ident("middleware"));
229
230    let server_args = route.server_args.clone();
231    let mut function_on_server = function.clone();
232    function_on_server.sig.inputs.extend(server_args.clone());
233
234    // Now we can compile the route
235    let original_inputs = function
236        .sig
237        .inputs
238        .iter()
239        .map(|arg| match arg {
240            FnArg::Receiver(_receiver) => panic!("Self type is not supported"),
241            FnArg::Typed(pat_type) => {
242                quote! {
243                    #[allow(unused_mut)]
244                    #pat_type
245                }
246            }
247        })
248        .collect::<Punctuated<_, Token![,]>>();
249
250    let route = CompiledRoute::from_route(route, &function, false, method_from_macro)?;
251    let path_extractor = route.path_extractor();
252    let query_extractor = route.query_extractor();
253    let query_params_struct = route.query_params_struct(false);
254    let _state_type = &route.state;
255    let method_ident = &route.method;
256    let http_method = route.method.to_axum_method_name();
257    let _remaining_numbered_pats = route.remaining_pattypes_numbered(&function.sig.inputs);
258    let body_json_args = route.remaining_pattypes_named(&function.sig.inputs);
259    let body_json_names = body_json_args
260        .iter()
261        .enumerate()
262        .map(|(i, pat_type)| match &*pat_type.pat {
263            Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
264            _ => format_ident!("___arg{}", i),
265        })
266        .collect::<Vec<_>>();
267    let body_json_types = body_json_args
268        .iter()
269        .map(|pat_type| &pat_type.ty)
270        .collect::<Vec<_>>();
271    let extracted_idents = route.extracted_idents();
272    let route_docs = route.to_doc_comments();
273
274    // Get the variables we need for code generation
275    let fn_name = &function.sig.ident;
276    let vis = &function.vis;
277    let asyncness = &function.sig.asyncness;
278    let (impl_generics, ty_generics, where_clause) = &function.sig.generics.split_for_impl();
279    let ty_generics = ty_generics.as_turbofish();
280    let fn_docs = function
281        .attrs
282        .iter()
283        .filter(|attr| attr.path().is_ident("doc"));
284
285    let __axum = quote! { dioxus_server::axum };
286
287    let output_type = match &function.sig.output {
288        syn::ReturnType::Default => parse_quote! { () },
289        syn::ReturnType::Type(_, ty) => (*ty).clone(),
290    };
291
292    let query_param_names = route.query_params.iter().map(|(ident, _)| ident);
293
294    let path_param_args = route.path_params.iter().map(|(_slash, param)| match param {
295        PathParam::Capture(_lit, _brace_1, ident, _ty, _brace_2) => {
296            Some(quote! { #ident = #ident, })
297        }
298        PathParam::WildCard(_lit, _brace_1, _star, ident, _ty, _brace_2) => {
299            Some(quote! { #ident = #ident, })
300        }
301        PathParam::Static(_lit) => None,
302    });
303
304    let out_ty = match output_type.as_ref() {
305        Type::Tuple(tuple) if tuple.elems.is_empty() => parse_quote! { () },
306        _ => output_type.clone(),
307    };
308
309    let server_names = server_args
310        .iter()
311        .map(|pat_type| match pat_type {
312            FnArg::Receiver(_) => quote! { () },
313            FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {
314                Pat::Ident(pat_ident) => {
315                    let name = &pat_ident.ident;
316                    quote! { #name }
317                }
318                _ => panic!("Expected Pat::Ident"),
319            },
320        })
321        .collect::<Vec<_>>();
322
323    let server_types = server_args
324        .iter()
325        .map(|pat_type| match pat_type {
326            FnArg::Receiver(_) => parse_quote! { () },
327            FnArg::Typed(pat_type) => (*pat_type.ty).clone(),
328        })
329        .collect::<Vec<_>>();
330
331    let body_struct_impl = {
332        let tys = body_json_types
333            .iter()
334            .enumerate()
335            .map(|(idx, _)| format_ident!("__Ty{}", idx));
336
337        let names = body_json_names.iter().enumerate().map(|(idx, name)| {
338            let ty_name = format_ident!("__Ty{}", idx);
339            quote! { #name: #ty_name }
340        });
341
342        quote! {
343            #[derive(serde::Serialize, serde::Deserialize)]
344            #[serde(crate = "serde")]
345            struct ___Body_Serialize___< #(#tys,)* > {
346                #(#names,)*
347            }
348        }
349    };
350
351    // This unpacks the body struct into the individual variables that get scoped
352    let unpack = {
353        let unpack_args = body_json_names.iter().map(|name| quote! { data.#name });
354        quote! {
355            |data| { ( #(#unpack_args,)* ) }
356        }
357    };
358
359    // there's no active request on the server, so we just create a dummy one
360    let server_defaults = if server_args.is_empty() {
361        quote! {}
362    } else {
363        quote! {
364            let (#(#server_names,)*) = dioxus_fullstack::FullstackContext::extract::<(#(#server_types,)*), _>().await?;
365        }
366    };
367
368    let as_axum_path = route.to_axum_path_string();
369
370    let query_endpoint = if let Some(route_lit) = route.route_lit.as_ref() {
371        let prefix = route
372            .prefix
373            .as_ref()
374            .cloned()
375            .unwrap_or_else(|| LitStr::new("", Span::call_site()))
376            .value();
377        let url_without_queries = route_lit.value().split('?').next().unwrap().to_string();
378        let full_url = format!(
379            "{}{}{}",
380            prefix,
381            if url_without_queries.starts_with("/") {
382                ""
383            } else {
384                "/"
385            },
386            url_without_queries
387        );
388        quote! { format!(#full_url, #( #path_param_args)*) }
389    } else {
390        quote! { __ENDPOINT_PATH.to_string() }
391    };
392
393    let endpoint_path = {
394        let prefix = route
395            .prefix
396            .as_ref()
397            .cloned()
398            .unwrap_or_else(|| LitStr::new("", Span::call_site()));
399
400        let route_lit = if !as_axum_path.is_empty() {
401            quote! { #as_axum_path }
402        } else {
403            quote! {
404                concat!(
405                    "/",
406                    stringify!(#fn_name)
407                )
408            }
409        };
410
411        let hash = match route.route_lit.as_ref() {
412            // Explicit route lit, no need to hash
413            Some(_) => quote! { "" },
414
415            // Implicit route lit, we need to hash the function signature to avoid collisions
416            None => {
417                // let enable_hash = option_env!("DISABLE_SERVER_FN_HASH").is_none();
418                let key_env_var = match option_env!("SERVER_FN_OVERRIDE_KEY") {
419                    Some(_) => "SERVER_FN_OVERRIDE_KEY",
420                    None => "CARGO_MANIFEST_DIR",
421                };
422                quote! {
423                    dioxus_fullstack::xxhash_rust::const_xxh64::xxh64(
424                        concat!(env!(#key_env_var), ":", module_path!()).as_bytes(),
425                        0
426                    )
427                }
428            }
429        };
430
431        quote! {
432            dioxus_fullstack::const_format::concatcp!(#prefix, #route_lit, #hash)
433        }
434    };
435
436    let middleware_extra = middleware_inits
437        .iter()
438        .map(|init| {
439            quote! {
440                .layer(#init)
441            }
442        })
443        .collect::<Vec<_>>();
444
445    Ok(quote! {
446        #(#fn_docs)*
447        #route_docs
448        #[deny(
449            unexpected_cfgs,
450            reason = "
451==========================================================================================
452  Using Dioxus Server Functions requires a `server` feature flag in your `Cargo.toml`.
453  Please add the following to your `Cargo.toml`:
454
455  ```toml
456  [features]
457  server = [\"dioxus/server\"]
458  ```
459
460  To enable better Rust-Analyzer support, you can make `server` a default feature:
461  ```toml
462  [features]
463  default = [\"web\", \"server\"]
464  web = [\"dioxus/web\"]
465  server = [\"dioxus/server\"]
466  ```
467==========================================================================================
468        "
469        )]
470        #vis async fn #fn_name #impl_generics(
471            #original_inputs
472        ) -> #out_ty #where_clause {
473            use dioxus_fullstack::serde as serde;
474            use dioxus_fullstack::{
475                // concrete types
476                ServerFnEncoder, ServerFnDecoder, DioxusServerState,
477
478                // "magic" traits for encoding/decoding on the client
479                ExtractRequest, EncodeRequest, RequestDecodeResult, RequestDecodeErr,
480
481                // "magic" traits for encoding/decoding on the server
482                MakeAxumResponse, MakeAxumError,
483            };
484
485            _ = dioxus_fullstack::assert_is_result::<#out_ty>();
486
487            #query_params_struct
488
489            #body_struct_impl
490
491            const __ENDPOINT_PATH: &str = #endpoint_path;
492
493            // On the client, we make the request to the server
494            // We want to support extremely flexible error types and return types, making this more complex than it should
495            #[allow(clippy::unused_unit)]
496            #[cfg(not(feature = "server"))]
497            {
498                let client = dioxus_fullstack::ClientRequest::new(
499                    dioxus_fullstack::http::Method::#method_ident,
500                    #query_endpoint,
501                    &__QueryParams__ { #(#query_param_names,)* },
502                );
503
504                let verify_token = (&&&&&&&&&&&&&&ServerFnEncoder::<___Body_Serialize___<#(#body_json_types,)*>, (#(#body_json_types,)*)>::new())
505                    .verify_can_serialize();
506
507                dioxus_fullstack::assert_can_encode(verify_token);
508
509                let response = (&&&&&&&&&&&&&&ServerFnEncoder::<___Body_Serialize___<#(#body_json_types,)*>, (#(#body_json_types,)*)>::new())
510                    .fetch_client(client, ___Body_Serialize___ { #(#body_json_names,)* }, #unpack)
511                    .await;
512
513                let decoded = (&&&&&ServerFnDecoder::<#out_ty>::new())
514                    .decode_client_response(response)
515                    .await;
516
517                let result = (&&&&&ServerFnDecoder::<#out_ty>::new())
518                    .decode_client_err(decoded)
519                    .await;
520
521                return result;
522            }
523
524            // On the server, we expand the tokens and submit the function to inventory
525            #[cfg(feature = "server")] {
526                use #__axum::response::IntoResponse;
527                use dioxus_server::ServerFunction;
528
529                #function_on_server
530
531                #[allow(clippy::unused_unit)]
532                #asyncness fn __inner__function__ #impl_generics(
533                    ___state: #__axum::extract::State<DioxusServerState>,
534                    #path_extractor
535                    #query_extractor
536                    request: #__axum::extract::Request,
537                ) -> Result<#__axum::response::Response, #__axum::response::Response> #where_clause {
538                    let ((#(#server_names,)*), (  #(#body_json_names,)* )) = (&&&&&&&&&&&&&&ServerFnEncoder::<___Body_Serialize___<#(#body_json_types,)*>, (#(#body_json_types,)*)>::new())
539                        .extract_axum(___state.0, request, #unpack).await?;
540
541                    let encoded = (&&&&&&ServerFnDecoder::<#out_ty>::new())
542                        .make_axum_response(
543                            #fn_name #ty_generics(#(#extracted_idents,)*  #(#body_json_names,)* #(#server_names,)*).await
544                        );
545
546                    let response = (&&&&&ServerFnDecoder::<#out_ty>::new())
547                        .make_axum_error(encoded);
548
549                    return response;
550                }
551
552                dioxus_server::inventory::submit! {
553                    ServerFunction::new(
554                        dioxus_server::http::Method::#method_ident,
555                        __ENDPOINT_PATH,
556                        || {
557                            #__axum::routing::#http_method(__inner__function__ #ty_generics) #(#middleware_extra)*
558                        }
559                    )
560                }
561
562                #server_defaults
563
564                return #fn_name #ty_generics(
565                    #(#extracted_idents,)*
566                    #(#body_json_names,)*
567                    #(#server_names,)*
568                ).await;
569            }
570
571            #[allow(unreachable_code)]
572            {
573                unreachable!()
574            }
575        }
576    })
577}
578
579struct CompiledRoute {
580    method: Method,
581    #[allow(clippy::type_complexity)]
582    path_params: Vec<(Slash, PathParam)>,
583    query_params: Vec<(Ident, Box<Type>)>,
584    state: Type,
585    route_lit: Option<LitStr>,
586    prefix: Option<LitStr>,
587    oapi_options: Option<OapiOptions>,
588}
589
590impl CompiledRoute {
591    fn to_axum_path_string(&self) -> String {
592        let mut path = String::new();
593
594        for (_slash, param) in &self.path_params {
595            path.push('/');
596            match param {
597                PathParam::Capture(lit, _brace_1, _, _, _brace_2) => {
598                    path.push('{');
599                    path.push_str(&lit.value());
600                    path.push('}');
601                }
602                PathParam::WildCard(lit, _brace_1, _, _, _, _brace_2) => {
603                    path.push('{');
604                    path.push('*');
605                    path.push_str(&lit.value());
606                    path.push('}');
607                }
608                PathParam::Static(lit) => path.push_str(&lit.value()),
609            }
610            // if colon.is_some() {
611            //     path.push(':');
612            // }
613            // path.push_str(&ident.value());
614        }
615
616        path
617    }
618
619    /// Removes the arguments in `route` from `args`, and merges them in the output.
620    pub fn from_route(
621        mut route: Route,
622        function: &ItemFn,
623        with_aide: bool,
624        method_from_macro: Option<Method>,
625    ) -> syn::Result<Self> {
626        if !with_aide && route.oapi_options.is_some() {
627            return Err(syn::Error::new(
628                Span::call_site(),
629                "Use `api_route` instead of `route` to use OpenAPI options",
630            ));
631        } else if with_aide && route.oapi_options.is_none() {
632            route.oapi_options = Some(OapiOptions {
633                summary: None,
634                description: None,
635                id: None,
636                hidden: None,
637                tags: None,
638                security: None,
639                responses: None,
640                transform: None,
641            });
642        }
643
644        let sig = &function.sig;
645        let mut arg_map = sig
646            .inputs
647            .iter()
648            .filter_map(|item| match item {
649                syn::FnArg::Receiver(_) => None,
650                syn::FnArg::Typed(pat_type) => Some(pat_type),
651            })
652            .filter_map(|pat_type| match &*pat_type.pat {
653                syn::Pat::Ident(ident) => Some((ident.ident.clone(), pat_type.ty.clone())),
654                _ => None,
655            })
656            .collect::<HashMap<_, _>>();
657
658        for (_slash, path_param) in &mut route.path_params {
659            match path_param {
660                PathParam::Capture(_lit, _, ident, ty, _) => {
661                    let (new_ident, new_ty) = arg_map.remove_entry(ident).ok_or_else(|| {
662                        syn::Error::new(
663                            ident.span(),
664                            format!("path parameter `{}` not found in function arguments", ident),
665                        )
666                    })?;
667                    *ident = new_ident;
668                    *ty = new_ty;
669                }
670                PathParam::WildCard(_lit, _, _star, ident, ty, _) => {
671                    let (new_ident, new_ty) = arg_map.remove_entry(ident).ok_or_else(|| {
672                        syn::Error::new(
673                            ident.span(),
674                            format!("path parameter `{}` not found in function arguments", ident),
675                        )
676                    })?;
677                    *ident = new_ident;
678                    *ty = new_ty;
679                }
680                PathParam::Static(_lit) => {}
681            }
682        }
683
684        let mut query_params = Vec::new();
685        for ident in route.query_params {
686            let (ident, ty) = arg_map.remove_entry(&ident).ok_or_else(|| {
687                syn::Error::new(
688                    ident.span(),
689                    format!(
690                        "query parameter `{}` not found in function arguments",
691                        ident
692                    ),
693                )
694            })?;
695            query_params.push((ident, ty));
696        }
697
698        if let Some(options) = route.oapi_options.as_mut() {
699            options.merge_with_fn(function)
700        }
701
702        let method = match (method_from_macro, route.method) {
703            (Some(method), None) => method,
704            (None, Some(method)) => method,
705            (Some(_), Some(_)) => {
706                return Err(syn::Error::new(
707                    Span::call_site(),
708                    "HTTP method specified both in macro and in attribute",
709                ))
710            }
711            (None, None) => {
712                return Err(syn::Error::new(
713                    Span::call_site(),
714                    "HTTP method not specified in macro or in attribute",
715                ))
716            }
717        };
718
719        Ok(Self {
720            method,
721            route_lit: route.route_lit,
722            path_params: route.path_params,
723            query_params,
724            state: route.state.unwrap_or_else(|| guess_state_type(sig)),
725            oapi_options: route.oapi_options,
726            prefix: route.prefix,
727        })
728    }
729
730    pub fn path_extractor(&self) -> TokenStream2 {
731        let path_iter = self
732            .path_params
733            .iter()
734            .filter_map(|(_slash, path_param)| path_param.capture());
735        let idents = path_iter.clone().map(|item| item.0);
736        let types = path_iter.clone().map(|item| item.1);
737        quote! {
738            dioxus_server::axum::extract::Path((#(#idents,)*)): dioxus_server::axum::extract::Path<(#(#types,)*)>,
739        }
740    }
741
742    pub fn query_extractor(&self) -> TokenStream2 {
743        let idents = self.query_params.iter().map(|item| &item.0);
744        quote! {
745            dioxus_server::axum::extract::Query(__QueryParams__ { #(#idents,)* }): dioxus_server::axum::extract::Query<__QueryParams__>,
746        }
747    }
748
749    pub fn query_params_struct(&self, with_aide: bool) -> TokenStream2 {
750        let idents = self.query_params.iter().map(|item| &item.0);
751        let types = self.query_params.iter().map(|item| &item.1);
752        let derive = match with_aide {
753            true => quote! {
754                #[derive(serde::Deserialize, serde::Serialize, ::schemars::JsonSchema)]
755                #[serde(crate = "serde")]
756            },
757            false => quote! {
758                #[derive(serde::Deserialize, serde::Serialize)]
759                #[serde(crate = "serde")]
760            },
761        };
762        quote! {
763            #derive
764            struct __QueryParams__ {
765                #(#idents: #types,)*
766            }
767        }
768    }
769
770    pub fn extracted_idents(&self) -> Vec<Ident> {
771        let mut idents = Vec::new();
772        for (_slash, path_param) in &self.path_params {
773            if let Some((ident, _ty)) = path_param.capture() {
774                idents.push(ident.clone());
775            }
776        }
777        for (ident, _ty) in &self.query_params {
778            idents.push(ident.clone());
779        }
780        idents
781    }
782
783    fn remaining_pattypes_named(
784        &self,
785        args: &Punctuated<FnArg, Comma>,
786    ) -> Punctuated<PatType, Comma> {
787        args.iter()
788            .filter_map(|item| {
789                if let FnArg::Typed(pat_type) = item {
790                    if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
791                        if self.path_params.iter().any(|(_slash, path_param)| {
792                            if let Some((path_ident, _ty)) = path_param.capture() {
793                                path_ident == &pat_ident.ident
794                            } else {
795                                false
796                            }
797                        }) || self
798                            .query_params
799                            .iter()
800                            .any(|(query_ident, _)| query_ident == &pat_ident.ident)
801                        {
802                            return None;
803                        }
804                    }
805
806                    Some(pat_type.clone())
807                } else {
808                    unimplemented!("Self type is not supported")
809                }
810            })
811            .collect()
812    }
813
814    /// The arguments not used in the route.
815    /// Map the identifier to `___arg___{i}: Type`.
816    pub fn remaining_pattypes_numbered(
817        &self,
818        args: &Punctuated<FnArg, Comma>,
819    ) -> Punctuated<PatType, Comma> {
820        args.iter()
821            .enumerate()
822            .filter_map(|(i, item)| {
823                if let FnArg::Typed(pat_type) = item {
824                    if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
825                        if self.path_params.iter().any(|(_slash, path_param)| {
826                            if let Some((path_ident, _ty)) = path_param.capture() {
827                                path_ident == &pat_ident.ident
828                            } else {
829                                false
830                            }
831                        }) || self
832                            .query_params
833                            .iter()
834                            .any(|(query_ident, _)| query_ident == &pat_ident.ident)
835                        {
836                            return None;
837                        }
838                    }
839
840                    let mut new_pat_type = pat_type.clone();
841                    let ident = format_ident!("___arg___{}", i);
842                    new_pat_type.pat = Box::new(parse_quote!(#ident));
843                    Some(new_pat_type)
844                } else {
845                    unimplemented!("Self type is not supported")
846                }
847            })
848            .collect()
849    }
850
851    #[allow(dead_code)]
852    fn aide() {
853        // let http_method = format_ident!("{}_with", http_method);
854        // let summary = route
855        //     .get_oapi_summary()
856        //     .map(|summary| quote! { .summary(#summary) });
857        // let description = route
858        //     .get_oapi_description()
859        //     .map(|description| quote! { .description(#description) });
860        // let hidden = route
861        //     .get_oapi_hidden()
862        //     .map(|hidden| quote! { .hidden(#hidden) });
863        // let tags = route.get_oapi_tags();
864        // let id = route
865        //     .get_oapi_id(&function.sig)
866        //     .map(|id| quote! { .id(#id) });
867        // let transform = route.get_oapi_transform()?;
868        // let responses = route.get_oapi_responses();
869        // let response_code = responses.iter().map(|response| &response.0);
870        // let response_type = responses.iter().map(|response| &response.1);
871        // let security = route.get_oapi_security();
872        // let schemes = security.iter().map(|sec| &sec.0);
873        // let scopes = security.iter().map(|sec| &sec.1);
874
875        // (
876        //     route.ide_documentation_for_aide_methods(),
877        //     quote! {
878        //         ::aide::axum::routing::#http_method(
879        //             __inner__function__ #ty_generics,
880        //             |__op__| {
881        //                 let __op__ = __op__
882        //                     #summary
883        //                     #description
884        //                     #hidden
885        //                     #id
886        //                     #(.tag(#tags))*
887        //                     #(.security_requirement_scopes::<Vec<&'static str>, _>(#schemes, vec![#(#scopes),*]))*
888        //                     #(.response::<#response_code, #response_type>())*
889        //                     ;
890        //                 #transform
891        //                 __op__
892        //             }
893        //         )
894        //     },
895        //     quote! { ::aide::axum::routing::ApiMethodRouter },
896        // )
897    }
898
899    #[allow(dead_code)]
900    pub fn ide_documentation_for_aide_methods(&self) -> TokenStream2 {
901        let Some(options) = &self.oapi_options else {
902            return quote! {};
903        };
904        let summary = options.summary.as_ref().map(|(ident, _)| {
905            let method = Ident::new("summary", ident.span());
906            quote!( let x = x.#method(""); )
907        });
908        let description = options.description.as_ref().map(|(ident, _)| {
909            let method = Ident::new("description", ident.span());
910            quote!( let x = x.#method(""); )
911        });
912        let id = options.id.as_ref().map(|(ident, _)| {
913            let method = Ident::new("id", ident.span());
914            quote!( let x = x.#method(""); )
915        });
916        let hidden = options.hidden.as_ref().map(|(ident, _)| {
917            let method = Ident::new("hidden", ident.span());
918            quote!( let x = x.#method(false); )
919        });
920        let tags = options.tags.as_ref().map(|(ident, _)| {
921            let method = Ident::new("tag", ident.span());
922            quote!( let x = x.#method(""); )
923        });
924        let security = options.security.as_ref().map(|(ident, _)| {
925            let method = Ident::new("security_requirement_scopes", ident.span());
926            quote!( let x = x.#method("", [""]); )
927        });
928        let responses = options.responses.as_ref().map(|(ident, _)| {
929            let method = Ident::new("response", ident.span());
930            quote!( let x = x.#method::<0, String>(); )
931        });
932        let transform = options.transform.as_ref().map(|(ident, _)| {
933            let method = Ident::new("with", ident.span());
934            quote!( let x = x.#method(|x|x); )
935        });
936
937        quote! {
938            #[allow(unused)]
939            #[allow(clippy::no_effect)]
940            fn ____ide_documentation_for_aide____(x: ::aide::transform::TransformOperation) {
941                #summary
942                #description
943                #id
944                #hidden
945                #tags
946                #security
947                #responses
948                #transform
949            }
950        }
951    }
952
953    #[allow(dead_code)]
954    pub fn get_oapi_summary(&self) -> Option<LitStr> {
955        if let Some(oapi_options) = &self.oapi_options {
956            if let Some(summary) = &oapi_options.summary {
957                return Some(summary.1.clone());
958            }
959        }
960        None
961    }
962
963    #[allow(dead_code)]
964    pub fn get_oapi_description(&self) -> Option<LitStr> {
965        if let Some(oapi_options) = &self.oapi_options {
966            if let Some(description) = &oapi_options.description {
967                return Some(description.1.clone());
968            }
969        }
970        None
971    }
972
973    #[allow(dead_code)]
974    pub fn get_oapi_hidden(&self) -> Option<LitBool> {
975        if let Some(oapi_options) = &self.oapi_options {
976            if let Some(hidden) = &oapi_options.hidden {
977                return Some(hidden.1.clone());
978            }
979        }
980        None
981    }
982
983    #[allow(dead_code)]
984    pub fn get_oapi_tags(&self) -> Vec<LitStr> {
985        if let Some(oapi_options) = &self.oapi_options {
986            if let Some(tags) = &oapi_options.tags {
987                return tags.1 .0.clone();
988            }
989        }
990        Vec::new()
991    }
992
993    #[allow(dead_code)]
994    pub fn get_oapi_id(&self, sig: &Signature) -> Option<LitStr> {
995        if let Some(oapi_options) = &self.oapi_options {
996            if let Some(id) = &oapi_options.id {
997                return Some(id.1.clone());
998            }
999        }
1000        Some(LitStr::new(&sig.ident.to_string(), sig.ident.span()))
1001    }
1002
1003    #[allow(dead_code)]
1004    pub fn get_oapi_transform(&self) -> syn::Result<Option<TokenStream2>> {
1005        if let Some(oapi_options) = &self.oapi_options {
1006            if let Some(transform) = &oapi_options.transform {
1007                if transform.1.inputs.len() != 1 {
1008                    return Err(syn::Error::new(
1009                        transform.1.span(),
1010                        "expected a single identifier",
1011                    ));
1012                }
1013
1014                let pat = transform.1.inputs.first().unwrap();
1015                let body = &transform.1.body;
1016
1017                if let Pat::Ident(pat_ident) = pat {
1018                    let ident = &pat_ident.ident;
1019                    return Ok(Some(quote! {
1020                        let #ident = __op__;
1021                        let __op__ = #body;
1022                    }));
1023                } else {
1024                    return Err(syn::Error::new(
1025                        pat.span(),
1026                        "expected a single identifier without type",
1027                    ));
1028                }
1029            }
1030        }
1031        Ok(None)
1032    }
1033
1034    #[allow(dead_code)]
1035    pub fn get_oapi_responses(&self) -> Vec<(LitInt, Type)> {
1036        if let Some(oapi_options) = &self.oapi_options {
1037            if let Some((_ident, Responses(responses))) = &oapi_options.responses {
1038                return responses.clone();
1039            }
1040        }
1041        Default::default()
1042    }
1043
1044    #[allow(dead_code)]
1045    pub fn get_oapi_security(&self) -> Vec<(LitStr, Vec<LitStr>)> {
1046        if let Some(oapi_options) = &self.oapi_options {
1047            if let Some((_ident, Security(security))) = &oapi_options.security {
1048                return security
1049                    .iter()
1050                    .map(|(scheme, StrArray(scopes))| (scheme.clone(), scopes.clone()))
1051                    .collect();
1052            }
1053        }
1054        Default::default()
1055    }
1056
1057    pub(crate) fn to_doc_comments(&self) -> TokenStream2 {
1058        let mut doc = format!(
1059            "# Handler information
1060- Method: `{}`
1061- Path: `{}`
1062- State: `{}`",
1063            self.method.to_axum_method_name(),
1064            self.route_lit
1065                .as_ref()
1066                .map(|lit| lit.value())
1067                .unwrap_or_else(|| "<auto>".into()),
1068            self.state.to_token_stream(),
1069        );
1070
1071        if let Some(options) = &self.oapi_options {
1072            let summary = options
1073                .summary
1074                .as_ref()
1075                .map(|(_, summary)| format!("\"{}\"", summary.value()))
1076                .unwrap_or("None".to_string());
1077            let description = options
1078                .description
1079                .as_ref()
1080                .map(|(_, description)| format!("\"{}\"", description.value()))
1081                .unwrap_or("None".to_string());
1082            let id = options
1083                .id
1084                .as_ref()
1085                .map(|(_, id)| format!("\"{}\"", id.value()))
1086                .unwrap_or("None".to_string());
1087            let hidden = options
1088                .hidden
1089                .as_ref()
1090                .map(|(_, hidden)| hidden.value().to_string())
1091                .unwrap_or("None".to_string());
1092            let tags = options
1093                .tags
1094                .as_ref()
1095                .map(|(_, tags)| tags.to_string())
1096                .unwrap_or("[]".to_string());
1097            let security = options
1098                .security
1099                .as_ref()
1100                .map(|(_, security)| security.to_string())
1101                .unwrap_or("{}".to_string());
1102
1103            doc = format!(
1104                "{doc}
1105
1106## OpenAPI
1107- Summary: `{summary}`
1108- Description: `{description}`
1109- Operation id: `{id}`
1110- Tags: `{tags}`
1111- Security: `{security}`
1112- Hidden: `{hidden}`
1113"
1114            );
1115        }
1116
1117        quote!(
1118            #[doc = #doc]
1119        )
1120    }
1121}
1122
1123fn guess_state_type(sig: &syn::Signature) -> Type {
1124    for arg in &sig.inputs {
1125        if let FnArg::Typed(pat_type) = arg {
1126            // Returns `T` if the type of the last segment is exactly `State<T>`.
1127            if let Type::Path(ty) = &*pat_type.ty {
1128                let last_segment = ty.path.segments.last().unwrap();
1129                if last_segment.ident == "State" {
1130                    if let PathArguments::AngleBracketed(args) = &last_segment.arguments {
1131                        if args.args.len() == 1 {
1132                            if let GenericArgument::Type(ty) = args.args.first().unwrap() {
1133                                return ty.clone();
1134                            }
1135                        }
1136                    }
1137                }
1138            }
1139        }
1140    }
1141
1142    parse_quote! { () }
1143}
1144
1145struct RouteParser {
1146    path_params: Vec<(Slash, PathParam)>,
1147    query_params: Vec<Ident>,
1148}
1149
1150impl RouteParser {
1151    fn new(lit: LitStr) -> syn::Result<Self> {
1152        let val = lit.value();
1153        let span = lit.span();
1154        let split_route = val.split('?').collect::<Vec<_>>();
1155        if split_route.len() > 2 {
1156            return Err(syn::Error::new(span, "expected at most one '?'"));
1157        }
1158
1159        let path = split_route[0];
1160        if !path.starts_with('/') {
1161            return Err(syn::Error::new(span, "expected path to start with '/'"));
1162        }
1163        let path = path.strip_prefix('/').unwrap();
1164
1165        let mut path_params = Vec::new();
1166
1167        for path_param in path.split('/') {
1168            path_params.push((
1169                Slash(span),
1170                PathParam::new(path_param, span, Box::new(parse_quote!(())))?,
1171            ));
1172        }
1173
1174        let path_param_len = path_params.len();
1175        for (i, (_slash, path_param)) in path_params.iter().enumerate() {
1176            match path_param {
1177                PathParam::WildCard(_, _, _, _, _, _) => {
1178                    if i != path_param_len - 1 {
1179                        return Err(syn::Error::new(
1180                            span,
1181                            "wildcard path param must be the last path param",
1182                        ));
1183                    }
1184                }
1185                PathParam::Capture(_, _, _, _, _) => (),
1186                PathParam::Static(lit) => {
1187                    if lit.value() == "*" && i != path_param_len - 1 {
1188                        return Err(syn::Error::new(
1189                            span,
1190                            "wildcard path param must be the last path param",
1191                        ));
1192                    }
1193                }
1194            }
1195        }
1196
1197        let mut query_params = Vec::new();
1198        if split_route.len() == 2 {
1199            let query = split_route[1];
1200            for query_param in query.split('&') {
1201                query_params.push(Ident::new(query_param, span));
1202            }
1203        }
1204
1205        Ok(Self {
1206            path_params,
1207            query_params,
1208        })
1209    }
1210}
1211
1212enum PathParam {
1213    WildCard(LitStr, Brace, Star, Ident, Box<Type>, Brace),
1214    Capture(LitStr, Brace, Ident, Box<Type>, Brace),
1215    Static(LitStr),
1216}
1217
1218impl PathParam {
1219    fn _captures(&self) -> bool {
1220        matches!(self, Self::Capture(..) | Self::WildCard(..))
1221    }
1222
1223    fn capture(&self) -> Option<(&Ident, &Type)> {
1224        match self {
1225            Self::Capture(_, _, ident, ty, _) => Some((ident, ty)),
1226            Self::WildCard(_, _, _, ident, ty, _) => Some((ident, ty)),
1227            _ => None,
1228        }
1229    }
1230
1231    fn new(str: &str, span: Span, ty: Box<Type>) -> syn::Result<Self> {
1232        let ok = if str.starts_with('{') {
1233            let str = str
1234                .strip_prefix('{')
1235                .unwrap()
1236                .strip_suffix('}')
1237                .ok_or_else(|| {
1238                    syn::Error::new(span, "expected path param to be wrapped in curly braces")
1239                })?;
1240            Self::Capture(
1241                LitStr::new(str, span),
1242                Brace(span),
1243                Ident::new(str, span),
1244                ty,
1245                Brace(span),
1246            )
1247        } else if str.starts_with('*') && str.len() > 1 {
1248            let str = str.strip_prefix('*').unwrap();
1249            Self::WildCard(
1250                LitStr::new(str, span),
1251                Brace(span),
1252                Star(span),
1253                Ident::new(str, span),
1254                ty,
1255                Brace(span),
1256            )
1257        } else if str.starts_with(':') && str.len() > 1 {
1258            let str = str.strip_prefix(':').unwrap();
1259            Self::Capture(
1260                LitStr::new(str, span),
1261                Brace(span),
1262                Ident::new(str, span),
1263                ty,
1264                Brace(span),
1265            )
1266        } else {
1267            Self::Static(LitStr::new(str, span))
1268        };
1269
1270        Ok(ok)
1271    }
1272}
1273
1274struct OapiOptions {
1275    summary: Option<(Ident, LitStr)>,
1276    description: Option<(Ident, LitStr)>,
1277    id: Option<(Ident, LitStr)>,
1278    hidden: Option<(Ident, LitBool)>,
1279    tags: Option<(Ident, StrArray)>,
1280    security: Option<(Ident, Security)>,
1281    responses: Option<(Ident, Responses)>,
1282    transform: Option<(Ident, ExprClosure)>,
1283}
1284
1285struct Security(Vec<(LitStr, StrArray)>);
1286impl Parse for Security {
1287    fn parse(input: ParseStream) -> syn::Result<Self> {
1288        let inner;
1289        braced!(inner in input);
1290
1291        let mut arr = Vec::new();
1292        while !inner.is_empty() {
1293            let scheme = inner.parse::<LitStr>()?;
1294            let _ = inner.parse::<Token![:]>()?;
1295            let scopes = inner.parse::<StrArray>()?;
1296            let _ = inner.parse::<Token![,]>().ok();
1297            arr.push((scheme, scopes));
1298        }
1299
1300        Ok(Self(arr))
1301    }
1302}
1303
1304impl std::fmt::Display for Security {
1305    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1306        write!(f, "{{")?;
1307        for (i, (scheme, scopes)) in self.0.iter().enumerate() {
1308            if i > 0 {
1309                write!(f, ", ")?;
1310            }
1311            write!(f, "{}: {}", scheme.value(), scopes)?;
1312        }
1313        write!(f, "}}")
1314    }
1315}
1316
1317struct Responses(Vec<(LitInt, Type)>);
1318impl Parse for Responses {
1319    fn parse(input: ParseStream) -> syn::Result<Self> {
1320        let inner;
1321        braced!(inner in input);
1322
1323        let mut arr = Vec::new();
1324        while !inner.is_empty() {
1325            let status = inner.parse::<LitInt>()?;
1326            let _ = inner.parse::<Token![:]>()?;
1327            let ty = inner.parse::<Type>()?;
1328            let _ = inner.parse::<Token![,]>().ok();
1329            arr.push((status, ty));
1330        }
1331
1332        Ok(Self(arr))
1333    }
1334}
1335
1336impl std::fmt::Display for Responses {
1337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1338        write!(f, "{{")?;
1339        for (i, (status, ty)) in self.0.iter().enumerate() {
1340            if i > 0 {
1341                write!(f, ", ")?;
1342            }
1343            write!(f, "{}: {}", status, ty.to_token_stream())?;
1344        }
1345        write!(f, "}}")
1346    }
1347}
1348
1349#[derive(Clone)]
1350struct StrArray(Vec<LitStr>);
1351impl Parse for StrArray {
1352    fn parse(input: ParseStream) -> syn::Result<Self> {
1353        let inner;
1354        bracketed!(inner in input);
1355        let mut arr = Vec::new();
1356        while !inner.is_empty() {
1357            arr.push(inner.parse::<LitStr>()?);
1358            inner.parse::<Token![,]>().ok();
1359        }
1360        Ok(Self(arr))
1361    }
1362}
1363
1364impl std::fmt::Display for StrArray {
1365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1366        write!(f, "[")?;
1367        for (i, lit) in self.0.iter().enumerate() {
1368            if i > 0 {
1369                write!(f, ", ")?;
1370            }
1371            write!(f, "\"{}\"", lit.value())?;
1372        }
1373        write!(f, "]")
1374    }
1375}
1376
1377impl Parse for OapiOptions {
1378    fn parse(input: ParseStream) -> syn::Result<Self> {
1379        let mut this = Self {
1380            summary: None,
1381            description: None,
1382            id: None,
1383            hidden: None,
1384            tags: None,
1385            security: None,
1386            responses: None,
1387            transform: None,
1388        };
1389
1390        while !input.is_empty() {
1391            let ident = input.parse::<Ident>()?;
1392            let _ = input.parse::<Token![:]>()?;
1393            match ident.to_string().as_str() {
1394                "summary" => this.summary = Some((ident, input.parse()?)),
1395                "description" => this.description = Some((ident, input.parse()?)),
1396                "id" => this.id = Some((ident, input.parse()?)),
1397                "hidden" => this.hidden = Some((ident, input.parse()?)),
1398                "tags" => this.tags = Some((ident, input.parse()?)),
1399                "security" => this.security = Some((ident, input.parse()?)),
1400                "responses" => this.responses = Some((ident, input.parse()?)),
1401                "transform" => this.transform = Some((ident, input.parse()?)),
1402                _ => {
1403                    return Err(syn::Error::new(
1404                        ident.span(),
1405                        "unexpected field, expected one of (summary, description, id, hidden, tags, security, responses, transform)",
1406                    ))
1407                }
1408            }
1409            let _ = input.parse::<Token![,]>().ok();
1410        }
1411
1412        Ok(this)
1413    }
1414}
1415
1416impl OapiOptions {
1417    fn merge_with_fn(&mut self, function: &ItemFn) {
1418        if self.description.is_none() {
1419            self.description = doc_iter(&function.attrs)
1420                .skip(2)
1421                .map(|item| item.value())
1422                .reduce(|mut acc, item| {
1423                    acc.push('\n');
1424                    acc.push_str(&item);
1425                    acc
1426                })
1427                .map(|item| (parse_quote!(description), parse_quote!(#item)))
1428        }
1429        if self.summary.is_none() {
1430            self.summary = doc_iter(&function.attrs)
1431                .next()
1432                .map(|item| (parse_quote!(summary), item.clone()))
1433        }
1434        if self.id.is_none() {
1435            let id = &function.sig.ident;
1436            self.id = Some((parse_quote!(id), LitStr::new(&id.to_string(), id.span())));
1437        }
1438    }
1439}
1440
1441fn doc_iter(attrs: &[Attribute]) -> impl Iterator<Item = &LitStr> + '_ {
1442    attrs
1443        .iter()
1444        .filter(|attr| attr.path().is_ident("doc"))
1445        .map(|attr| {
1446            let Meta::NameValue(meta) = &attr.meta else {
1447                panic!("doc attribute is not a name-value attribute");
1448            };
1449            let Expr::Lit(lit) = &meta.value else {
1450                panic!("doc attribute is not a string literal");
1451            };
1452            let Lit::Str(lit_str) = &lit.lit else {
1453                panic!("doc attribute is not a string literal");
1454            };
1455            lit_str
1456        })
1457}
1458
1459struct Route {
1460    method: Option<Method>,
1461    path_params: Vec<(Slash, PathParam)>,
1462    query_params: Vec<Ident>,
1463    state: Option<Type>,
1464    route_lit: Option<LitStr>,
1465    prefix: Option<LitStr>,
1466    oapi_options: Option<OapiOptions>,
1467    server_args: Punctuated<FnArg, Comma>,
1468
1469    // todo: support these since `server_fn` had them
1470    _input_encoding: Option<Type>,
1471    _output_encoding: Option<Type>,
1472}
1473
1474impl Parse for Route {
1475    fn parse(input: ParseStream) -> syn::Result<Self> {
1476        let method = if input.peek(Ident) {
1477            Some(input.parse::<Method>()?)
1478        } else {
1479            None
1480        };
1481
1482        let route_lit = input.parse::<LitStr>()?;
1483        let RouteParser {
1484            path_params,
1485            query_params,
1486        } = RouteParser::new(route_lit.clone())?;
1487
1488        // todo: maybe let the user include `State<T>` here, eventually?
1489        // let state = match input.parse::<kw::with>() {
1490        //     Ok(_) => Some(input.parse::<Type>()?),
1491        //     Err(_) => None,
1492        // };
1493
1494        let state = None;
1495        let oapi_options = input
1496            .peek(Brace)
1497            .then(|| {
1498                let inner;
1499                braced!(inner in input);
1500                inner.parse::<OapiOptions>()
1501            })
1502            .transpose()?;
1503
1504        let server_args = if input.peek(Comma) {
1505            let _ = input.parse::<Comma>()?;
1506            input.parse_terminated(FnArg::parse, Comma)?
1507        } else {
1508            Punctuated::new()
1509        };
1510
1511        Ok(Route {
1512            method,
1513            path_params,
1514            query_params,
1515            state,
1516            route_lit: Some(route_lit),
1517            oapi_options,
1518            server_args,
1519            prefix: None,
1520            _input_encoding: None,
1521            _output_encoding: None,
1522        })
1523    }
1524}
1525
1526#[derive(Clone)]
1527enum Method {
1528    Get(Ident),
1529    Post(Ident),
1530    Put(Ident),
1531    Delete(Ident),
1532    Head(Ident),
1533    Connect(Ident),
1534    Options(Ident),
1535    Trace(Ident),
1536}
1537
1538impl ToTokens for Method {
1539    fn to_tokens(&self, tokens: &mut TokenStream2) {
1540        match self {
1541            Self::Get(ident)
1542            | Self::Post(ident)
1543            | Self::Put(ident)
1544            | Self::Delete(ident)
1545            | Self::Head(ident)
1546            | Self::Connect(ident)
1547            | Self::Options(ident)
1548            | Self::Trace(ident) => {
1549                ident.to_tokens(tokens);
1550            }
1551        }
1552    }
1553}
1554
1555impl Parse for Method {
1556    fn parse(input: ParseStream) -> syn::Result<Self> {
1557        let ident = input.parse::<Ident>()?;
1558        match ident.to_string().to_uppercase().as_str() {
1559            "GET" => Ok(Self::Get(ident)),
1560            "POST" => Ok(Self::Post(ident)),
1561            "PUT" => Ok(Self::Put(ident)),
1562            "DELETE" => Ok(Self::Delete(ident)),
1563            "HEAD" => Ok(Self::Head(ident)),
1564            "CONNECT" => Ok(Self::Connect(ident)),
1565            "OPTIONS" => Ok(Self::Options(ident)),
1566            "TRACE" => Ok(Self::Trace(ident)),
1567            _ => Err(input
1568                .error("expected one of (GET, POST, PUT, DELETE, HEAD, CONNECT, OPTIONS, TRACE)")),
1569        }
1570    }
1571}
1572
1573impl Method {
1574    fn to_axum_method_name(&self) -> Ident {
1575        match self {
1576            Self::Get(span) => Ident::new("get", span.span()),
1577            Self::Post(span) => Ident::new("post", span.span()),
1578            Self::Put(span) => Ident::new("put", span.span()),
1579            Self::Delete(span) => Ident::new("delete", span.span()),
1580            Self::Head(span) => Ident::new("head", span.span()),
1581            Self::Connect(span) => Ident::new("connect", span.span()),
1582            Self::Options(span) => Ident::new("options", span.span()),
1583            Self::Trace(span) => Ident::new("trace", span.span()),
1584        }
1585    }
1586
1587    fn new_from_string(s: &str) -> Self {
1588        match s.to_uppercase().as_str() {
1589            "GET" => Self::Get(Ident::new("GET", Span::call_site())),
1590            "POST" => Self::Post(Ident::new("POST", Span::call_site())),
1591            "PUT" => Self::Put(Ident::new("PUT", Span::call_site())),
1592            "DELETE" => Self::Delete(Ident::new("DELETE", Span::call_site())),
1593            "HEAD" => Self::Head(Ident::new("HEAD", Span::call_site())),
1594            "CONNECT" => Self::Connect(Ident::new("CONNECT", Span::call_site())),
1595            "OPTIONS" => Self::Options(Ident::new("OPTIONS", Span::call_site())),
1596            "TRACE" => Self::Trace(Ident::new("TRACE", Span::call_site())),
1597            _ => panic!("expected one of (GET, POST, PUT, DELETE, HEAD, CONNECT, OPTIONS, TRACE)"),
1598        }
1599    }
1600}
1601
1602mod kw {
1603    syn::custom_keyword!(with);
1604}
1605
1606/// The arguments to the `server` macro.
1607///
1608/// These originally came from the `server_fn` crate, but many no longer apply after the 0.7 fullstack
1609/// overhaul. We keep the parser here for temporary backwards compatibility with existing code, but
1610/// these arguments will be removed in a future release.
1611#[derive(Debug)]
1612#[non_exhaustive]
1613#[allow(unused)]
1614struct ServerFnArgs {
1615    /// The name of the struct that will implement the server function trait
1616    /// and be submitted to inventory.
1617    struct_name: Option<Ident>,
1618    /// The prefix to use for the server function URL.
1619    prefix: Option<LitStr>,
1620    /// The input http encoding to use for the server function.
1621    input: Option<Type>,
1622    /// Additional traits to derive on the input struct for the server function.
1623    input_derive: Option<ExprTuple>,
1624    /// The output http encoding to use for the server function.
1625    output: Option<Type>,
1626    /// The path to the server function crate.
1627    fn_path: Option<LitStr>,
1628    /// The server type to use for the server function.
1629    server: Option<Type>,
1630    /// The client type to use for the server function.
1631    client: Option<Type>,
1632    /// The custom wrapper to use for the server function struct.
1633    custom_wrapper: Option<syn::Path>,
1634    /// If the generated input type should implement `From` the only field in the input
1635    impl_from: Option<LitBool>,
1636    /// If the generated input type should implement `Deref` to the only field in the input
1637    impl_deref: Option<LitBool>,
1638    /// The protocol to use for the server function implementation.
1639    protocol: Option<Type>,
1640    builtin_encoding: bool,
1641}
1642
1643impl Parse for ServerFnArgs {
1644    fn parse(stream: ParseStream) -> syn::Result<Self> {
1645        // legacy 4-part arguments
1646        let mut struct_name: Option<Ident> = None;
1647        let mut prefix: Option<LitStr> = None;
1648        let mut encoding: Option<LitStr> = None;
1649        let mut fn_path: Option<LitStr> = None;
1650
1651        // new arguments: can only be keyed by name
1652        let mut input: Option<Type> = None;
1653        let mut input_derive: Option<ExprTuple> = None;
1654        let mut output: Option<Type> = None;
1655        let mut server: Option<Type> = None;
1656        let mut client: Option<Type> = None;
1657        let mut custom_wrapper: Option<syn::Path> = None;
1658        let mut impl_from: Option<LitBool> = None;
1659        let mut impl_deref: Option<LitBool> = None;
1660        let mut protocol: Option<Type> = None;
1661
1662        let mut use_key_and_value = false;
1663        let mut arg_pos = 0;
1664
1665        while !stream.is_empty() {
1666            arg_pos += 1;
1667            let lookahead = stream.lookahead1();
1668            if lookahead.peek(Ident) {
1669                let key_or_value: Ident = stream.parse()?;
1670
1671                let lookahead = stream.lookahead1();
1672                if lookahead.peek(Token![=]) {
1673                    stream.parse::<Token![=]>()?;
1674                    let key = key_or_value;
1675                    use_key_and_value = true;
1676                    if key == "name" {
1677                        if struct_name.is_some() {
1678                            return Err(syn::Error::new(
1679                                key.span(),
1680                                "keyword argument repeated: `name`",
1681                            ));
1682                        }
1683                        struct_name = Some(stream.parse()?);
1684                    } else if key == "prefix" {
1685                        if prefix.is_some() {
1686                            return Err(syn::Error::new(
1687                                key.span(),
1688                                "keyword argument repeated: `prefix`",
1689                            ));
1690                        }
1691                        prefix = Some(stream.parse()?);
1692                    } else if key == "encoding" {
1693                        if encoding.is_some() {
1694                            return Err(syn::Error::new(
1695                                key.span(),
1696                                "keyword argument repeated: `encoding`",
1697                            ));
1698                        }
1699                        encoding = Some(stream.parse()?);
1700                    } else if key == "endpoint" {
1701                        if fn_path.is_some() {
1702                            return Err(syn::Error::new(
1703                                key.span(),
1704                                "keyword argument repeated: `endpoint`",
1705                            ));
1706                        }
1707                        fn_path = Some(stream.parse()?);
1708                    } else if key == "input" {
1709                        if encoding.is_some() {
1710                            return Err(syn::Error::new(
1711                                key.span(),
1712                                "`encoding` and `input` should not both be \
1713                                 specified",
1714                            ));
1715                        } else if input.is_some() {
1716                            return Err(syn::Error::new(
1717                                key.span(),
1718                                "keyword argument repeated: `input`",
1719                            ));
1720                        }
1721                        input = Some(stream.parse()?);
1722                    } else if key == "input_derive" {
1723                        if input_derive.is_some() {
1724                            return Err(syn::Error::new(
1725                                key.span(),
1726                                "keyword argument repeated: `input_derive`",
1727                            ));
1728                        }
1729                        input_derive = Some(stream.parse()?);
1730                    } else if key == "output" {
1731                        if encoding.is_some() {
1732                            return Err(syn::Error::new(
1733                                key.span(),
1734                                "`encoding` and `output` should not both be \
1735                                 specified",
1736                            ));
1737                        } else if output.is_some() {
1738                            return Err(syn::Error::new(
1739                                key.span(),
1740                                "keyword argument repeated: `output`",
1741                            ));
1742                        }
1743                        output = Some(stream.parse()?);
1744                    } else if key == "server" {
1745                        if server.is_some() {
1746                            return Err(syn::Error::new(
1747                                key.span(),
1748                                "keyword argument repeated: `server`",
1749                            ));
1750                        }
1751                        server = Some(stream.parse()?);
1752                    } else if key == "client" {
1753                        if client.is_some() {
1754                            return Err(syn::Error::new(
1755                                key.span(),
1756                                "keyword argument repeated: `client`",
1757                            ));
1758                        }
1759                        client = Some(stream.parse()?);
1760                    } else if key == "custom" {
1761                        if custom_wrapper.is_some() {
1762                            return Err(syn::Error::new(
1763                                key.span(),
1764                                "keyword argument repeated: `custom`",
1765                            ));
1766                        }
1767                        custom_wrapper = Some(stream.parse()?);
1768                    } else if key == "impl_from" {
1769                        if impl_from.is_some() {
1770                            return Err(syn::Error::new(
1771                                key.span(),
1772                                "keyword argument repeated: `impl_from`",
1773                            ));
1774                        }
1775                        impl_from = Some(stream.parse()?);
1776                    } else if key == "impl_deref" {
1777                        if impl_deref.is_some() {
1778                            return Err(syn::Error::new(
1779                                key.span(),
1780                                "keyword argument repeated: `impl_deref`",
1781                            ));
1782                        }
1783                        impl_deref = Some(stream.parse()?);
1784                    } else if key == "protocol" {
1785                        if protocol.is_some() {
1786                            return Err(syn::Error::new(
1787                                key.span(),
1788                                "keyword argument repeated: `protocol`",
1789                            ));
1790                        }
1791                        protocol = Some(stream.parse()?);
1792                    } else {
1793                        return Err(lookahead.error());
1794                    }
1795                } else {
1796                    let value = key_or_value;
1797                    if use_key_and_value {
1798                        return Err(syn::Error::new(
1799                            value.span(),
1800                            "positional argument follows keyword argument",
1801                        ));
1802                    }
1803                    if arg_pos == 1 {
1804                        struct_name = Some(value)
1805                    } else {
1806                        return Err(syn::Error::new(value.span(), "expected string literal"));
1807                    }
1808                }
1809            } else if lookahead.peek(LitStr) {
1810                if use_key_and_value {
1811                    return Err(syn::Error::new(
1812                        stream.span(),
1813                        "If you use keyword arguments (e.g., `name` = \
1814                         Something), then you can no longer use arguments \
1815                         without a keyword.",
1816                    ));
1817                }
1818                match arg_pos {
1819                    1 => return Err(lookahead.error()),
1820                    2 => prefix = Some(stream.parse()?),
1821                    3 => encoding = Some(stream.parse()?),
1822                    4 => fn_path = Some(stream.parse()?),
1823                    _ => return Err(syn::Error::new(stream.span(), "unexpected extra argument")),
1824                }
1825            } else {
1826                return Err(lookahead.error());
1827            }
1828
1829            if !stream.is_empty() {
1830                stream.parse::<Token![,]>()?;
1831            }
1832        }
1833
1834        // parse legacy encoding into input/output
1835        let mut builtin_encoding = false;
1836        if let Some(encoding) = encoding {
1837            match encoding.value().to_lowercase().as_str() {
1838                "url" => {
1839                    input = Some(type_from_ident(syn::parse_quote!(Url)));
1840                    output = Some(type_from_ident(syn::parse_quote!(Json)));
1841                    builtin_encoding = true;
1842                }
1843                "cbor" => {
1844                    input = Some(type_from_ident(syn::parse_quote!(Cbor)));
1845                    output = Some(type_from_ident(syn::parse_quote!(Cbor)));
1846                    builtin_encoding = true;
1847                }
1848                "getcbor" => {
1849                    input = Some(type_from_ident(syn::parse_quote!(GetUrl)));
1850                    output = Some(type_from_ident(syn::parse_quote!(Cbor)));
1851                    builtin_encoding = true;
1852                }
1853                "getjson" => {
1854                    input = Some(type_from_ident(syn::parse_quote!(GetUrl)));
1855                    output = Some(syn::parse_quote!(Json));
1856                    builtin_encoding = true;
1857                }
1858                _ => return Err(syn::Error::new(encoding.span(), "Encoding not found.")),
1859            }
1860        }
1861
1862        Ok(Self {
1863            struct_name,
1864            prefix,
1865            input,
1866            input_derive,
1867            output,
1868            fn_path,
1869            builtin_encoding,
1870            server,
1871            client,
1872            custom_wrapper,
1873            impl_from,
1874            impl_deref,
1875            protocol,
1876        })
1877    }
1878}
1879
1880/// An argument type in a server function.
1881#[allow(unused)]
1882// todo - we used to support a number of these attributes and pass them along to serde. bring them back.
1883#[derive(Debug, Clone)]
1884struct ServerFnArg {
1885    /// The attributes on the server function argument.
1886    server_fn_attributes: Vec<Attribute>,
1887    /// The type of the server function argument.
1888    arg: syn::PatType,
1889}
1890
1891impl ToTokens for ServerFnArg {
1892    fn to_tokens(&self, tokens: &mut TokenStream2) {
1893        let ServerFnArg { arg, .. } = self;
1894        tokens.extend(quote! {
1895            #arg
1896        });
1897    }
1898}
1899
1900impl Parse for ServerFnArg {
1901    fn parse(input: ParseStream) -> Result<Self> {
1902        let arg: syn::FnArg = input.parse()?;
1903        let mut arg = match arg {
1904            FnArg::Receiver(_) => {
1905                return Err(syn::Error::new(
1906                    arg.span(),
1907                    "cannot use receiver types in server function macro",
1908                ))
1909            }
1910            FnArg::Typed(t) => t,
1911        };
1912
1913        fn rename_path(path: Path, from_ident: Ident, to_ident: Ident) -> Path {
1914            if path.is_ident(&from_ident) {
1915                Path {
1916                    leading_colon: None,
1917                    segments: Punctuated::from_iter([PathSegment {
1918                        ident: to_ident,
1919                        arguments: PathArguments::None,
1920                    }]),
1921                }
1922            } else {
1923                path
1924            }
1925        }
1926
1927        let server_fn_attributes = arg
1928            .attrs
1929            .iter()
1930            .cloned()
1931            .map(|attr| {
1932                if attr.path().is_ident("server") {
1933                    // Allow the following attributes:
1934                    // - #[server(default)]
1935                    // - #[server(rename = "fieldName")]
1936
1937                    // Rename `server` to `serde`
1938                    let attr = Attribute {
1939                        meta: match attr.meta {
1940                            Meta::Path(path) => Meta::Path(rename_path(
1941                                path,
1942                                format_ident!("server"),
1943                                format_ident!("serde"),
1944                            )),
1945                            Meta::List(mut list) => {
1946                                list.path = rename_path(
1947                                    list.path,
1948                                    format_ident!("server"),
1949                                    format_ident!("serde"),
1950                                );
1951                                Meta::List(list)
1952                            }
1953                            Meta::NameValue(mut name_value) => {
1954                                name_value.path = rename_path(
1955                                    name_value.path,
1956                                    format_ident!("server"),
1957                                    format_ident!("serde"),
1958                                );
1959                                Meta::NameValue(name_value)
1960                            }
1961                        },
1962                        ..attr
1963                    };
1964
1965                    let args = attr.parse_args::<Meta>()?;
1966                    match args {
1967                        // #[server(default)]
1968                        Meta::Path(path) if path.is_ident("default") => Ok(attr.clone()),
1969                        // #[server(flatten)]
1970                        Meta::Path(path) if path.is_ident("flatten") => Ok(attr.clone()),
1971                        // #[server(default = "value")]
1972                        Meta::NameValue(name_value) if name_value.path.is_ident("default") => {
1973                            Ok(attr.clone())
1974                        }
1975                        // #[server(skip)]
1976                        Meta::Path(path) if path.is_ident("skip") => Ok(attr.clone()),
1977                        // #[server(rename = "value")]
1978                        Meta::NameValue(name_value) if name_value.path.is_ident("rename") => {
1979                            Ok(attr.clone())
1980                        }
1981                        _ => Err(Error::new(
1982                            attr.span(),
1983                            "Unrecognized #[server] attribute, expected \
1984                             #[server(default)] or #[server(rename = \
1985                             \"fieldName\")]",
1986                        )),
1987                    }
1988                } else if attr.path().is_ident("doc") {
1989                    // Allow #[doc = "documentation"]
1990                    Ok(attr.clone())
1991                } else if attr.path().is_ident("allow") {
1992                    // Allow #[allow(...)]
1993                    Ok(attr.clone())
1994                } else if attr.path().is_ident("deny") {
1995                    // Allow #[deny(...)]
1996                    Ok(attr.clone())
1997                } else if attr.path().is_ident("ignore") {
1998                    // Allow #[ignore]
1999                    Ok(attr.clone())
2000                } else {
2001                    Err(Error::new(
2002                        attr.span(),
2003                        "Unrecognized attribute, expected #[server(...)]",
2004                    ))
2005                }
2006            })
2007            .collect::<Result<Vec<_>>>()?;
2008        arg.attrs = vec![];
2009        Ok(ServerFnArg {
2010            arg,
2011            server_fn_attributes,
2012        })
2013    }
2014}
2015
2016fn type_from_ident(ident: Ident) -> Type {
2017    let mut segments = Punctuated::new();
2018    segments.push(PathSegment {
2019        ident,
2020        arguments: PathArguments::None,
2021    });
2022    Type::Path(TypePath {
2023        qself: None,
2024        path: Path {
2025            leading_colon: None,
2026            segments,
2027        },
2028    })
2029}