drogue_bazaar/app/run/
mod.rs1mod main;
2
3pub use main::*;
4
5use crate::app::init::{self, Tracing};
6use crate::core::{config::ConfigFromEnv, info::ComponentInformation};
7use crate::{app::health::HealthServerConfig, core::Spawner, health::HealthChecked};
8use std::future::Future;
9use std::io::Write;
10use std::pin::Pin;
11use std::time::Duration;
12
13#[derive(Clone, Debug, Default, serde::Deserialize)]
14pub struct RuntimeConfig {
15 #[serde(default)]
16 pub console_metrics: ConsoleMetrics,
17 #[serde(default)]
18 pub health: HealthServerConfig,
19 #[serde(default)]
20 pub tracing: Tracing,
21}
22
23#[derive(Clone, Debug, serde::Deserialize)]
24pub struct ConsoleMetrics {
25 pub enabled: bool,
26 #[serde(
27 default = "default::console_metrics_duration",
28 with = "humantime_serde"
29 )]
30 pub period: Duration,
31}
32
33impl Default for ConsoleMetrics {
34 fn default() -> Self {
35 Self {
36 enabled: false,
37 period: default::console_metrics_duration(),
38 }
39 }
40}
41
42mod default {
43 use super::*;
44
45 pub const fn console_metrics_duration() -> Duration {
46 Duration::from_secs(60)
47 }
48}
49
50pub struct Runtime {
51 component: ComponentInformation,
52 dotenv: Option<bool>,
53 show_banner: Option<bool>,
54}
55
56#[macro_export]
76macro_rules! runtime {
77 ($project:expr) => {
78 $crate::app::Runtime::new($crate::component!($project))
79 };
80}
81
82impl Runtime {
83 pub fn new(component: ComponentInformation) -> Self {
84 Self {
85 component,
86 dotenv: None,
87 show_banner: None,
88 }
89 }
90
91 #[allow(clippy::needless_doctest_main)]
104 pub fn dotenv<I: Into<Option<bool>>>(mut self, dotenv: I) -> Self {
105 self.dotenv = dotenv.into();
106 self
107 }
108
109 fn banner(&self) {
111 if self
112 .show_banner
113 .or_else(|| flag_opt("RUNTIME__SHOW_BANNER"))
114 .unwrap_or(true)
115 {
116 println!(
117 r#"{}
118{} {} - {} {} ({})
119"#,
120 self.component.project.banner,
121 self.component.project.name,
122 self.component.project.version,
123 self.component.name,
124 self.component.version,
125 self.component.description
126 );
127
128 std::io::stdout().flush().ok();
129 }
130 }
131
132 pub async fn exec<C, A>(self, app: A) -> anyhow::Result<()>
133 where
134 A: App<C>,
135 for<'de> C: ConfigFromEnv<'de>,
136 {
137 init::phase1(
140 self.dotenv
141 .unwrap_or_else(|| !flag("RUNTIME__DISABLE_DOTENV")),
142 );
143
144 self.banner();
146
147 let mut main = Main::from_env()?;
150 init::phase2(self.component.name, main.runtime_config().tracing.clone());
151
152 let config = C::from_env()?;
155 app.run(config, &mut main).await?;
156 main.run().await?;
157
158 opentelemetry::global::shutdown_tracer_provider();
160
161 Ok(())
164 }
165
166 pub async fn exec_fn<C, F>(self, f: F) -> anyhow::Result<()>
167 where
168 for<'de> C: ConfigFromEnv<'de> + Send + 'static,
169 F: for<'f> AppFn<C, &'f mut dyn Startup>,
170 {
171 self.exec(f).await
172 }
173}
174
175pub trait AppFn<C, S>: FnOnce(C, S) -> <Self as AppFn<C, S>>::Fut {
176 type Fut: Future<Output = anyhow::Result<()>>;
177}
178
179impl<C, S, F, Fut> AppFn<C, S> for F
180where
181 F: FnOnce(C, S) -> Fut,
182 Fut: Future<Output = anyhow::Result<()>>,
183{
184 type Fut = Fut;
185}
186
187#[async_trait::async_trait(?Send)]
188pub trait App<C>
189where
190 for<'de> C: ConfigFromEnv<'de>,
191{
192 async fn run(self, config: C, startup: &mut dyn Startup) -> anyhow::Result<()>;
193}
194
195#[async_trait::async_trait(?Send)]
196impl<C, A> App<C> for A
197where
198 A: for<'f> AppFn<C, &'f mut dyn Startup>,
199 C: for<'de> ConfigFromEnv<'de> + 'static,
200{
201 async fn run(self, config: C, startup: &mut dyn Startup) -> anyhow::Result<()> {
202 (self)(config, startup).await
203 }
204}
205
206fn flag(name: &str) -> bool {
207 flag_opt(name).unwrap_or_default()
208}
209
210fn flag_opt(name: &str) -> Option<bool> {
211 std::env::var(name).map(|v| v.to_lowercase() == "true").ok()
212}
213
214pub trait Startup: Spawner {
216 fn check_boxed(&mut self, check: Box<dyn HealthChecked>);
218
219 fn use_tracing(&self) -> bool;
223
224 fn runtime_config(&self) -> &RuntimeConfig;
226}
227
228pub trait StartupExt: Startup {
229 fn check_iter<I>(&mut self, iter: I)
231 where
232 I: IntoIterator<Item = Box<dyn HealthChecked>>,
233 {
234 for i in iter {
235 self.check_boxed(i);
236 }
237 }
238
239 fn check<C>(&mut self, c: C)
240 where
241 C: HealthChecked + 'static,
242 {
243 self.check_boxed(Box::new(c))
244 }
245
246 fn spawn_iter<I>(&mut self, iter: I)
247 where
248 I: IntoIterator<Item = Pin<Box<dyn Future<Output = anyhow::Result<()>>>>>,
249 {
250 for i in iter {
251 self.spawn_boxed(i);
252 }
253 }
254
255 fn spawn<F>(&mut self, f: F)
256 where
257 F: Future<Output = anyhow::Result<()>> + 'static,
258 {
259 self.spawn_boxed(Box::pin(f))
260 }
261}
262
263impl<S: ?Sized> StartupExt for S where S: Startup {}