1#![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#[proc_macro_attribute]
91pub fn server(attr: proc_macro::TokenStream, mut item: TokenStream) -> TokenStream {
92 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 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 tokens.into()
139 }
140
141 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 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 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 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 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 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 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 Some(_) => quote! { "" },
414
415 None => {
417 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 ServerFnEncoder, ServerFnDecoder, DioxusServerState,
477
478 ExtractRequest, EncodeRequest, RequestDecodeResult, RequestDecodeErr,
480
481 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 #[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 #[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 }
615
616 path
617 }
618
619 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 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 }
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 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 _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 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#[derive(Debug)]
1612#[non_exhaustive]
1613#[allow(unused)]
1614struct ServerFnArgs {
1615 struct_name: Option<Ident>,
1618 prefix: Option<LitStr>,
1620 input: Option<Type>,
1622 input_derive: Option<ExprTuple>,
1624 output: Option<Type>,
1626 fn_path: Option<LitStr>,
1628 server: Option<Type>,
1630 client: Option<Type>,
1632 custom_wrapper: Option<syn::Path>,
1634 impl_from: Option<LitBool>,
1636 impl_deref: Option<LitBool>,
1638 protocol: Option<Type>,
1640 builtin_encoding: bool,
1641}
1642
1643impl Parse for ServerFnArgs {
1644 fn parse(stream: ParseStream) -> syn::Result<Self> {
1645 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 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 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#[allow(unused)]
1882#[derive(Debug, Clone)]
1884struct ServerFnArg {
1885 server_fn_attributes: Vec<Attribute>,
1887 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 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 Meta::Path(path) if path.is_ident("default") => Ok(attr.clone()),
1969 Meta::Path(path) if path.is_ident("flatten") => Ok(attr.clone()),
1971 Meta::NameValue(name_value) if name_value.path.is_ident("default") => {
1973 Ok(attr.clone())
1974 }
1975 Meta::Path(path) if path.is_ident("skip") => Ok(attr.clone()),
1977 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 Ok(attr.clone())
1991 } else if attr.path().is_ident("allow") {
1992 Ok(attr.clone())
1994 } else if attr.path().is_ident("deny") {
1995 Ok(attr.clone())
1997 } else if attr.path().is_ident("ignore") {
1998 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}