Skip to main content

a3s_boot/app/
factory.rs

1use super::{
2    BootApplication, BootApplicationBuilder, BootApplicationContext, BootApplicationHandle,
3    BootMicroservice,
4};
5use crate::{MessageTransport, Module, Result};
6use std::sync::Arc;
7
8/// NestFactory-style entrypoint for building managed Boot applications.
9pub struct BootFactory;
10
11impl BootFactory {
12    pub fn create<M>(module: M) -> Result<BootApplicationHandle>
13    where
14        M: Module,
15    {
16        Self::create_with_builder(BootApplication::builder().import(module))
17    }
18
19    pub fn create_arc(module: Arc<dyn Module>) -> Result<BootApplicationHandle> {
20        Self::create_with_builder(BootApplication::builder().import_arc(module))
21    }
22
23    pub fn create_with_builder(builder: BootApplicationBuilder) -> Result<BootApplicationHandle> {
24        Ok(BootApplicationHandle::from_app(builder.build()?))
25    }
26
27    pub async fn create_async<M>(module: M) -> Result<BootApplicationHandle>
28    where
29        M: Module,
30    {
31        Self::create_with_builder_async(BootApplication::builder().import(module)).await
32    }
33
34    pub async fn create_arc_async(module: Arc<dyn Module>) -> Result<BootApplicationHandle> {
35        Self::create_with_builder_async(BootApplication::builder().import_arc(module)).await
36    }
37
38    pub async fn create_with_builder_async(
39        builder: BootApplicationBuilder,
40    ) -> Result<BootApplicationHandle> {
41        Ok(BootApplicationHandle::from_app(
42            builder.build_async().await?,
43        ))
44    }
45
46    pub fn create_application_context<M>(module: M) -> Result<BootApplicationContext>
47    where
48        M: Module,
49    {
50        Self::create_application_context_with_builder(BootApplication::builder().import(module))
51    }
52
53    pub fn create_application_context_arc(
54        module: Arc<dyn Module>,
55    ) -> Result<BootApplicationContext> {
56        Self::create_application_context_with_builder(BootApplication::builder().import_arc(module))
57    }
58
59    pub fn create_application_context_with_builder(
60        builder: BootApplicationBuilder,
61    ) -> Result<BootApplicationContext> {
62        Ok(BootApplicationContext {
63            handle: Self::create_with_builder(builder)?,
64        })
65    }
66
67    pub async fn create_application_context_async<M>(module: M) -> Result<BootApplicationContext>
68    where
69        M: Module,
70    {
71        Self::create_application_context_with_builder_async(
72            BootApplication::builder().import(module),
73        )
74        .await
75    }
76
77    pub async fn create_application_context_arc_async(
78        module: Arc<dyn Module>,
79    ) -> Result<BootApplicationContext> {
80        Self::create_application_context_with_builder_async(
81            BootApplication::builder().import_arc(module),
82        )
83        .await
84    }
85
86    pub async fn create_application_context_with_builder_async(
87        builder: BootApplicationBuilder,
88    ) -> Result<BootApplicationContext> {
89        Ok(BootApplicationContext {
90            handle: Self::create_with_builder_async(builder).await?,
91        })
92    }
93
94    pub fn create_microservice<M, T>(module: M, transport: T) -> Result<BootMicroservice<T>>
95    where
96        M: Module,
97        T: MessageTransport,
98    {
99        Self::create_microservice_with_builder(BootApplication::builder().import(module), transport)
100    }
101
102    pub fn create_microservice_with_builder<T>(
103        builder: BootApplicationBuilder,
104        transport: T,
105    ) -> Result<BootMicroservice<T>>
106    where
107        T: MessageTransport,
108    {
109        Ok(BootMicroservice::new(builder.build()?, transport))
110    }
111
112    pub async fn create_microservice_async<M, T>(
113        module: M,
114        transport: T,
115    ) -> Result<BootMicroservice<T>>
116    where
117        M: Module,
118        T: MessageTransport,
119    {
120        Self::create_microservice_with_builder_async(
121            BootApplication::builder().import(module),
122            transport,
123        )
124        .await
125    }
126
127    pub async fn create_microservice_with_builder_async<T>(
128        builder: BootApplicationBuilder,
129        transport: T,
130    ) -> Result<BootMicroservice<T>>
131    where
132        T: MessageTransport,
133    {
134        Ok(BootMicroservice::new(
135            builder.build_async().await?,
136            transport,
137        ))
138    }
139}
140
141#[cfg(all(test, feature = "shutdown-hooks"))]
142mod tests {
143    use super::*;
144    use crate::{
145        BootApplication, BootError, BoxFuture, HttpAdapter, MessageTransport, Module, ModuleRef,
146        ShutdownSignal,
147    };
148    use std::net::SocketAddr;
149    use std::sync::{Arc, Mutex};
150    use tokio::sync::oneshot;
151
152    struct ShutdownHookModule {
153        log: Arc<Mutex<Vec<String>>>,
154    }
155
156    impl Module for ShutdownHookModule {
157        fn name(&self) -> &'static str {
158            "shutdown-hook"
159        }
160
161        fn on_module_init(&self, _module_ref: &ModuleRef) -> crate::Result<()> {
162            self.log.lock().unwrap().push("init".to_string());
163            Ok(())
164        }
165
166        fn on_application_bootstrap(
167            &self,
168            _module_ref: ModuleRef,
169        ) -> BoxFuture<'static, crate::Result<()>> {
170            let log = Arc::clone(&self.log);
171            Box::pin(async move {
172                log.lock().unwrap().push("bootstrap".to_string());
173                Ok(())
174            })
175        }
176
177        fn on_module_destroy(
178            &self,
179            _module_ref: ModuleRef,
180            signal: Option<String>,
181        ) -> BoxFuture<'static, crate::Result<()>> {
182            let log = Arc::clone(&self.log);
183            Box::pin(async move {
184                log.lock()
185                    .unwrap()
186                    .push(format!("destroy:{}", signal.unwrap_or_default()));
187                Ok(())
188            })
189        }
190
191        fn before_application_shutdown(
192            &self,
193            _module_ref: ModuleRef,
194            signal: Option<String>,
195        ) -> BoxFuture<'static, crate::Result<()>> {
196            let log = Arc::clone(&self.log);
197            Box::pin(async move {
198                log.lock()
199                    .unwrap()
200                    .push(format!("before:{}", signal.unwrap_or_default()));
201                Ok(())
202            })
203        }
204
205        fn on_application_shutdown_with_signal(
206            &self,
207            _module_ref: ModuleRef,
208            signal: Option<String>,
209        ) -> BoxFuture<'static, crate::Result<()>> {
210            let log = Arc::clone(&self.log);
211            Box::pin(async move {
212                log.lock()
213                    .unwrap()
214                    .push(format!("shutdown:{}", signal.unwrap_or_default()));
215                Ok(())
216            })
217        }
218    }
219
220    struct PendingAdapter {
221        log: Arc<Mutex<Vec<String>>>,
222        ready: Mutex<Option<oneshot::Sender<()>>>,
223    }
224
225    impl PendingAdapter {
226        fn new(log: Arc<Mutex<Vec<String>>>, ready: oneshot::Sender<()>) -> Self {
227            Self {
228                log,
229                ready: Mutex::new(Some(ready)),
230            }
231        }
232    }
233
234    impl HttpAdapter for PendingAdapter {
235        type Output = ();
236
237        fn build(&self, _app: BootApplication) -> crate::Result<Self::Output> {
238            Ok(())
239        }
240
241        fn serve(
242            &self,
243            _app: BootApplication,
244            _addr: SocketAddr,
245        ) -> BoxFuture<'static, crate::Result<()>> {
246            let log = Arc::clone(&self.log);
247            let ready = self.ready.lock().unwrap().take();
248            Box::pin(async move {
249                log.lock().unwrap().push("serve".to_string());
250                if let Some(ready) = ready {
251                    let _ = ready.send(());
252                }
253                futures_util::future::pending::<crate::Result<()>>().await
254            })
255        }
256    }
257
258    struct PendingTransport {
259        log: Arc<Mutex<Vec<String>>>,
260        ready: Mutex<Option<oneshot::Sender<()>>>,
261    }
262
263    impl PendingTransport {
264        fn new(log: Arc<Mutex<Vec<String>>>, ready: oneshot::Sender<()>) -> Self {
265            Self {
266                log,
267                ready: Mutex::new(Some(ready)),
268            }
269        }
270    }
271
272    impl MessageTransport for PendingTransport {
273        type Output = ();
274
275        fn build(&self, _app: BootApplication) -> crate::Result<Self::Output> {
276            Ok(())
277        }
278
279        fn serve(&self, _app: BootApplication) -> BoxFuture<'static, crate::Result<()>> {
280            let log = Arc::clone(&self.log);
281            let ready = self.ready.lock().unwrap().take();
282            Box::pin(async move {
283                log.lock().unwrap().push("microservice".to_string());
284                if let Some(ready) = ready {
285                    let _ = ready.send(());
286                }
287                futures_util::future::pending::<crate::Result<()>>().await
288            })
289        }
290    }
291
292    #[tokio::test]
293    async fn listen_with_shutdown_hooks_closes_with_signal_when_signal_wins() {
294        let log = Arc::new(Mutex::new(Vec::new()));
295        let (ready_tx, ready_rx) = oneshot::channel();
296        let mut app = BootFactory::create(ShutdownHookModule {
297            log: Arc::clone(&log),
298        })
299        .unwrap();
300        app.enable_shutdown_hooks([ShutdownSignal::Sigterm]);
301
302        app.listen_with_shutdown_signal_future(
303            &PendingAdapter::new(Arc::clone(&log), ready_tx),
304            ([127, 0, 0, 1], 0).into(),
305            async move {
306                ready_rx.await.map_err(|error| {
307                    BootError::Internal(format!("pending adapter did not start: {error}"))
308                })?;
309                Ok(ShutdownSignal::Sigterm)
310            },
311        )
312        .await
313        .unwrap();
314
315        assert!(!app.is_initialized());
316        assert_eq!(
317            log.lock().unwrap().as_slice(),
318            [
319                "init",
320                "bootstrap",
321                "serve",
322                "destroy:SIGTERM",
323                "before:SIGTERM",
324                "shutdown:SIGTERM"
325            ]
326        );
327    }
328
329    #[tokio::test]
330    async fn microservice_shutdown_hooks_close_with_signal_when_signal_wins() {
331        let log = Arc::new(Mutex::new(Vec::new()));
332        let (ready_tx, ready_rx) = oneshot::channel();
333        let mut microservice = BootFactory::create_microservice(
334            ShutdownHookModule {
335                log: Arc::clone(&log),
336            },
337            PendingTransport::new(Arc::clone(&log), ready_tx),
338        )
339        .unwrap();
340        microservice.enable_shutdown_hooks([ShutdownSignal::Sigterm]);
341
342        microservice
343            .listen_with_shutdown_signal_future(async move {
344                ready_rx.await.map_err(|error| {
345                    BootError::Internal(format!("pending transport did not start: {error}"))
346                })?;
347                Ok(ShutdownSignal::Sigterm)
348            })
349            .await
350            .unwrap();
351
352        assert!(!microservice.is_initialized());
353        assert_eq!(
354            log.lock().unwrap().as_slice(),
355            [
356                "init",
357                "bootstrap",
358                "microservice",
359                "destroy:SIGTERM",
360                "before:SIGTERM",
361                "shutdown:SIGTERM"
362            ]
363        );
364    }
365}