use std::{borrow::Cow, path::PathBuf};
use clap::{ArgAction, Command};
use config::{Config, FileFormat};
use futures::{future::BoxFuture, TryFutureExt};
use tower::BoxError;
use tracing::error;
use crate::tracing::{get_subscriber, init_subscriber};
pub mod impl_;
pub trait Recipe: Send + Sync + 'static {
type Config: Send + Sync + 'static + serde::Serialize + for<'a> serde::Deserialize<'a>;
fn cli_name(&self) -> Cow<'static, str>;
fn description(&self) -> Cow<'static, str>;
fn serve(&self, config: Self::Config) -> BoxFuture<'static, Result<(), BoxError>>;
}
pub trait RecipeExt: Recipe {
fn cli_command(&self) -> Command {
Command::new(self.cli_name().into_owned())
.about(self.description().into_owned())
.arg(
clap::arg!(
-c --config <FILE> "Sets a custom config file"
)
.required(true)
.value_parser(clap::value_parser!(PathBuf)),
)
.arg(
clap::arg!(
-d --debug ... "Turn debugging information on"
)
.action(ArgAction::SetTrue),
)
}
fn parse_cli_args(&self) -> Result<RecipeCliArgs, BoxError> {
let mut cli = self.cli_command().get_matches();
Ok(RecipeCliArgs {
config_path: cli
.remove_one::<PathBuf>("config")
.ok_or("Config is required.")?,
log_level: if cli.get_flag("debug") {
"debug"
} else {
"info"
}
.to_owned(),
})
}
fn run(&self, args: RecipeCliArgs) -> BoxFuture<'_, Result<(), BoxError>> {
Box::pin(async move {
init_subscriber(get_subscriber("Manas".to_owned(), args.log_level));
let config_content = String::from_utf8(
tokio::fs::read(args.config_path)
.inspect_err(|e| {
error!("Error in reading config fle. {}", e);
})
.await?,
)
.map_err(|_| "Invalid config file content")?;
let config = Config::builder()
.add_source(config::File::from_str(&config_content, FileFormat::Toml))
.build()?
.try_deserialize::<Self::Config>()
.map_err(|e| {
error!("Error in parsing configuration. Error: {}", e);
e
})?;
self.serve(config).await
})
}
#[inline]
fn main(&self) -> BoxFuture<'_, Result<(), BoxError>> {
Box::pin(async move { self.run(self.parse_cli_args()?).await })
}
}
impl<R: Recipe> RecipeExt for R {}
#[derive(Debug, Clone)]
pub struct RecipeCliArgs {
pub config_path: PathBuf,
pub log_level: String,
}