scuffle_bootstrap_derive/
lib.rs

1#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
2
3use proc_macro::TokenStream;
4
5mod main_impl;
6
7#[proc_macro]
8pub fn main(input: TokenStream) -> TokenStream {
9    handle_error(main_impl::impl_main(input.into()))
10}
11
12fn handle_error(input: Result<proc_macro2::TokenStream, syn::Error>) -> TokenStream {
13    match input {
14        Ok(value) => value.into(),
15        Err(err) => err.to_compile_error().into(),
16    }
17}
18
19#[cfg(test)]
20#[cfg_attr(all(test, coverage_nightly), coverage(off))]
21mod tests {
22    use super::*;
23
24    #[test]
25    fn test_main() {
26        let input = quote::quote! {
27            MyGlobal {
28                MyService,
29            }
30        };
31
32        let output = match main_impl::impl_main(input) {
33            Ok(value) => value,
34            Err(err) => err.to_compile_error(),
35        };
36
37        let syntax_tree = prettyplease::unparse(&syn::parse_file(&output.to_string()).unwrap());
38
39        insta::assert_snapshot!(syntax_tree, @r##"
40        #[automatically_derived]
41        fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> {
42            #[doc(hidden)]
43            pub const fn impl_global<G: ::scuffle_bootstrap::global::Global>() {}
44            const _: () = impl_global::<MyGlobal>();
45            ::scuffle_bootstrap::prelude::anyhow::Context::context(
46                <MyGlobal as ::scuffle_bootstrap::global::Global>::pre_init(),
47                "pre_init",
48            )?;
49            let runtime = <MyGlobal as ::scuffle_bootstrap::global::Global>::tokio_runtime();
50            let config = ::scuffle_bootstrap::prelude::anyhow::Context::context(
51                runtime
52                    .block_on(
53                        <<MyGlobal as ::scuffle_bootstrap::global::Global>::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(),
54                    ),
55                "config parse",
56            )?;
57            let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global();
58            let mut shared_global = ::core::option::Option::None;
59            let mut services_vec = ::std::vec::Vec::<
60                ::scuffle_bootstrap::service::NamedFuture<
61                    ::scuffle_bootstrap::prelude::tokio::task::JoinHandle<anyhow::Result<()>>,
62                >,
63            >::new();
64            let result = runtime
65                .block_on(async {
66                    let global = <MyGlobal as ::scuffle_bootstrap::global::Global>::init(config)
67                        .await?;
68                    shared_global = ::core::option::Option::Some(global.clone());
69                    {
70                        #[doc(hidden)]
71                        pub async fn spawn_service(
72                            svc: impl ::scuffle_bootstrap::service::Service<MyGlobal>,
73                            global: &::std::sync::Arc<MyGlobal>,
74                            ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler,
75                            name: &'static str,
76                        ) -> anyhow::Result<
77                            Option<
78                                ::scuffle_bootstrap::service::NamedFuture<
79                                    ::scuffle_bootstrap::prelude::tokio::task::JoinHandle<
80                                        anyhow::Result<()>,
81                                    >,
82                                >,
83                            >,
84                        > {
85                            let name = ::scuffle_bootstrap::service::Service::<
86                                MyGlobal,
87                            >::name(&svc)
88                                .unwrap_or_else(|| name);
89                            if ::scuffle_bootstrap::prelude::anyhow::Context::context(
90                                ::scuffle_bootstrap::service::Service::<
91                                    MyGlobal,
92                                >::enabled(&svc, &global)
93                                    .await,
94                                name,
95                            )? {
96                                Ok(
97                                    Some(
98                                        ::scuffle_bootstrap::service::NamedFuture::new(
99                                            name,
100                                            ::scuffle_bootstrap::prelude::tokio::spawn(
101                                                ::scuffle_bootstrap::service::Service::<
102                                                    MyGlobal,
103                                                >::run(svc, global.clone(), ctx_handle.context()),
104                                            ),
105                                        ),
106                                    ),
107                                )
108                            } else {
109                                Ok(None)
110                            }
111                        }
112                        let res = spawn_service(MyService, &global, &ctx_handle, "MyService")
113                            .await;
114                        if let Some(spawned) = res? {
115                            services_vec.push(spawned);
116                        }
117                    }
118                    <MyGlobal as ::scuffle_bootstrap::global::Global>::on_services_start(&global)
119                        .await?;
120                    macro_rules! handle_service_exit {
121                        ($remaining:ident) => {
122                            { let ((name, result), _, remaining) =
123                            ::scuffle_bootstrap::prelude::futures::future::select_all($remaining)
124                            . await; let result =
125                            ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result,
126                            name) ?, name); < MyGlobal as ::scuffle_bootstrap::global::Global >
127                            ::on_service_exit(& global, name, result). await ?; remaining }
128                        };
129                    }
130                    let mut remaining = handle_service_exit!(services_vec);
131                    while !remaining.is_empty() {
132                        remaining = handle_service_exit!(remaining);
133                    }
134                    ::scuffle_bootstrap::prelude::anyhow::Ok(())
135                });
136            let ::core::option::Option::Some(global) = shared_global else {
137                return result;
138            };
139            runtime
140                .block_on(
141                    <MyGlobal as ::scuffle_bootstrap::global::Global>::on_exit(&global, result),
142                )
143        }
144        "##);
145    }
146}