scuffle_bootstrap_derive/
lib.rs

1//! A proc-macro to generate the main function for the application.
2//!
3//! For more information checkout the [`scuffle-bootstrap`][scuffle_bootstrap] crate.
4//!
5//! ## License
6//!
7//! This project is licensed under the MIT or Apache-2.0 license.
8//! You can choose between one of them if you use this work.
9//!
10//! `SPDX-License-Identifier: MIT OR Apache-2.0`
11//!
12//! [scuffle_bootstrap]: https://docs.rs/scuffle-bootstrap
13#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
14#![cfg_attr(docsrs, feature(doc_auto_cfg))]
15#![deny(missing_docs)]
16#![deny(unsafe_code)]
17#![deny(unreachable_pub)]
18#![deny(clippy::mod_module_files)]
19
20use proc_macro::TokenStream;
21
22mod main_impl;
23
24/// Can be used to generate the main function for the application.
25#[proc_macro]
26pub fn main(input: TokenStream) -> TokenStream {
27    handle_error(main_impl::impl_main(input.into()))
28}
29
30fn handle_error(input: Result<proc_macro2::TokenStream, syn::Error>) -> TokenStream {
31    match input {
32        Ok(value) => value.into(),
33        Err(err) => err.to_compile_error().into(),
34    }
35}
36
37#[cfg(test)]
38#[cfg_attr(all(test, coverage_nightly), coverage(off))]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_main() {
44        let input = quote::quote! {
45            MyGlobal {
46                MyService,
47            }
48        };
49
50        let output = match main_impl::impl_main(input) {
51            Ok(value) => value,
52            Err(err) => err.to_compile_error(),
53        };
54
55        let syntax_tree = prettyplease::unparse(&syn::parse_file(&output.to_string()).unwrap());
56
57        insta::assert_snapshot!(syntax_tree, @r#"
58        #[automatically_derived]
59        fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> {
60            #[doc(hidden)]
61            const fn impl_global<G: ::scuffle_bootstrap::global::Global>() {}
62            const _: () = impl_global::<MyGlobal>();
63            ::scuffle_bootstrap::prelude::anyhow::Context::context(
64                <MyGlobal as ::scuffle_bootstrap::global::Global>::pre_init(),
65                "pre_init",
66            )?;
67            let runtime = <MyGlobal as ::scuffle_bootstrap::global::Global>::tokio_runtime();
68            let config = ::scuffle_bootstrap::prelude::anyhow::Context::context(
69                runtime
70                    .block_on(
71                        <<MyGlobal as ::scuffle_bootstrap::global::Global>::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(),
72                    ),
73                "config parse",
74            )?;
75            let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global();
76            let mut shared_global = ::core::option::Option::None;
77            let mut services_vec = ::std::vec::Vec::<
78                ::scuffle_bootstrap::service::NamedFuture<
79                    ::scuffle_bootstrap::prelude::tokio::task::JoinHandle<anyhow::Result<()>>,
80                >,
81            >::new();
82            let result = runtime
83                .block_on(async {
84                    let global = <MyGlobal as ::scuffle_bootstrap::global::Global>::init(config)
85                        .await?;
86                    shared_global = ::core::option::Option::Some(global.clone());
87                    {
88                        #[doc(hidden)]
89                        async fn spawn_service(
90                            svc: impl ::scuffle_bootstrap::service::Service<MyGlobal>,
91                            global: &::std::sync::Arc<MyGlobal>,
92                            ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler,
93                            name: &'static str,
94                        ) -> anyhow::Result<
95                            Option<
96                                ::scuffle_bootstrap::service::NamedFuture<
97                                    ::scuffle_bootstrap::prelude::tokio::task::JoinHandle<
98                                        anyhow::Result<()>,
99                                    >,
100                                >,
101                            >,
102                        > {
103                            let name = ::scuffle_bootstrap::service::Service::<
104                                MyGlobal,
105                            >::name(&svc)
106                                .unwrap_or_else(|| name);
107                            if ::scuffle_bootstrap::prelude::anyhow::Context::context(
108                                ::scuffle_bootstrap::service::Service::<
109                                    MyGlobal,
110                                >::enabled(&svc, &global)
111                                    .await,
112                                name,
113                            )? {
114                                Ok(
115                                    Some(
116                                        ::scuffle_bootstrap::service::NamedFuture::new(
117                                            name,
118                                            ::scuffle_bootstrap::prelude::tokio::spawn(
119                                                ::scuffle_bootstrap::service::Service::<
120                                                    MyGlobal,
121                                                >::run(svc, global.clone(), ctx_handle.context()),
122                                            ),
123                                        ),
124                                    ),
125                                )
126                            } else {
127                                Ok(None)
128                            }
129                        }
130                        let res = spawn_service(MyService, &global, &ctx_handle, "MyService")
131                            .await;
132                        if let Some(spawned) = res? {
133                            services_vec.push(spawned);
134                        }
135                    }
136                    <MyGlobal as ::scuffle_bootstrap::global::Global>::on_services_start(&global)
137                        .await?;
138                    let mut remaining = services_vec;
139                    while !remaining.is_empty() {
140                        let ((name, result), _, new_remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all(
141                                remaining,
142                            )
143                            .await;
144                        let result = ::scuffle_bootstrap::prelude::anyhow::Context::context(
145                            ::scuffle_bootstrap::prelude::anyhow::Context::context(
146                                result,
147                                name,
148                            )?,
149                            name,
150                        );
151                        <MyGlobal as ::scuffle_bootstrap::global::Global>::on_service_exit(
152                                &global,
153                                name,
154                                result,
155                            )
156                            .await?;
157                        remaining = new_remaining;
158                    }
159                    ::scuffle_bootstrap::prelude::anyhow::Ok(())
160                });
161            let ::core::option::Option::Some(global) = shared_global else {
162                return result;
163            };
164            runtime
165                .block_on(
166                    <MyGlobal as ::scuffle_bootstrap::global::Global>::on_exit(&global, result),
167                )
168        }
169        "#);
170    }
171}