1use crate::{
2 export, sails_paths,
3 shared::{self, FnBuilder, InvocationExport},
4};
5use args::ProgramArgs;
6use proc_macro_error::abort;
7use proc_macro2::{Span, TokenStream as TokenStream2};
8use quote::quote;
9use std::{
10 collections::BTreeMap,
11 env,
12 ops::{Deref, DerefMut},
13};
14use syn::{
15 Generics, Ident, ImplItem, ImplItemFn, ItemImpl, Path, PathArguments, Receiver, ReturnType,
16 Type, TypePath, Visibility, WhereClause, parse_quote, spanned::Spanned,
17};
18
19mod args;
20#[cfg(feature = "ethexe")]
21mod ethexe;
22
23static mut PROGRAM_SPANS: BTreeMap<String, Span> = BTreeMap::new();
25
26pub fn gprogram(args: TokenStream2, program_impl_tokens: TokenStream2) -> TokenStream2 {
27 let program_impl = parse_gprogram_impl(program_impl_tokens);
28 ensure_single_gprogram(&program_impl);
29 let args = parse_args(args);
30 gen_gprogram_impl(program_impl, args)
31}
32
33#[doc(hidden)]
34pub fn __gprogram_internal(args: TokenStream2, program_impl_tokens: TokenStream2) -> TokenStream2 {
35 let program_impl = parse_gprogram_impl(program_impl_tokens);
36 let args = parse_args(args);
37 gen_gprogram_impl(program_impl, args)
38}
39
40fn parse_args(args: TokenStream2) -> ProgramArgs {
41 syn::parse2(args).unwrap_or_else(|err| {
42 abort!(
43 err.span(),
44 "failed to parse `program` attribute arguments: {}",
45 err
46 )
47 })
48}
49
50fn parse_gprogram_impl(program_impl_tokens: TokenStream2) -> ItemImpl {
51 syn::parse2(program_impl_tokens).unwrap_or_else(|err| {
52 abort!(
53 err.span(),
54 "`program` attribute can be applied to impls only: {}",
55 err
56 )
57 })
58}
59
60#[allow(static_mut_refs)]
61fn ensure_single_gprogram(program_impl: &ItemImpl) {
62 let crate_name = env::var("CARGO_CRATE_NAME").unwrap_or("crate".to_string());
63 if unsafe { PROGRAM_SPANS.get(&crate_name) }.is_some() {
64 abort!(
65 program_impl,
66 "multiple `program` attributes are not allowed"
67 )
68 }
69 unsafe { PROGRAM_SPANS.insert(crate_name, program_impl.span()) };
70}
71
72struct ProgramBuilder {
73 program_impl: ItemImpl,
74 program_args: ProgramArgs,
75 type_constraints: Option<WhereClause>,
76}
77
78impl ProgramBuilder {
79 fn new(program_impl: ItemImpl, program_args: ProgramArgs) -> Self {
80 let mut program_impl = program_impl;
81 let type_constraints = program_impl.generics.where_clause.take();
82 ensure_default_program_ctor(&mut program_impl);
83
84 Self {
85 program_impl,
86 program_args,
87 type_constraints,
88 }
89 }
90
91 fn sails_path(&self) -> &Path {
92 self.program_args.sails_path()
93 }
94
95 fn impl_type(&self) -> (&TypePath, &PathArguments, &Ident) {
96 shared::impl_type_refs(self.program_impl.self_ty.as_ref())
97 }
98
99 fn impl_constraints(&self) -> (&Generics, Option<&WhereClause>) {
100 (&self.program_impl.generics, self.type_constraints.as_ref())
101 }
102
103 fn program_ctors(&self) -> Vec<FnBuilder<'_>> {
104 discover_program_ctors(&self.program_impl, self.sails_path())
105 }
106
107 fn handle_reply_fn(&mut self) -> Option<&mut ImplItemFn> {
108 let mut fn_iter = self.program_impl.items.iter_mut().filter_map(|item| {
109 if let ImplItem::Fn(fn_item) = item
110 && has_handle_reply_attr(fn_item) {
111 fn_item
112 .attrs
113 .retain(|attr| !attr.path().is_ident("handle_reply"));
114 if handle_reply_predicate(fn_item) {
115 return Some(fn_item);
116 } else {
117 abort!(
118 fn_item,
119 "`handle_reply` function must have a single `&self` argument and no return type"
120 );
121 }
122 }
123 None
124 });
125 let handle_reply_fn = fn_iter.next();
126 if let Some(duplicate) = fn_iter.next() {
127 abort!(duplicate, "only one `handle_reply` function is allowed");
128 }
129 handle_reply_fn
130 }
131
132 #[cfg(feature = "ethexe")]
133 fn service_ctors(&self) -> Vec<FnBuilder<'_>> {
134 shared::discover_invocation_targets(self, service_ctor_predicate, self.sails_path())
135 }
136}
137
138impl ProgramBuilder {
139 fn wire_up_service_exposure(
140 &mut self,
141 program_ident: &Ident,
142 ) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) {
143 let mut services_route = Vec::new();
144 let mut services_meta = Vec::new();
145 let mut meta_asyncness = Vec::new();
146 let mut invocation_dispatches = Vec::new();
147 let mut routes = BTreeMap::new();
148 #[allow(unused_mut)]
150 let mut solidity_dispatchers: Vec<TokenStream2> = Vec::new();
151
152 let has_async_ctor = self
153 .program_ctors()
154 .iter()
155 .any(|fn_builder| fn_builder.is_async());
156
157 if has_async_ctor {
158 meta_asyncness.push(quote!(true));
159 }
160
161 let item_impl = self
162 .program_impl
163 .items
164 .iter()
165 .enumerate()
166 .filter_map(|(idx, impl_item)| {
167 if let ImplItem::Fn(fn_item) = impl_item
168 && service_ctor_predicate(fn_item)
169 {
170 let invocation_export = shared::invocation_export_or_default(fn_item);
171
172 #[cfg(feature = "ethexe")]
173 {
174 use convert_case::{Case, Casing};
175 let camel_case_route = invocation_export.route.to_case(Case::Camel);
176 shared::validation::validate_identifier(
177 &camel_case_route,
178 fn_item.sig.ident.span(),
179 "Exposed Service",
180 );
181 }
182
183 if let Some(duplicate) = routes.insert(
184 invocation_export.route.clone(),
185 fn_item.sig.ident.to_string(),
186 ) {
187 abort!(
188 invocation_export.span,
189 "`export` attribute conflicts with one already assigned to '{}'",
190 duplicate
191 );
192 }
193 return Some((idx, fn_item, invocation_export));
194 }
195 None
196 })
197 .map(|(idx, fn_item, invocation_export)| {
198 let InvocationExport {
199 route,
200 unwrap_result,
201 #[cfg(feature = "ethexe")]
202 payable,
203 ..
204 } = invocation_export;
205
206 let fn_builder =
207 FnBuilder::new(route, true, fn_item, unwrap_result, self.sails_path());
208
209 #[cfg(feature = "ethexe")]
210 let fn_builder = fn_builder.payable(payable);
211
212 let original_service_ctor_fn = fn_builder.original_service_ctor_fn();
213 let wrapping_service_ctor_fn =
214 fn_builder.wrapping_service_ctor_fn(&original_service_ctor_fn.sig.ident);
215
216 services_route.push(fn_builder.service_const_route());
217 services_meta.push(fn_builder.service_meta());
218
219 if !has_async_ctor {
220 meta_asyncness.push(fn_builder.service_meta_asyncness());
223 }
224 invocation_dispatches.push(fn_builder.service_invocation());
225 #[cfg(feature = "ethexe")]
226 solidity_dispatchers.push(fn_builder.sol_service_invocation());
227
228 (idx, original_service_ctor_fn, wrapping_service_ctor_fn)
229 })
230 .collect::<Vec<_>>();
231
232 if meta_asyncness.is_empty() {
233 meta_asyncness.push(quote!(false));
235 }
236
237 for (idx, original_service_ctor_fn, wrapping_service_ctor_fn, ..) in item_impl {
239 self.program_impl.items[idx] = ImplItem::Fn(original_service_ctor_fn);
240 self.program_impl
241 .items
242 .push(ImplItem::Fn(wrapping_service_ctor_fn));
243 }
244
245 let handle_reply_fn = self.handle_reply_fn().map(|item_fn| {
246 let handle_reply_fn_ident = &item_fn.sig.ident;
247 quote! {
248 let program_ref = unsafe { #program_ident.as_mut() }.expect("Program not initialized");
249 program_ref.#handle_reply_fn_ident();
250 }
251 })
252 .unwrap_or_default();
253
254 let handle_signal_fn = self
255 .program_args
256 .handle_signal()
257 .map(|handle_signal_path| quote!( #handle_signal_path ();))
258 .unwrap_or_default();
259
260 let sails_path = self.sails_path();
261 let (program_type_path, _program_type_args, _) = self.impl_type();
262 let (generics, program_type_constraints) = self.impl_constraints();
263
264 let program_meta_impl = quote! {
265 #(#services_route)*
266
267 impl #generics #sails_path::meta::ProgramMeta for #program_type_path #program_type_constraints {
268 type ConstructorsMeta = meta_in_program::ConstructorsMeta;
269
270 const SERVICES: &'static [(&'static str, #sails_path::meta::AnyServiceMetaFn)] = &[
271 #(#services_meta),*
272 ];
273 const ASYNC: bool = #( #meta_asyncness )||*;
274 }
275 };
276
277 invocation_dispatches.push(quote! {
278 { gstd::unknown_input_panic("Unexpected service", &input) }
279 });
280
281 let solidity_main = self.sol_main(solidity_dispatchers.as_slice());
282
283 let payable = self.program_args.payable().then(|| {
284 quote! {
285 if gstd::msg::value() > 0 && gstd::msg::size() == 0 {
286 return;
287 }
288 }
289 });
290
291 let main_fn = quote!(
292 #[unsafe(no_mangle)]
293 extern "C" fn handle() {
294 #payable
295
296 let mut input = gstd::msg::load_bytes().expect("Failed to read input");
297 let program_ref = unsafe { #program_ident.as_mut() }.expect("Program not initialized");
298
299 #solidity_main
300
301 #(#invocation_dispatches)else*;
302 }
303
304 );
305
306 let handle_reply_fn = quote! {
307 #[unsafe(no_mangle)]
308 extern "C" fn handle_reply() {
309 use #sails_path::meta::ProgramMeta;
310
311 if #program_type_path::ASYNC {
312 gstd::handle_reply_with_hook();
313 }
314
315 #handle_reply_fn
316 }
317 };
318
319 #[cfg(not(feature = "ethexe"))]
320 let handle_signal_fn = quote! {
321 #[unsafe(no_mangle)]
322 extern "C" fn handle_signal() {
323 use #sails_path::meta::ProgramMeta;
324
325 if #program_type_path::ASYNC {
326 gstd::handle_signal();
327 }
328
329 #handle_signal_fn
330 }
331 };
332
333 (
334 program_meta_impl,
335 main_fn,
336 handle_reply_fn,
337 handle_signal_fn,
338 )
339 }
340
341 fn generate_init(&self, program_ident: &Ident) -> (TokenStream2, TokenStream2) {
342 let sails_path = self.sails_path();
343 let scale_codec_path = sails_paths::scale_codec_path(sails_path);
344 let scale_info_path = sails_paths::scale_info_path(sails_path);
345
346 let (program_type_path, ..) = self.impl_type();
347 let input_ident = Ident::new("input", Span::call_site());
348
349 let program_ctors = self.program_ctors();
350
351 let mut ctor_dispatches = Vec::with_capacity(program_ctors.len() + 1);
352 let mut ctor_params_structs = Vec::with_capacity(program_ctors.len());
353 let mut ctor_meta_variants = Vec::with_capacity(program_ctors.len());
354
355 for fn_builder in program_ctors {
356 ctor_dispatches.push(fn_builder.ctor_branch_impl(
357 program_type_path,
358 &input_ident,
359 program_ident,
360 ));
361 ctor_params_structs
362 .push(fn_builder.ctor_params_struct(&scale_codec_path, &scale_info_path));
363 ctor_meta_variants.push(fn_builder.ctor_meta_variant());
364 }
365
366 ctor_dispatches.push(quote! {
367 { gstd::unknown_input_panic("Unexpected ctor", input) }
368 });
369
370 let solidity_init = self.sol_init(&input_ident);
371
372 let init_fn = quote! {
373 #[unsafe(no_mangle)]
374 extern "C" fn init() {
375 use gstd::InvocationIo;
376
377 let mut #input_ident: &[u8] = &gstd::msg::load_bytes().expect("Failed to read input");
378
379 #solidity_init
380
381 #(#ctor_dispatches)else*;
382 }
383 };
384
385 let meta_in_program = quote! {
386 mod meta_in_program {
387 use super::*;
388 use #sails_path::gstd::InvocationIo;
389
390 #( #ctor_params_structs )*
391
392 #[derive(#sails_path ::TypeInfo)]
393 #[scale_info(crate = #scale_info_path)]
394 pub enum ConstructorsMeta {
395 #( #ctor_meta_variants ),*
396 }
397 }
398 };
399 (meta_in_program, init_fn)
400 }
401}
402
403#[cfg(not(feature = "ethexe"))]
405impl ProgramBuilder {
406 fn program_signature_impl(&self) -> TokenStream2 {
407 quote!()
408 }
409
410 fn match_ctor_impl(&self, _program_ident: &Ident) -> TokenStream2 {
411 quote!()
412 }
413
414 fn program_const(&self) -> TokenStream2 {
415 quote!()
416 }
417
418 fn sol_init(&self, _input_ident: &Ident) -> TokenStream2 {
419 quote!()
420 }
421
422 fn sol_main(&self, _solidity_dispatchers: &[TokenStream2]) -> TokenStream2 {
423 quote!()
424 }
425}
426
427impl Deref for ProgramBuilder {
428 type Target = ItemImpl;
429
430 fn deref(&self) -> &Self::Target {
431 &self.program_impl
432 }
433}
434
435impl DerefMut for ProgramBuilder {
436 fn deref_mut(&mut self) -> &mut Self::Target {
437 &mut self.program_impl
438 }
439}
440
441fn gen_gprogram_impl(program_impl: ItemImpl, program_args: ProgramArgs) -> TokenStream2 {
442 let mut program_builder = ProgramBuilder::new(program_impl, program_args);
443
444 let sails_path = program_builder.sails_path().clone();
445
446 let program_ident = Ident::new("PROGRAM", Span::call_site());
447
448 let program_signature_impl = program_builder.program_signature_impl();
450 let match_ctor_impl = program_builder.match_ctor_impl(&program_ident);
451 let program_const = program_builder.program_const();
452
453 let (program_meta_impl, main_fn, handle_reply_fn, handle_signal_fn) =
454 program_builder.wire_up_service_exposure(&program_ident);
455 let (meta_in_program, init_fn) = program_builder.generate_init(&program_ident);
456
457 let (program_type_path, ..) = program_builder.impl_type();
458
459 let program_impl = program_builder.deref();
460
461 quote!(
462 #program_impl
463
464 #program_meta_impl
465
466 #meta_in_program
467
468 #program_signature_impl
469
470 #program_const
471
472 #[cfg(target_arch = "wasm32")]
473 pub mod wasm {
474 use super::*;
475 use #sails_path::{gstd, hex, prelude::*};
476
477 static mut #program_ident: Option<#program_type_path> = None;
478
479 #init_fn
480
481 #match_ctor_impl
482
483 #main_fn
484
485 #handle_reply_fn
486
487 #handle_signal_fn
488 }
489 )
490}
491
492fn ensure_default_program_ctor(program_impl: &mut ItemImpl) {
493 let sails_path = &sails_paths::sails_path_or_default(None);
494 if discover_program_ctors(program_impl, sails_path).is_empty() {
495 program_impl.items.push(ImplItem::Fn(parse_quote!(
496 pub fn create() -> Self {
497 Default::default()
498 }
499 )));
500 }
501}
502
503fn discover_program_ctors<'a>(
504 program_impl: &'a ItemImpl,
505 sails_path: &'a Path,
506) -> Vec<FnBuilder<'a>> {
507 let self_type_path: TypePath = parse_quote!(Self);
508 let (program_type_path, _, _) = shared::impl_type_refs(program_impl.self_ty.as_ref());
509 let ctors = shared::discover_invocation_targets(
510 program_impl,
511 |fn_item| program_ctor_predicate(fn_item, &self_type_path, program_type_path),
512 sails_path,
513 );
514
515 #[cfg(feature = "ethexe")]
516 {
517 for ctor in &ctors {
518 shared::validation::validate_identifier(
519 &ctor.route_camel_case(),
520 ctor.ident.span(),
521 "Program constructor",
522 );
523 }
524 }
525
526 ctors
527}
528
529fn program_ctor_predicate(
530 fn_item: &ImplItemFn,
531 self_type_path: &TypePath,
532 program_type_path: &TypePath,
533) -> bool {
534 if matches!(fn_item.vis, Visibility::Public(_))
535 && fn_item.sig.receiver().is_none()
536 && let ReturnType::Type(_, output_type) = &fn_item.sig.output
537 && let Type::Path(output_type_path) = output_type.as_ref()
538 {
539 if output_type_path == self_type_path || output_type_path == program_type_path {
540 return true;
541 }
542 if let Some(Type::Path(output_type_path)) = shared::extract_result_type(output_type_path)
543 && (output_type_path == self_type_path || output_type_path == program_type_path)
544 {
545 return true;
546 }
547 }
548 false
549}
550
551fn service_ctor_predicate(fn_item: &ImplItemFn) -> bool {
552 matches!(fn_item.vis, Visibility::Public(_))
553 && matches!(
554 fn_item.sig.receiver(),
555 Some(Receiver {
556 reference: Some(_),
557 ..
558 })
559 )
560 && fn_item.sig.inputs.len() == 1
561 && !matches!(fn_item.sig.output, ReturnType::Default)
562}
563
564fn has_handle_reply_attr(fn_item: &ImplItemFn) -> bool {
565 fn_item
566 .attrs
567 .iter()
568 .any(|attr| attr.path().is_ident("handle_reply"))
569}
570
571fn handle_reply_predicate(fn_item: &ImplItemFn) -> bool {
572 matches!(fn_item.vis, Visibility::Inherited)
573 && matches!(
574 fn_item.sig.receiver(),
575 Some(Receiver {
576 mutability: None,
577 reference: Some(_),
578 ..
579 })
580 )
581 && fn_item.sig.inputs.len() == 1
582 && matches!(fn_item.sig.output, ReturnType::Default)
583}
584
585impl FnBuilder<'_> {
586 fn route_ident(&self) -> Ident {
587 Ident::new(
588 &format!("__ROUTE_{}", self.route.to_ascii_uppercase()),
589 Span::call_site(),
590 )
591 }
592
593 fn service_meta(&self) -> TokenStream2 {
594 let sails_path = self.sails_path;
595 let route = &self.route;
596 let service_type = &self.result_type;
597 quote!(
598 ( #route , #sails_path::meta::AnyServiceMeta::new::< #service_type >)
599 )
600 }
601
602 fn service_meta_asyncness(&self) -> TokenStream2 {
603 let sails_path = self.sails_path;
604 let service_type = &self.result_type;
605 quote!(< #service_type as #sails_path::meta::ServiceMeta>::ASYNC )
606 }
607
608 fn service_const_route(&self) -> TokenStream2 {
609 let route_ident = &self.route_ident();
610 let ctor_route_bytes = self.encoded_route.as_slice();
611 let ctor_route_len = ctor_route_bytes.len();
612 quote!(
613 const #route_ident: [u8; #ctor_route_len] = [ #(#ctor_route_bytes),* ];
614 )
615 }
616
617 fn service_invocation(&self) -> TokenStream2 {
618 let route_ident = &self.route_ident();
619 let service_ctor_ident = self.ident;
620
621 quote! {
622 if input.starts_with(& #route_ident) {
623 let mut service = program_ref.#service_ctor_ident();
624 let is_async = service
625 .check_asyncness(&input[#route_ident .len()..])
626 .unwrap_or_else(|| {
627 gstd::unknown_input_panic("Unknown call", &input[#route_ident .len()..])
628 });
629 if is_async {
630 gstd::message_loop(async move {
631 service
632 .try_handle_async(&input[#route_ident .len()..], |encoded_result, value| {
633 gstd::msg::reply_bytes(encoded_result, value)
634 .expect("Failed to send output");
635 })
636 .await
637 .unwrap_or_else(|| {
638 gstd::unknown_input_panic("Unknown request", &input)
639 });
640 });
641 } else {
642 service
643 .try_handle(&input[#route_ident .len()..], |encoded_result, value| {
644 gstd::msg::reply_bytes(encoded_result, value)
645 .expect("Failed to send output");
646 })
647 .unwrap_or_else(|| gstd::unknown_input_panic("Unknown request", &input));
648 }
649 }
650 }
651 }
652
653 fn original_service_ctor_fn(&self) -> ImplItemFn {
654 let mut original_service_ctor_fn = self.impl_fn.clone();
655 let original_service_ctor_fn_ident = Ident::new(
656 &format!("__{}", original_service_ctor_fn.sig.ident),
657 original_service_ctor_fn.sig.ident.span(),
658 );
659 original_service_ctor_fn.attrs.clear();
660 original_service_ctor_fn.vis = Visibility::Inherited;
661 original_service_ctor_fn.sig.ident = original_service_ctor_fn_ident;
662 original_service_ctor_fn
663 }
664
665 fn wrapping_service_ctor_fn(&self, original_service_ctor_fn_ident: &Ident) -> ImplItemFn {
666 let sails_path = self.sails_path;
667 let service_type = &self.result_type;
668 let route_ident = &self.route_ident();
669 let unwrap_token = self.unwrap_result.then(|| quote!(.unwrap()));
670
671 let mut wrapping_service_ctor_fn = self.impl_fn.clone();
672 wrapping_service_ctor_fn
674 .attrs
675 .retain(|attr| export::parse_attr(attr).is_none());
676 wrapping_service_ctor_fn.sig.output = parse_quote!(
677 -> < #service_type as #sails_path::gstd::services::Service>::Exposure
678 );
679 wrapping_service_ctor_fn.block = parse_quote!({
680 let service = self. #original_service_ctor_fn_ident () #unwrap_token;
681 let exposure = < #service_type as #sails_path::gstd::services::Service>::expose(
682 service,
683 #route_ident .as_ref(),
684 );
685 exposure
686 });
687 wrapping_service_ctor_fn
688 }
689
690 fn ctor_branch_impl(
691 &self,
692 program_type_path: &TypePath,
693 input_ident: &Ident,
694 program_ident: &Ident,
695 ) -> TokenStream2 {
696 let handler_ident = self.ident;
697 let unwrap_token = self.unwrap_result.then(|| quote!(.unwrap()));
698 let handler_args = self
699 .params_idents()
700 .iter()
701 .map(|ident| quote!(request.#ident));
702 let params_struct_ident = &self.params_struct_ident;
703 let payable_check = {
704 #[cfg(feature = "ethexe")]
705 {
706 self.payable_check()
707 }
708 #[cfg(not(feature = "ethexe"))]
709 {
710 quote!()
711 }
712 };
713
714 let ctor_call_impl = if self.is_async() {
715 quote! {
716 gstd::message_loop(async move {
717 let program = #program_type_path :: #handler_ident (#(#handler_args),*) .await #unwrap_token ;
718
719 unsafe {
720 #program_ident = Some(program);
721 }
722 });
723 }
724 } else {
725 quote! {
726 let program = #program_type_path :: #handler_ident (#(#handler_args),*) #unwrap_token;
727 unsafe {
728 #program_ident = Some(program);
729 }
730 }
731 };
732
733 quote!(
734 if let Ok(request) = meta_in_program::#params_struct_ident::decode_params( #input_ident) {
735 #payable_check
736 #ctor_call_impl
737 }
738 )
739 }
740
741 fn ctor_params_struct(&self, scale_codec_path: &Path, scale_info_path: &Path) -> TokenStream2 {
742 let sails_path = self.sails_path;
743 let params_struct_ident = &self.params_struct_ident;
744 let params_struct_members = self.params().map(|(ident, ty)| quote!(#ident: #ty));
745 let ctor_route_bytes = self.encoded_route.as_slice();
746 let is_async = self.is_async();
747
748 quote! {
749 #[derive(#sails_path ::Decode, #sails_path ::TypeInfo)]
750 #[codec(crate = #scale_codec_path )]
751 #[scale_info(crate = #scale_info_path )]
752 pub struct #params_struct_ident {
753 #(pub(super) #params_struct_members,)*
754 }
755
756 impl InvocationIo for #params_struct_ident {
757 const ROUTE: &'static [u8] = &[ #(#ctor_route_bytes),* ];
758 type Params = Self;
759 const ASYNC: bool = #is_async;
760 }
761 }
762 }
763
764 fn ctor_meta_variant(&self) -> TokenStream2 {
765 let ctor_route = Ident::new(self.route.as_str(), Span::call_site());
766 let ctor_docs_attrs = self
767 .impl_fn
768 .attrs
769 .iter()
770 .filter(|attr| attr.path().is_ident("doc"));
771 let params_struct_ident = &self.params_struct_ident;
772
773 quote! {
774 #( #ctor_docs_attrs )*
775 #ctor_route(#params_struct_ident)
776 }
777 }
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783 use quote::quote;
784
785 #[test]
786 fn gprogram_discovers_public_associated_functions_returning_self_or_the_type_as_ctors() {
787 let program_impl = syn::parse2(quote!(
788 impl MyProgram {
789 fn non_public_associated_func_returning_self() -> Self {}
790 fn non_public_associated_func_returning_type() -> MyProgram {}
791 fn non_public_associated_func_returning_smth() -> u32 {}
792 pub fn public_associated_func_returning_self() -> Self {}
793 pub fn public_associated_func_returning_type() -> MyProgram {}
794 pub fn public_associated_func_returning_smth() -> u32 {}
795 fn non_public_method_returning_self(&self) -> Self {}
796 fn non_public_method_returning_type(&self) -> MyProgram {}
797 fn non_public_method_returning_smth(&self) -> u32 {}
798 pub fn public_method_returning_self(&self) -> Self {}
799 pub fn public_method_returning_type(&self) -> MyProgram {}
800 pub fn public_method_returning_smth(&self) -> u32 {}
801 }
802 ))
803 .unwrap();
804
805 let sails_path = &sails_paths::sails_path_or_default(None);
806 let discovered_ctors = discover_program_ctors(&program_impl, sails_path)
807 .iter()
808 .map(|fn_builder| fn_builder.ident.to_string())
809 .collect::<Vec<_>>();
810
811 assert_eq!(discovered_ctors.len(), 2);
812 assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_self")));
813 assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_type")));
814 }
815
816 #[test]
817 fn gprogram_discovers_public_methods_with_self_ref_only_and_some_return_as_service_funcs() {
818 let program_impl = syn::parse2(quote!(
819 impl MyProgram {
820 fn non_public_associated_func_returning_smth() -> u32 {}
821 fn non_public_associated_func_returning_unit() {}
822 pub fn public_associated_func_returning_smth() -> MyProgram {}
823 pub fn public_associated_func_returning_unit() {}
824 fn non_public_method_returning_smth(&self) -> u32 {}
825 fn non_public_method_returning_unit(&self) {}
826 pub fn public_method_returning_smth(&self) -> u32 {}
827 pub fn public_method_returning_smth_with_other_params(&self, p1: u32) -> u32 {}
828 pub fn public_methos_returning_smth_and_consuming_self(self) -> u32 {}
829 }
830 ))
831 .unwrap();
832
833 let sails_path = &sails_paths::sails_path_or_default(None);
834 let discovered_services =
835 shared::discover_invocation_targets(&program_impl, service_ctor_predicate, sails_path)
836 .iter()
837 .map(|fn_builder| fn_builder.ident.to_string())
838 .collect::<Vec<_>>();
839
840 assert_eq!(discovered_services.len(), 1);
841 assert!(discovered_services.contains(&String::from("public_method_returning_smth")));
842 }
843}