use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use mockd::config::Config;
use mockd::server::Server;
#[derive(Debug, Parser)]
#[command(name = "mockd", version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Serve {
config: PathBuf,
#[arg(long)]
cors: bool,
},
Validate {
config: PathBuf,
},
Generate { path: Option<PathBuf> },
}
fn main() -> ExitCode {
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("mockd=info"));
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let cli = Cli::parse();
match run(cli) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
tracing::error!("{err:#}");
ExitCode::FAILURE
}
}
}
fn run(cli: Cli) -> anyhow::Result<()> {
match cli.command {
Command::Serve { config, cors } => {
let cfg = Config::from_file(&config)?;
let server = Server::from_config(cfg)?.with_cors(cors);
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
runtime.block_on(server.serve(shutdown_signal()))?;
}
Command::Validate { config } => {
let cfg = Config::from_file(&config)?;
let server = Server::from_config(cfg)?;
tracing::info!(
"config is valid: {} route(s) registered",
server.route_count()
);
}
Command::Generate { path } => {
let path_dir = path.unwrap_or_else(|| PathBuf::from("./docs/"));
Config::write_config_schema(&path_dir)?;
}
}
Ok(())
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
tracing::info!("shutdown signal received");
}