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