use std::pin::Pin;
use std::sync::Arc;
use crate::App;
use crate::config;
use crate::container::{Container, RawContainer, TypeNotRegistered};
use crate::error::{Result, TypedError};
use crate::server::BoxedServer;
use crate::server::Server;
use crate::tracing;
use crate::tracing::config::TracingConfig;
crate::define_errors! {
crate::error::ErrorCode::Internal => [
BootstrapError,
]
}
pub struct AppBuilder {
app: App,
config_provider: config::TomlConfigProvider<String>,
tracing_builder: tracing::TracingBuilder,
servers: Vec<BoxedServer>,
}
impl Default for AppBuilder {
fn default() -> Self {
let app = App::with_container(RawContainer::new());
let config_provider = config::TomlConfigProvider::new("app.toml").build();
let mut builder = Self {
app,
config_provider,
tracing_builder: tracing::TracingBuilder::default(),
servers: vec![],
};
builder.init().unwrap();
builder
}
}
impl AppBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn app(&self) -> &App {
&self.app
}
pub fn add_server<S>(&mut self, server: S) -> &mut Self
where
S: Server<Future = Pin<Box<dyn Future<Output = ()> + Send>>> + Send + 'static,
{
self.servers.push(Box::new(server));
self
}
pub fn compose<F>(&mut self, composer: F) -> &mut Self
where
F: Fn(&mut AppBuilder),
{
composer(self);
self
}
pub fn bind_config<T>(&mut self) -> Result<&mut Self>
where
T: 'static + config::Config + serde::de::DeserializeOwned,
{
let config = self.config_provider.parse::<T>()?;
self.bind(config);
Ok(self)
}
pub fn get_config<T>(&self) -> Result<Arc<T>>
where
T: 'static + config::Config,
{
self.get::<T>().map_err(|e| {
if e.is(TypeNotRegistered) {
config::ConfigNotRegistered::error(format!(
"Configuration of type {} is not registered",
std::any::type_name::<T>()
))
} else {
e
}
})
}
pub async fn start(mut self) -> Result<()> {
use tracing::info;
self.bootstrap_core()?;
info!("🚀 Starting application...");
match self.servers.len() {
0 => {
tracing::warn!("No servers registered");
}
1 => {
let server = self.servers.into_iter().next().unwrap();
server.start().await;
}
_ => {
let mut set = tokio::task::JoinSet::new();
for server in self.servers {
set.spawn(async move { server.start().await });
}
while set.join_next().await.is_some() {}
}
}
Ok(())
}
fn init(&mut self) -> Result<()> {
self.set_bootstrap_logger();
self.configure_core()?;
Ok(())
}
fn configure_core(&mut self) -> Result<()> {
self.bind_config::<tracing::config::TracingConfig>()?;
self.tracing_builder.with_config(true);
Ok(())
}
fn bootstrap_core(&mut self) -> Result<()> {
#[cfg(feature = "otel")]
self.bootstrap_telemetry()?;
self.bootstrap_logger()?;
Ok(())
}
#[cfg(feature = "otel")]
fn bootstrap_telemetry(&mut self) -> Result<()> {
use crate::telemetry::{OtelConfig, create_otel_layer, init_tracer_provider};
self.bind_config::<OtelConfig>()?;
let otel_config = self.get_config::<OtelConfig>()?;
let provider = init_tracer_provider(&otel_config);
let otel_layer = create_otel_layer(&provider);
self.tracing_builder.add_layer("otel", otel_layer);
self.bind(provider);
tracing::info!("OpenTelemetry tracing enabled");
Ok(())
}
pub fn with_tracing<F>(&mut self, configure: F) -> &mut Self
where
F: FnOnce(&mut tracing::TracingBuilder),
{
configure(&mut self.tracing_builder);
self
}
#[deprecated(since = "0.2.0", note = "Use `with_tracing` instead")]
pub fn with_logger<F>(&mut self, configure: F) -> &mut Self
where
F: FnOnce(&mut tracing::TracingBuilder),
{
self.with_tracing(configure)
}
fn bootstrap_logger(&mut self) -> Result<()> {
let log_config = self.get_config::<TracingConfig>()?;
let (subscriber, keeper) = self.tracing_builder.build(log_config.as_ref());
self.drop_bootstrap_logger();
if cfg!(not(test)) {
::tracing::subscriber::set_global_default(subscriber).map_err(|e| {
BootstrapError::error(format!("Failed to set global tracing subscriber: {}", e))
})?;
}
self.bind(keeper);
Ok(())
}
fn set_bootstrap_logger(&mut self) {
self.bind(tracing::boot::BootstrapperSubscriber::new());
}
fn drop_bootstrap_logger(&mut self) {
self
.app
.container
.drop::<tracing::boot::BootstrapperSubscriber>();
}
}
impl Container for AppBuilder {
fn add<F, T, Deps>(&mut self, factory: F)
where
F: crate::container::Factory<T, Deps>,
T: 'static,
Deps: 'static,
{
self.app.container.add(factory);
}
fn bind<T>(&mut self, instance: T)
where
T: 'static,
{
self.app.container.bind(instance);
}
fn get<T>(&self) -> Result<Arc<T>>
where
T: 'static,
{
self.app.container.get::<T>()
}
fn drop<T>(&mut self)
where
T: 'static,
{
self.app.container.drop::<T>();
}
}