//! Executable entry point for Lisa's Phase 1 foundation.
use std::{
env,
path::PathBuf,
process::ExitCode,
time::{SystemTime, UNIX_EPOCH},
};
use lisa::{app::LisaApp, config::AppConfig, logging, state::StateStore};
const CONFIGURATION_ERROR_EXIT: u8 = 78;
struct Command
{
config_path: PathBuf,
health: bool,
}
#[tokio::main]
async fn main() -> ExitCode {
let command = match command() {
Ok(command) => command,
Err(error) => {
eprintln!("{error}");
return ExitCode::FAILURE;
}
};
let config = match AppConfig::load(&command.config_path) {
Ok(config) => config,
Err(error) => {
eprintln!("configuration error: {error}");
return ExitCode::from(CONFIGURATION_ERROR_EXIT);
}
};
logging::init(&config.runtime.log_level);
if command.health
{
return health(&config).await;
}
let result = match LisaApp::connect(config).await {
Ok(app) => app.run().await,
Err(error) => Err(error),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("Lisa stopped: {error}");
if error.isPermanentConfigurationFailure()
{
ExitCode::from(CONFIGURATION_ERROR_EXIT)
}
else
{
ExitCode::FAILURE
}
}
}
}
async fn health(config: &AppConfig) -> ExitCode
{
let store = match StateStore::open(&config.runtime.database_path)
{
Ok(store) => store,
Err(error) =>
{
eprintln!("health check failed: {error}");
return ExitCode::FAILURE;
}
};
let now_ms = SystemTime::now().duration_since(UNIX_EPOCH)
.unwrap_or_default().as_millis().try_into().unwrap_or(u64::MAX);
match store.health(now_ms).await
{
Ok(snapshot) =>
{
println!(
"{{\"candidate_count\":{},\"ready_to_send_count\":{},\"oldest_ready_age_ms\":{}}}",
snapshot.candidate_count,
snapshot.ready_to_send_count,
snapshot.oldest_ready_age_ms.map_or_else(|| "null".to_owned(), |age| age.to_string()),
);
ExitCode::SUCCESS
}
Err(error) =>
{
eprintln!("health check failed: {error}");
ExitCode::FAILURE
}
}
}
fn command() -> Result<Command, String> {
let mut arguments = env::args_os().skip(1);
let mut config_path = PathBuf::from("config.toml");
let mut health = false;
while let Some(argument) = arguments.next()
{
if argument == "--health"
{
health = true;
}
else if argument == "--config"
{
config_path = arguments.next().map(PathBuf::from)
.ok_or_else(|| "--config requires a path".to_owned())?;
}
else
{
return Err("usage: lisa [--config PATH] [--health]".to_owned());
}
}
Ok(Command { config_path, health })
}