1use super::{
2 BootApplication, BootApplicationBuilder, BootApplicationContext, BootApplicationHandle,
3 BootMicroservice,
4};
5use crate::{MessageTransport, Module, Result};
6use std::sync::Arc;
7
8pub 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}