#![deny(unsafe_code)]
pub mod error;
#[cfg(feature = "cli")]
pub mod cli;
#[cfg(feature = "config")]
pub mod config;
#[cfg(feature = "logging")]
pub mod logging;
#[cfg(feature = "otel")]
pub mod otel;
#[cfg(feature = "shutdown")]
pub mod shutdown;
#[cfg(feature = "crash")]
pub mod crash;
#[cfg(feature = "mcp")]
pub mod mcp;
#[cfg(feature = "lockfile")]
pub mod lockfile;
#[cfg(feature = "http")]
pub mod http;
#[cfg(feature = "cache")]
pub mod cache;
#[cfg(feature = "update")]
pub mod update;
#[cfg(feature = "dispatch")]
pub mod dispatch;
#[cfg(feature = "diagnostics")]
pub mod diagnostics;
#[cfg(any(feature = "bench", feature = "bench-gungraun"))]
pub mod bench;
#[cfg(feature = "logging")]
use tracing_subscriber::layer::SubscriberExt;
#[cfg(feature = "logging")]
use tracing_subscriber::util::SubscriberInitExt;
pub use error::{Error, Result};
pub struct App<C = ()> {
app_name: String,
version: String,
config: C,
#[cfg(feature = "config")]
config_sources: config::ConfigSources,
#[cfg(feature = "cli")]
cli: cli::CommonArgs,
#[cfg(feature = "shutdown")]
shutdown_handle: Option<shutdown::ShutdownHandle>,
#[cfg(feature = "otel")]
_otel_guard: Option<otel::OtelGuard>,
#[cfg(feature = "logging")]
_logging_guard: Option<logging::LoggingGuard>,
}
impl<C> App<C> {
pub const fn config(&self) -> &C {
&self.config
}
pub fn app_name(&self) -> &str {
&self.app_name
}
pub fn version(&self) -> &str {
&self.version
}
}
#[cfg(feature = "config")]
impl<C> App<C> {
pub const fn config_sources(&self) -> &config::ConfigSources {
&self.config_sources
}
}
#[cfg(feature = "cli")]
impl<C> App<C> {
pub const fn cli(&self) -> &cli::CommonArgs {
&self.cli
}
}
#[cfg(feature = "shutdown")]
impl<C> App<C> {
pub fn shutdown_token(&self) -> Option<shutdown::ShutdownToken> {
self.shutdown_handle.as_ref().map(|h| h.token())
}
pub fn shutdown(&self) {
if let Some(ref handle) = self.shutdown_handle {
handle.shutdown();
}
}
}
struct BuilderInner {
app_name: String,
version: Option<String>,
#[cfg(feature = "cli")]
cli: Option<cli::CommonArgs>,
#[cfg(feature = "logging")]
enable_logging: bool,
#[cfg(feature = "logging")]
log_dir: Option<std::path::PathBuf>,
#[cfg(feature = "otel")]
enable_otel: bool,
#[cfg(feature = "shutdown")]
enable_shutdown: bool,
#[cfg(feature = "crash")]
enable_crash: bool,
}
struct SubsystemInit {
app_name: String,
version: String,
#[cfg(feature = "cli")]
cli: cli::CommonArgs,
#[cfg(feature = "shutdown")]
shutdown_handle: Option<shutdown::ShutdownHandle>,
#[cfg(feature = "otel")]
otel_guard: Option<otel::OtelGuard>,
#[cfg(feature = "logging")]
logging_guard: Option<logging::LoggingGuard>,
}
impl BuilderInner {
#[cfg(all(feature = "logging", feature = "cli"))]
fn cli_flags(&self) -> (bool, u8) {
self.cli
.as_ref()
.map_or((false, 0), |c| (c.quiet, c.verbose))
}
#[cfg(all(feature = "logging", not(feature = "cli")))]
fn cli_flags(&self) -> (bool, u8) {
(false, 0)
}
fn init_subsystems(self) -> Result<SubsystemInit> {
#[cfg(feature = "logging")]
let cli_flags = self.cli_flags();
#[cfg(feature = "logging")]
let do_logging = self.enable_logging;
let app_version = self
.version
.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
#[cfg(feature = "logging")]
let (log_layer, log_guard) = if do_logging {
let log_cfg =
logging::LoggingConfig::from_app_name(&self.app_name).with_log_dir(self.log_dir);
let (layer, guard) = logging::build_json_layer(&log_cfg)?;
(Some(layer), Some(logging::LoggingGuard::from_guard(guard)))
} else {
(None, None)
};
#[cfg(feature = "otel")]
let (otel_layer, otel_guard) = if self.enable_otel {
let otel_cfg = otel::OtelConfig::from_app_name(&self.app_name, &app_version);
otel::build_otel_layer(&otel_cfg)?
} else {
(None, None)
};
#[cfg(all(feature = "logging", not(feature = "otel")))]
if log_layer.is_some() {
let (quiet, verbose) = cli_flags;
let filter = logging::env_filter(quiet, verbose, "info");
tracing_subscriber::registry()
.with(filter)
.with(log_layer)
.try_init()
.map_err(Error::TracingInit)?;
}
#[cfg(all(feature = "logging", feature = "otel"))]
if log_layer.is_some() || otel_layer.is_some() {
let (quiet, verbose) = cli_flags;
let filter = logging::env_filter(quiet, verbose, "info");
let mut layers: Vec<
Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + Send + Sync>,
> = Vec::new();
layers.push(Box::new(filter));
if let Some(l) = log_layer {
layers.push(Box::new(l));
}
if let Some(l) = otel_layer {
layers.push(l);
}
tracing_subscriber::registry()
.with(layers)
.try_init()
.map_err(Error::TracingInit)?;
}
#[cfg(feature = "shutdown")]
let shutdown_handle = if self.enable_shutdown {
let handle = shutdown::ShutdownHandle::new();
handle.register_signals()?;
Some(handle)
} else {
None
};
#[cfg(feature = "crash")]
if self.enable_crash {
crash::install(&self.app_name, &app_version);
}
Ok(SubsystemInit {
app_name: self.app_name,
version: app_version,
#[cfg(feature = "cli")]
cli: self.cli.unwrap_or_else(default_cli),
#[cfg(feature = "shutdown")]
shutdown_handle,
#[cfg(feature = "otel")]
otel_guard,
#[cfg(feature = "logging")]
logging_guard: log_guard,
})
}
}
macro_rules! builder_methods {
() => {
#[cfg(feature = "cli")]
pub fn with_cli(mut self, common: cli::CommonArgs) -> Self {
self.inner.cli = Some(common);
self
}
#[cfg(feature = "logging")]
pub const fn logging(mut self) -> Self {
self.inner.enable_logging = true;
self
}
#[cfg(feature = "logging")]
pub fn with_log_dir(mut self, dir: std::path::PathBuf) -> Self {
self.inner.log_dir = Some(dir);
self
}
#[cfg(feature = "otel")]
pub const fn otel(mut self) -> Self {
self.inner.enable_otel = true;
self
}
#[cfg(feature = "shutdown")]
pub const fn shutdown(mut self) -> Self {
self.inner.enable_shutdown = true;
self
}
#[cfg(feature = "crash")]
pub const fn crash_handler(mut self) -> Self {
self.inner.enable_crash = true;
self
}
pub fn with_version(mut self, version: &str) -> Self {
self.inner.version = Some(version.to_string());
self
}
};
}
pub fn init(app_name: &str) -> Builder {
Builder {
inner: BuilderInner {
app_name: app_name.to_string(),
version: None,
#[cfg(feature = "cli")]
cli: None,
#[cfg(feature = "logging")]
enable_logging: false,
#[cfg(feature = "logging")]
log_dir: None,
#[cfg(feature = "otel")]
enable_otel: false,
#[cfg(feature = "shutdown")]
enable_shutdown: false,
#[cfg(feature = "crash")]
enable_crash: false,
},
}
}
pub struct Builder {
inner: BuilderInner,
}
impl Builder {
builder_methods!();
pub fn start(self) -> Result<App> {
let sub = self.inner.init_subsystems()?;
Ok(App {
app_name: sub.app_name,
version: sub.version,
config: (),
#[cfg(feature = "config")]
config_sources: config::ConfigSources::default(),
#[cfg(feature = "cli")]
cli: sub.cli,
#[cfg(feature = "shutdown")]
shutdown_handle: sub.shutdown_handle,
#[cfg(feature = "otel")]
_otel_guard: sub.otel_guard,
#[cfg(feature = "logging")]
_logging_guard: sub.logging_guard,
})
}
}
#[cfg(feature = "config")]
impl Builder {
pub fn config_from_file<C>(self, path: &camino::Utf8Path) -> ConfiguredBuilder<C>
where
C: serde::de::DeserializeOwned + Default + serde::Serialize,
{
ConfiguredBuilder {
inner: self.inner,
config_source: CfgSource::File(path.to_path_buf()),
}
}
pub fn config<C>(self) -> ConfiguredBuilder<C>
where
C: serde::de::DeserializeOwned + Default + serde::Serialize,
{
ConfiguredBuilder {
inner: self.inner,
config_source: CfgSource::Discover,
}
}
pub fn with_config<C>(self, config: C) -> ConfiguredBuilder<C>
where
C: serde::Serialize,
{
ConfiguredBuilder {
inner: self.inner,
config_source: CfgSource::Preloaded(config),
}
}
}
#[cfg(feature = "config")]
enum CfgSource<C> {
Discover,
File(camino::Utf8PathBuf),
Preloaded(C),
}
#[cfg(feature = "config")]
pub struct ConfiguredBuilder<C> {
inner: BuilderInner,
config_source: CfgSource<C>,
}
#[cfg(feature = "config")]
impl<C> ConfiguredBuilder<C> {
builder_methods!();
}
#[cfg(feature = "config")]
impl<C> ConfiguredBuilder<C>
where
C: serde::de::DeserializeOwned + Default + serde::Serialize,
{
pub fn start(self) -> Result<App<C>> {
let (config, sources) = match self.config_source {
CfgSource::Discover => {
let cwd = std::env::current_dir().map_err(crate::Error::Io)?;
let cwd = camino::Utf8PathBuf::try_from(cwd).map_err(|e| {
crate::Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"current directory is not valid UTF-8: {}",
e.into_path_buf().display()
),
))
})?;
config::ConfigLoader::new(&self.inner.app_name)
.with_project_search(&cwd)
.load::<C>()?
}
CfgSource::File(path) => config::ConfigLoader::new(&self.inner.app_name)
.with_user_config(false)
.with_file(&path)
.load::<C>()?,
CfgSource::Preloaded(config) => (config, config::ConfigSources::default()),
};
let sub = self.inner.init_subsystems()?;
Ok(App {
app_name: sub.app_name,
version: sub.version,
config,
config_sources: sources,
#[cfg(feature = "cli")]
cli: sub.cli,
#[cfg(feature = "shutdown")]
shutdown_handle: sub.shutdown_handle,
#[cfg(feature = "otel")]
_otel_guard: sub.otel_guard,
#[cfg(feature = "logging")]
_logging_guard: sub.logging_guard,
})
}
}
#[cfg(feature = "cli")]
const fn default_cli() -> cli::CommonArgs {
cli::CommonArgs {
version_only: false,
chdir: None,
quiet: false,
verbose: 0,
color: cli::ColorChoice::Auto,
json: false,
}
}