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