1use crate::{
2 util::{CollectToResult, ExpectLit, IntoIdent},
3 AttributeArgs
4};
5use lazy_regex::regex_is_match;
6use paste::paste;
7use proc_macro2::{Ident, Span, TokenStream};
8use quote::{format_ident, quote, quote_spanned, ToTokens};
9use std::str::FromStr;
10use syn::{
11 parse::Parse, spanned::Spanned, Attribute, Error, Expr, FnArg, ItemFn, LitBool, LitStr, Meta,
12 PatType, Result, ReturnType, Type
13};
14use unindent::Unindent;
15
16#[allow(clippy::large_enum_variant)]
17pub enum EndpointType {
18 ReadAll,
19 Read,
20 Search,
21 Create,
22 UpdateAll,
23 Update,
24 DeleteAll,
25 Delete,
26 Custom {
27 method: Option<Expr>,
28 uri: Option<LitStr>,
29 params: Option<LitBool>,
30 body: Option<LitBool>
31 }
32}
33
34impl EndpointType {
35 pub fn custom() -> Self {
36 Self::Custom {
37 method: None,
38 uri: None,
39 params: None,
40 body: None
41 }
42 }
43}
44
45macro_rules! endpoint_type_setter {
46 ($name:ident : $ty:ty) => {
47 impl EndpointType {
48 paste! {
49 fn [<set_ $name>](&mut self, span: Span, [<new_ $name>]: $ty) -> Result<()> {
50 match self {
51 Self::Custom { $name, .. } if $name.is_some() => {
52 Err(Error::new(span, concat!("`", stringify!($name), "` must not appear more than once")))
53 },
54 Self::Custom { $name, .. } => {
55 *$name = Some([<new_ $name>]);
56 Ok(())
57 },
58 _ => Err(Error::new(span, concat!("`", stringify!($name), "` can only be used on custom endpoints")))
59 }
60 }
61 }
62 }
63 };
64}
65
66endpoint_type_setter!(method: Expr);
67endpoint_type_setter!(uri: LitStr);
68endpoint_type_setter!(params: LitBool);
69endpoint_type_setter!(body: LitBool);
70
71impl FromStr for EndpointType {
72 type Err = Error;
73
74 fn from_str(str: &str) -> Result<Self> {
75 match str {
76 "ReadAll" | "read_all" => Ok(Self::ReadAll),
77 "Read" | "read" => Ok(Self::Read),
78 "Search" | "search" => Ok(Self::Search),
79 "Create" | "create" => Ok(Self::Create),
80 "ChangeAll" | "change_all" => Ok(Self::UpdateAll),
81 "Change" | "change" => Ok(Self::Update),
82 "RemoveAll" | "remove_all" => Ok(Self::DeleteAll),
83 "Remove" | "remove" => Ok(Self::Delete),
84 _ => Err(Error::new(
85 Span::call_site(),
86 format!("Unknown method: `{str}'")
87 ))
88 }
89 }
90}
91
92impl EndpointType {
93 fn http_method(&self) -> Option<TokenStream> {
94 let hyper_method = quote!(::gotham_restful::gotham::hyper::Method);
95 match self {
96 Self::ReadAll | Self::Read | Self::Search => Some(quote!(#hyper_method::GET)),
97 Self::Create => Some(quote!(#hyper_method::POST)),
98 Self::UpdateAll | Self::Update => Some(quote!(#hyper_method::PUT)),
99 Self::DeleteAll | Self::Delete => Some(quote!(#hyper_method::DELETE)),
100 Self::Custom { method, .. } => method.as_ref().map(ToTokens::to_token_stream)
101 }
102 }
103
104 fn uri(&self) -> Option<TokenStream> {
105 match self {
106 Self::ReadAll | Self::Create | Self::UpdateAll | Self::DeleteAll => Some(quote!("")),
107 Self::Read | Self::Update | Self::Delete => Some(quote!(":id")),
108 Self::Search => Some(quote!("search")),
109 Self::Custom { uri, .. } => uri.as_ref().map(ToTokens::to_token_stream)
110 }
111 }
112
113 fn operation_verb(&self) -> TokenStream {
114 let some = quote!(::core::option::Option::Some);
115 match self {
116 Self::ReadAll => quote!(#some("read_all")),
117 Self::Read => quote!(#some("read")),
118 Self::Search => quote!(#some("search")),
119 Self::Create => quote!(#some("create")),
120 Self::UpdateAll => quote!(#some("update_all")),
121 Self::Update => quote!(#some("update")),
122 Self::DeleteAll => quote!(#some("delete_all")),
123 Self::Delete => quote!(#some("delete")),
124 Self::Custom { .. } => quote!(::core::option::Option::None)
125 }
126 }
127
128 fn has_placeholders(&self) -> LitBool {
129 match self {
130 Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => {
131 LitBool {
132 value: false,
133 span: Span::call_site()
134 }
135 },
136 Self::Read | Self::Update | Self::Delete => LitBool {
137 value: true,
138 span: Span::call_site()
139 },
140 Self::Custom { uri, .. } => LitBool {
141 value: uri
142 .as_ref()
143 .map(|uri| regex_is_match!(r#"(^|/):[^/]+(/|$)"#, &uri.value()))
144 .unwrap_or(false),
145 span: Span::call_site()
146 }
147 }
148 }
149
150 fn placeholders_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
151 match self {
152 Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => {
153 quote!(::gotham_restful::NoopExtractor)
154 },
155 Self::Read | Self::Update | Self::Delete => {
156 quote!(::gotham_restful::private::IdPlaceholder::<#arg_ty>)
157 },
158 Self::Custom { .. } => {
159 if self.has_placeholders().value {
160 arg_ty.to_token_stream()
161 } else {
162 quote!(::gotham_restful::NoopExtractor)
163 }
164 },
165 }
166 }
167
168 fn needs_params(&self) -> LitBool {
169 match self {
170 Self::ReadAll
171 | Self::Read
172 | Self::Create
173 | Self::UpdateAll
174 | Self::Update
175 | Self::DeleteAll
176 | Self::Delete => LitBool {
177 value: false,
178 span: Span::call_site()
179 },
180 Self::Search => LitBool {
181 value: true,
182 span: Span::call_site()
183 },
184 Self::Custom { params, .. } => params.clone().unwrap_or_else(|| LitBool {
185 value: false,
186 span: Span::call_site()
187 })
188 }
189 }
190
191 fn params_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
192 match self {
193 Self::ReadAll
194 | Self::Read
195 | Self::Create
196 | Self::UpdateAll
197 | Self::Update
198 | Self::DeleteAll
199 | Self::Delete => {
200 quote!(::gotham_restful::NoopExtractor)
201 },
202 Self::Search => quote!(#arg_ty),
203 Self::Custom { .. } => {
204 if self.needs_params().value {
205 arg_ty.to_token_stream()
206 } else {
207 quote!(::gotham_restful::NoopExtractor)
208 }
209 },
210 }
211 }
212
213 fn needs_body(&self) -> LitBool {
214 match self {
215 Self::ReadAll | Self::Read | Self::Search | Self::DeleteAll | Self::Delete => LitBool {
216 value: false,
217 span: Span::call_site()
218 },
219 Self::Create | Self::UpdateAll | Self::Update => LitBool {
220 value: true,
221 span: Span::call_site()
222 },
223 Self::Custom { body, .. } => body.clone().unwrap_or_else(|| LitBool {
224 value: false,
225 span: Span::call_site()
226 })
227 }
228 }
229
230 fn body_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
231 match self {
232 Self::ReadAll | Self::Read | Self::Search | Self::DeleteAll | Self::Delete => {
233 quote!(())
234 },
235 Self::Create | Self::UpdateAll | Self::Update => quote!(#arg_ty),
236 Self::Custom { .. } => {
237 if self.needs_body().value {
238 arg_ty.to_token_stream()
239 } else {
240 quote!(())
241 }
242 },
243 }
244 }
245}
246
247#[allow(clippy::large_enum_variant)]
248enum HandlerArgType {
249 StateRef,
250 StateMutRef,
251 MethodArg(Type),
252 DatabaseConnection(Type),
253 AuthStatus(Type),
254 AuthStatusRef(Type)
255}
256
257impl HandlerArgType {
258 fn is_method_arg(&self) -> bool {
259 matches!(self, Self::MethodArg(_))
260 }
261
262 fn is_database_conn(&self) -> bool {
263 matches!(self, Self::DatabaseConnection(_))
264 }
265
266 fn is_auth_status(&self) -> bool {
267 matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_))
268 }
269
270 fn ty(&self) -> Option<&Type> {
271 match self {
272 Self::MethodArg(ty)
273 | Self::DatabaseConnection(ty)
274 | Self::AuthStatus(ty)
275 | Self::AuthStatusRef(ty) => Some(ty),
276 _ => None
277 }
278 }
279
280 fn quote_ty(&self) -> Option<TokenStream> {
281 self.ty().map(|ty| quote!(#ty))
282 }
283}
284
285struct HandlerArg {
286 ident_span: Span,
287 ty: HandlerArgType
288}
289
290impl HandlerArg {
291 fn span(&self) -> Span {
292 self.ident_span
293 }
294}
295
296fn interpret_arg_ty(attrs: &[Attribute], name: &str, ty: Type) -> Result<HandlerArgType> {
297 let attr = attrs
298 .iter()
299 .filter_map(|attr| {
300 match &attr.meta {
301 Meta::List(meta) if meta.path.is_ident("rest_arg") => {
302 Some(meta.tokens.to_string())
304 },
305 _ => None
306 }
307 })
308 .next();
309
310 if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state"))
312 {
313 return match ty {
314 Type::Reference(ty) => Ok(if ty.mutability.is_none() {
315 HandlerArgType::StateRef
316 } else {
317 HandlerArgType::StateMutRef
318 }),
319 _ => Err(Error::new(
320 ty.span(),
321 "The state parameter has to be a (mutable) reference to gotham_restful::State"
322 ))
323 };
324 }
325
326 if cfg!(feature = "auth")
327 && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth"))
328 {
329 return Ok(match ty {
330 Type::Reference(ty) => HandlerArgType::AuthStatusRef(*ty.elem),
331 ty => HandlerArgType::AuthStatus(ty)
332 });
333 }
334
335 if cfg!(feature = "database")
336 && (attr.as_deref() == Some("connection")
337 || attr.as_deref() == Some("conn")
338 || (attr.is_none() && name == "conn"))
339 {
340 return Ok(HandlerArgType::DatabaseConnection(match ty {
341 Type::Reference(ty) => *ty.elem,
342 ty => ty
343 }));
344 }
345
346 Ok(HandlerArgType::MethodArg(ty))
347}
348
349fn interpret_arg(_index: usize, arg: &PatType) -> Result<HandlerArg> {
350 let pat = &arg.pat;
351 let orig_name = quote!(#pat);
352 let ty = interpret_arg_ty(&arg.attrs, &orig_name.to_string(), *arg.ty.clone())?;
353
354 Ok(HandlerArg {
355 ident_span: arg.pat.span(),
356 ty
357 })
358}
359
360#[cfg(feature = "openapi")]
361fn expand_operation_verb(operation_verb: TokenStream) -> Option<TokenStream> {
362 Some(quote! {
363 fn operation_verb() -> ::core::option::Option<&'static ::core::primitive::str> {
364 #operation_verb
365 }
366 })
367}
368
369#[cfg(not(feature = "openapi"))]
370fn expand_operation_verb(_: TokenStream) -> Option<TokenStream> {
371 None
372}
373
374#[cfg(feature = "openapi")]
375fn expand_operation_id(fun_ident: &Ident, operation_id: Option<LitStr>) -> Option<TokenStream> {
376 let op_id = match operation_id {
377 Some(operation_id) => quote! {
378 ::gotham_restful::OperationId::Manual(
379 ::std::string::String::from(#operation_id)
380 )
381 },
382 None => {
383 let verb = fun_ident.to_string();
384 quote! {
385 ::gotham_restful::OperationId::SemiAuto(
386 ::std::borrow::Cow::Borrowed(#verb)
387 )
388 }
389 }
390 };
391 Some(quote! {
392 fn operation_id() -> ::gotham_restful::OperationId {
393 #op_id
394 }
395 })
396}
397
398#[cfg(not(feature = "openapi"))]
399fn expand_operation_id(_: &Ident, _: Option<LitStr>) -> Option<TokenStream> {
400 None
401}
402
403fn expand_wants_auth(wants_auth: Option<LitBool>, default: bool) -> TokenStream {
404 let wants_auth = wants_auth.unwrap_or_else(|| LitBool {
405 value: default,
406 span: Span::call_site()
407 });
408
409 quote! {
410 fn wants_auth() -> ::core::primitive::bool {
411 #wants_auth
412 }
413 }
414}
415
416pub fn endpoint_ident(fn_ident: &Ident) -> Ident {
417 format_ident!("{}___gotham_restful_endpoint", fn_ident)
418}
419
420macro_rules! error_if_not_openapi {
421 ($($ident:ident),*) => {
422 $(
423 #[cfg(not(feature = "openapi"))]
424 if let Some($ident) = $ident {
425 return Err(Error::new(
426 $ident.span(),
427 concat!("`", stringify!($ident), "` is only supported with the openapi feature")
428 ));
429 }
430 )*
431 };
432}
433
434#[allow(clippy::needless_collect)]
436fn expand_endpoint_type(
437 mut ty: EndpointType,
438 AttributeArgs(attrs): AttributeArgs,
439 fun: &ItemFn
440) -> Result<TokenStream> {
441 if let Some(unsafety) = fun.sig.unsafety {
443 return Err(Error::new(
444 unsafety.span(),
445 "Endpoint handler methods must not be unsafe"
446 ));
447 }
448
449 let mut debug: bool = false;
451 let mut operation_id: Option<LitStr> = None;
452 let mut schema: Option<Ident> = None;
453 let mut status_codes: Option<Ident> = None;
454 let mut wants_auth: Option<LitBool> = None;
455 for meta in attrs {
456 match meta {
457 Meta::NameValue(kv) => {
458 if kv.path.is_ident("debug") {
459 debug = kv.value.expect_bool()?.value;
460 } else if kv.path.is_ident("operation_id") {
461 operation_id = Some(kv.value.expect_str()?);
462 } else if kv.path.is_ident("schema") {
463 schema = Some(kv.value.expect_str()?.into_ident())
464 } else if kv.path.is_ident("status_codes") {
465 status_codes = Some(kv.value.expect_str()?.into_ident())
466 } else if kv.path.is_ident("wants_auth") {
467 wants_auth = Some(kv.value.expect_bool()?);
468 } else if kv.path.is_ident("method") {
469 ty.set_method(
470 kv.path.span(),
471 kv.value.expect_str()?.parse_with(Expr::parse)?
472 )?;
473 } else if kv.path.is_ident("uri") {
474 ty.set_uri(kv.path.span(), kv.value.expect_str()?)?;
475 } else if kv.path.is_ident("params") {
476 ty.set_params(kv.path.span(), kv.value.expect_bool()?)?;
477 } else if kv.path.is_ident("body") {
478 ty.set_body(kv.path.span(), kv.value.expect_bool()?)?;
479 } else {
480 return Err(Error::new(kv.path.span(), "Unknown attribute"));
481 }
482 },
483 _ => return Err(Error::new(meta.span(), "Invalid attribute syntax"))
484 }
485 }
486 error_if_not_openapi!(operation_id, schema, status_codes);
487 if schema.is_some() != status_codes.is_some() {
488 return Err(Error::new(
489 schema
490 .map(|s| s.span())
491 .unwrap_or_else(|| status_codes.unwrap().span()),
492 "`schema` and `status_codes` may only be used together"
493 ));
494 }
495
496 let mut doc: Vec<String> = vec![String::new()];
498 for attr in &fun.attrs {
499 match &attr.meta {
500 Meta::NameValue(kv) if kv.path.is_ident("doc") => {
501 doc.push(kv.value.clone().expect_str()?.value())
502 },
503 _ => {}
504 }
505 }
506 let doc = doc.join("\n").unindent();
507 #[cfg_attr(not(feature = "openapi"), allow(unused_variables))]
508 let doc = doc.trim_end();
509
510 #[allow(unused_mut)]
511 let mut description: Option<TokenStream> = None;
512 #[cfg(feature = "openapi")]
513 if !doc.is_empty() {
514 description = Some(quote! {
515 fn description() -> ::core::option::Option<::std::string::String> {
516 ::core::option::Option::Some(::std::string::String::from(#doc))
517 }
518 });
519 }
520
521 let args = fun
523 .sig
524 .inputs
525 .iter()
526 .enumerate()
527 .map(|(i, arg)| match arg {
528 FnArg::Typed(arg) => interpret_arg(i, arg),
529 FnArg::Receiver(_) => Err(Error::new(arg.span(), "Didn't expect self parameter"))
530 })
531 .collect_to_result()?;
532
533 let fun_vis = &fun.vis;
534 let fun_ident = &fun.sig.ident;
535 let fun_is_async = fun.sig.asyncness.is_some();
536
537 let ident = endpoint_ident(fun_ident);
538 let (output_ty, is_no_content) = match &fun.sig.output {
539 ReturnType::Default => (quote!(::gotham_restful::NoContent), true),
540 ReturnType::Type(_, ty) => (quote!(#ty), false)
541 };
542 let output_struct_ident = schema.as_ref().map(|schema_fn| {
543 Ident::new(
544 &format!("{schema_fn}_gotham_restful_ResponseSchema"),
545 Span::call_site()
546 )
547 });
548 let output_struct = schema.map(|schema_fn| {
549 let output_struct_ident = output_struct_ident
550 .as_ref()
551 .unwrap_or_else(|| unreachable!());
552 let status_codes_fn = status_codes.unwrap_or_else(|| unreachable!());
553
554 let schema_call = quote_spanned! { schema_fn.span() =>
555 let schema = ::gotham_restful::private::CustomSchema::schema(#schema_fn, code);
556 };
557 let status_codes_call = quote_spanned! { status_codes_fn.span() =>
558 let status_codes = ::gotham_restful::private::CustomStatusCodes::status_codes(#status_codes_fn);
559 };
560
561 quote! {
562 #[allow(non_camel_case_types)]
563 struct #output_struct_ident(#output_ty);
564
565 impl ::core::convert::From<#output_ty> for #output_struct_ident {
566 fn from(o: #output_ty) -> Self {
567 Self(o)
568 }
569 }
570
571 impl ::gotham_restful::IntoResponse for #output_struct_ident
572 where
573 #output_ty: ::gotham_restful::IntoResponse
574 {
575 type Err = <#output_ty as ::gotham_restful::IntoResponse>::Err;
576
577 fn into_response(self) -> ::gotham_restful::private::BoxFuture<'static,
578 ::core::result::Result<::gotham_restful::Response, Self::Err>>
579 {
580 ::gotham_restful::IntoResponse::into_response(self.0)
581 }
582
583 fn accepted_types() -> ::core::option::Option<
584 ::std::vec::Vec<::gotham_restful::gotham::mime::Mime>>
585 {
586 <#output_ty as ::gotham_restful::IntoResponse>::accepted_types()
587 }
588 }
589
590 impl ::gotham_restful::ResponseSchema for #output_struct_ident {
591 fn schema(
592 code: ::gotham_restful::gotham::hyper::StatusCode
593 ) -> ::gotham_restful::private::OpenapiSchema
594 {
595 #schema_call
596 schema
597 }
598
599 fn status_codes() -> ::std::vec::Vec<::gotham_restful::gotham::hyper::StatusCode> {
600 #status_codes_call
601 status_codes
602 }
603 }
604 }
605 });
606 let (output_typedef, final_return_ty) = match output_struct_ident {
607 Some(output_struct_ident) => (
608 quote!(type Output = #output_struct_ident;),
609 quote!(#output_struct_ident)
610 ),
611 None => (
612 quote_spanned!(output_ty.span() => type Output = #output_ty;),
613 quote!(#output_ty)
614 )
615 };
616
617 let arg_tys = args
618 .iter()
619 .filter(|arg| arg.ty.is_method_arg())
620 .collect::<Vec<_>>();
621 let mut arg_ty_idx = 0;
622 let mut next_arg_ty = |return_none: bool| {
623 if return_none {
624 return Ok(None);
625 }
626 if arg_ty_idx >= arg_tys.len() {
627 return Err(Error::new(
628 fun_ident.span(),
629 "Too few arguments for this endpoint handler"
630 ));
631 }
632 let ty = arg_tys[arg_ty_idx].ty.ty().unwrap();
633 arg_ty_idx += 1;
634 Ok(Some(ty))
635 };
636
637 let http_method = ty.http_method().ok_or_else(|| {
638 Error::new(
639 Span::call_site(),
640 "Missing `method` attribute (e.g. `#[endpoint(method = \"gotham_restful::gotham::hyper::Method::GET\")]`)"
641 )
642 })?;
643 let uri = ty.uri().ok_or_else(|| {
644 Error::new(
645 Span::call_site(),
646 "Missing `uri` attribute (e.g. `#[endpoint(uri = \"custom_endpoint\")]`)"
647 )
648 })?;
649 let has_placeholders = ty.has_placeholders();
650 let placeholder_ty = ty.placeholders_ty(next_arg_ty(!has_placeholders.value)?);
651 let placeholder_typedef =
652 quote_spanned!(placeholder_ty.span() => type Placeholders = #placeholder_ty;);
653 let needs_params = ty.needs_params();
654 let params_ty = ty.params_ty(next_arg_ty(!needs_params.value)?);
655 let params_typedef = quote_spanned!(params_ty.span() => type Params = #params_ty;);
656 let needs_body = ty.needs_body();
657 let body_ty = ty.body_ty(next_arg_ty(!needs_body.value)?);
658 let body_typedef = quote_spanned!(body_ty.span() => type Body = #body_ty;);
659
660 if arg_ty_idx < arg_tys.len() {
661 return Err(Error::new(
662 fun_ident.span(),
663 "Extra arguments for this endpoint handler"
664 ));
665 }
666
667 let mut handle_args: Vec<TokenStream> = Vec::new();
668 if has_placeholders.value {
669 if matches!(ty, EndpointType::Custom { .. }) {
670 handle_args.push(quote!(placeholders));
671 } else {
672 handle_args.push(quote!(placeholders.id));
673 }
674 }
675 if needs_params.value {
676 handle_args.push(quote!(params));
677 }
678 if needs_body.value {
679 handle_args.push(quote!(body.unwrap()));
680 }
681 let handle_args = args.iter().map(|arg| match arg.ty {
682 HandlerArgType::StateRef | HandlerArgType::StateMutRef => quote!(state),
683 HandlerArgType::MethodArg(_) => handle_args.remove(0),
684 HandlerArgType::DatabaseConnection(_) => quote!(&mut conn),
685 HandlerArgType::AuthStatus(_) => quote!(auth),
686 HandlerArgType::AuthStatusRef(_) => quote!(&auth)
687 });
688
689 let expand_handle_content = || {
690 let mut state_block = quote!();
691 if let Some(arg) = args.iter().find(|arg| arg.ty.is_auth_status()) {
692 let auth_ty = arg.ty.quote_ty();
693 let auth_borrow = quote! {
694 ::gotham_restful::private::clone_from_state::<#auth_ty>(state)
695 };
696 state_block = quote! {
697 #state_block
698 let auth: #auth_ty = #auth_borrow;
699 }
700 }
701
702 let mut handle_content = quote!(#fun_ident(#(#handle_args),*));
703 if fun_is_async {
704 if let Some(arg) = args
705 .iter()
706 .find(|arg| matches!(arg.ty, HandlerArgType::StateRef))
707 {
708 return Err(Error::new(
709 arg.span(),
710 "Endpoint handler functions that are async must not take `&State` as an argument, consider taking `&mut State`"
711 ));
712 }
713 handle_content = quote!(#handle_content.await);
714 }
715 if is_no_content {
716 handle_content = quote!(#handle_content; <::gotham_restful::NoContent as ::std::default::Default>::default())
717 }
718
719 if let Some(arg) = args.iter().find(|arg| arg.ty.is_database_conn()) {
720 let conn_ty = arg.ty.quote_ty();
721 state_block = quote! {
722 #state_block
723 let repo = <::gotham_restful::private::Repo<#conn_ty>>::borrow_from(state).clone();
724 };
725 handle_content = quote! {
726 repo.run::<_, _, ()>(move |mut conn| {
727 Ok({ #handle_content })
728 }).await.unwrap()
729 };
730 }
731
732 Ok(quote! {
733 use ::gotham_restful::private::FutureExt as _;
734 use ::gotham_restful::gotham::state::FromState as _;
735 #state_block
736 async move {
737 #handle_content
738 }.map(<#final_return_ty>::from).boxed()
739 })
740 };
741 let handle_content = match expand_handle_content() {
742 Ok(content) => content,
743 Err(err) => err.to_compile_error()
744 };
745
746 let tr8 = if cfg!(feature = "openapi") {
747 quote!(::gotham_restful::EndpointWithSchema)
748 } else {
749 quote!(::gotham_restful::Endpoint)
750 };
751 let operation_verb = expand_operation_verb(ty.operation_verb());
752 let operation_id = expand_operation_id(fun_ident, operation_id);
753 let wants_auth = expand_wants_auth(wants_auth, args.iter().any(|arg| arg.ty.is_auth_status()));
754 let code = quote! {
755 #[doc(hidden)]
756 #[allow(non_camel_case_types)]
758 #fun_vis struct #ident;
759
760 const _: () = {
761 #output_struct
762
763 impl #tr8 for #ident {
764 fn http_method() -> ::gotham_restful::gotham::hyper::Method {
765 #http_method
766 }
767
768 fn uri() -> ::std::borrow::Cow<'static, ::core::primitive::str> {
769 { #uri }.into()
770 }
771
772 #operation_verb
773
774 #output_typedef
775
776 fn has_placeholders() -> ::core::primitive::bool {
777 #has_placeholders
778 }
779 #placeholder_typedef
780
781 fn needs_params() -> ::core::primitive::bool {
782 #needs_params
783 }
784 #params_typedef
785
786 fn needs_body() -> ::core::primitive::bool {
787 #needs_body
788 }
789 #body_typedef
790
791 fn handle<'a>(
792 state: &'a mut ::gotham_restful::gotham::state::State,
793 placeholders: Self::Placeholders,
794 params: Self::Params,
795 body: ::core::option::Option<Self::Body>
796 ) -> ::gotham_restful::private::BoxFuture<'a, Self::Output> {
797 #handle_content
798 }
799
800 #operation_id
801 #description
802 #wants_auth
803 }
804 };
805 };
806 if debug {
807 eprintln!("{code}");
808 }
809 Ok(code)
810}
811
812pub(crate) fn expand_endpoint(
813 ty: EndpointType,
814 attrs: AttributeArgs,
815 fun: ItemFn
816) -> Result<TokenStream> {
817 let endpoint_type = match expand_endpoint_type(ty, attrs, &fun) {
818 Ok(code) => code,
819 Err(err) => err.to_compile_error()
820 };
821 Ok(quote! {
822 #fun
823 #endpoint_type
824 })
825}