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