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