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 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 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 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 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 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 Some(_) => quote! { "" },
399
400 None => {
402 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 ServerFnEncoder, ServerFnDecoder, DioxusServerState,
431
432 ExtractRequest, EncodeRequest, RequestDecodeResult, RequestDecodeErr,
434
435 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 #[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 #[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 }
568
569 path
570 }
571
572 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 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 }
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 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 _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 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#[derive(Debug)]
1565#[non_exhaustive]
1566#[allow(unused)]
1567struct ServerFnArgs {
1568 struct_name: Option<Ident>,
1571 prefix: Option<LitStr>,
1573 input: Option<Type>,
1575 input_derive: Option<ExprTuple>,
1577 output: Option<Type>,
1579 fn_path: Option<LitStr>,
1581 server: Option<Type>,
1583 client: Option<Type>,
1585 custom_wrapper: Option<syn::Path>,
1587 impl_from: Option<LitBool>,
1589 impl_deref: Option<LitBool>,
1591 protocol: Option<Type>,
1593 builtin_encoding: bool,
1594}
1595
1596impl Parse for ServerFnArgs {
1597 fn parse(stream: ParseStream) -> syn::Result<Self> {
1598 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 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 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#[allow(unused)]
1835#[derive(Debug, Clone)]
1837struct ServerFnArg {
1838 server_fn_attributes: Vec<Attribute>,
1840 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 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 Meta::Path(path) if path.is_ident("default") => Ok(attr.clone()),
1922 Meta::Path(path) if path.is_ident("flatten") => Ok(attr.clone()),
1924 Meta::NameValue(name_value) if name_value.path.is_ident("default") => {
1926 Ok(attr.clone())
1927 }
1928 Meta::Path(path) if path.is_ident("skip") => Ok(attr.clone()),
1930 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 Ok(attr.clone())
1944 } else if attr.path().is_ident("allow") {
1945 Ok(attr.clone())
1947 } else if attr.path().is_ident("deny") {
1948 Ok(attr.clone())
1950 } else if attr.path().is_ident("ignore") {
1951 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}