Browse Source

Added template app bootstrap modules with cli commands

develop-refactor
chodak166 5 months ago
parent
commit
19e80cb072
  1. 72
      app/src/app.rs
  2. 2
      app/src/bootstrap.rs
  3. 6
      app/src/bootstrap/app_config.rs
  4. 38
      app/src/bootstrap/cli.rs
  5. 2
      app/src/commands.rs
  6. 12
      app/src/commands/export.rs
  7. 13
      app/src/commands/server.rs
  8. 64
      app/src/main.rs

72
app/src/app.rs

@ -1,4 +1,6 @@
use crate::bootstrap::cli::Command;
use crate::bootstrap::{AppConfig, CliArgs};
use crate::commands;
use crate::container::Container;
use anyhow::Result;
use clap::Parser;
@ -9,6 +11,7 @@ use tracing::{info, warn};
pub struct Application {
config: AppConfig,
container: Container,
command: Command,
}
impl Application {
@ -16,55 +19,64 @@ impl Application {
pub async fn build() -> Result<Self> {
let args = CliArgs::parse();
// 1. Load Config
let config = AppConfig::build(&args)?;
// 1. Determine Port Override (only for Server command)
let port_override = if let Command::Server(ref server_args) = args.command {
server_args.port
} else {
None
};
// 2. Init Logging (Tracing)
// 2. Load Config
let config = AppConfig::build(&args.global, port_override)?;
// 3. Init Logging (Tracing)
tracing_subscriber::fmt()
.with_env_filter(&config.log_level)
.init();
info!("Bootstrapping application...");
// 3. Wire Dependencies
// 4. Wire Dependencies
let container = Container::new(&config).await?;
Ok(Self { config, container })
Ok(Self {
config,
container,
command: args.command,
})
}
/// Execution: Starts the main loop and waits for shutdown signal.
pub async fn run(self) -> Result<()> {
info!("Application started on port {}", self.config.server.port);
match self.command {
Command::Server(_) => {
info!("Application started on port {}", self.config.server.port);
// Spawn the server loop
let server_task = tokio::spawn(commands::server::run(
self.config.clone(),
self.container.clone(),
));
// Example: Spawn a web server or background worker here.
// We pass a clone of the container or specific services.
let server_task = tokio::spawn(server_loop(self.config.clone(), self.container.clone()));
// Wait for Shutdown Signal
match signal::ctrl_c().await {
Ok(()) => {
info!("Received shutdown signal (SIGINT/SIGTERM)");
}
Err(err) => {
warn!("Failed to listen for shutdown signal: {}", err);
}
}
// Wait for Shutdown Signal
match signal::ctrl_c().await {
Ok(()) => {
info!("Received shutdown signal (SIGINT/SIGTERM)");
// Graceful Shutdown Logic
info!("Shutting down...");
server_task.abort(); // Or send a specialized shutdown channel message
}
Err(err) => {
warn!("Failed to listen for shutdown signal: {}", err);
Command::ExportDict(export_args) => {
commands::export::run(export_args, self.config, self.container).await;
}
}
// Graceful Shutdown Logic
info!("Shutting down...");
server_task.abort(); // Or send a specialized shutdown channel message
Ok(())
}
}
/// Simulated long-running process (e.g., HTTP Server)
async fn server_loop(config: AppConfig, container: Container) {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
info!("Health check... DB URL: {}", config.database.url);
// Example usage of the container
// let _ = container.user_service.do_something();
}
}

2
app/src/bootstrap.rs

@ -1,5 +1,5 @@
mod app_config;
mod cli;
pub mod cli; // Make cli public so we can access subcommands
mod defaults;
pub use self::app_config::AppConfig;

6
app/src/bootstrap/app_config.rs

@ -2,7 +2,7 @@ use anyhow::{Context, Result};
use config::{Config, Environment, File};
use serde::Deserialize;
use super::cli::Args;
use super::cli::GlobalArgs;
use super::defaults::set_defaults;
#[derive(Debug, Deserialize, Clone)]
@ -25,7 +25,7 @@ pub struct DatabaseConfig {
}
impl AppConfig {
pub fn build(args: &Args) -> Result<Self> {
pub fn build(args: &GlobalArgs, port_override: Option<u16>) -> Result<Self> {
let mut builder = Config::builder();
// Defaults
@ -41,7 +41,7 @@ impl AppConfig {
builder = builder.add_source(Environment::with_prefix("APP").separator("__"));
// CLI Overrides Layer
if let Some(port) = args.port {
if let Some(port) = port_override {
builder = builder.set_override("server.port", port)?;
}
if let Some(ref level) = args.log_level {

38
app/src/bootstrap/cli.rs

@ -1,17 +1,49 @@
use super::defaults;
use clap::Parser;
use clap::{Args as ClapArgs, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(author, version, about)]
pub struct Args {
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Command,
}
#[derive(ClapArgs, Debug)]
pub struct GlobalArgs {
/// Path to config file
#[arg(short, long, default_value = "config.toml")]
pub config: PathBuf,
#[arg(long, help = defaults::HELP_LOG)]
pub log_level: Option<String>,
}
#[derive(Subcommand, Debug, Clone)]
pub enum Command {
/// Start the application server
Server(ServerArgs),
/// Export dictionary to file
ExportDict(ExportDictArgs),
}
#[derive(ClapArgs, Debug, Clone)]
pub struct ServerArgs {
#[arg(short, long, help = defaults::HELP_PORT)]
pub port: Option<u16>,
}
#[arg(long, help = defaults::HELP_LOG)]
pub log_level: Option<String>,
#[derive(ClapArgs, Debug, Clone)]
pub struct ExportDictArgs {
/// Name of the dictionary to export
#[arg(long)]
pub name: String,
/// Output file path
#[arg(long)]
pub output: PathBuf,
}

2
app/src/commands.rs

@ -0,0 +1,2 @@
pub mod export;
pub mod server;

12
app/src/commands/export.rs

@ -0,0 +1,12 @@
use crate::bootstrap::AppConfig;
use crate::bootstrap::cli::ExportDictArgs;
use crate::container::Container;
use tracing::info;
pub async fn run(args: ExportDictArgs, _config: AppConfig, _container: Container) {
info!("Exporting dictionary '{}' to {:?}", args.name, args.output);
// Logic for export would go here
// e.g. let dict = container.dict_service.get(args.name);
// encoder.write(dict, args.output);
}

13
app/src/commands/server.rs

@ -0,0 +1,13 @@
use crate::bootstrap::AppConfig;
use crate::container::Container;
use tracing::info;
pub async fn run(config: AppConfig, container: Container) {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
info!("Health check... DB URL: {}", config.database.url);
// Example usage of the container
// let _ = container.user_service.do_something();
}
}

64
app/src/main.rs

@ -1,68 +1,8 @@
// use clap::{Parser, ValueEnum};
// use libmnemor::{System, create_encoder};
// #[derive(ValueEnum, Debug, Clone, Copy)]
// #[clap(rename_all = "kebab-case")]
// enum SystemCli {
// MajorEn,
// MajorPl,
// }
// impl From<SystemCli> for System {
// fn from(cli_system: SystemCli) -> Self {
// match cli_system {
// SystemCli::MajorEn => System::MajorEn,
// SystemCli::MajorPl => System::MajorPl,
// }
// }
// }
// #[derive(Parser)]
// #[command(author, version, about)]
// struct CliArgs {
// /// System name
// #[arg(short, long, default_value = "major-pl")]
// system: SystemCli,
// /// Encode given word
// #[arg(short, long)]
// encode: Option<String>,
// /// List supported systems
// #[arg(long)]
// list_systems: bool,
// }
// fn main() -> Result<(), Box<dyn std::error::Error>> {
// let args = CliArgs::parse();
// if args.list_systems {
// println!("Supported systems:");
// for system in SystemCli::value_variants() {
// if let Some(possible_value) = system.to_possible_value() {
// println!("- {}", possible_value.get_name());
// }
// }
// return Ok(());
// }
// if let Some(word) = args.encode {
// println!(
// "Encoding {} with system {:?}...",
// word,
// args.system.to_possible_value().unwrap().get_name()
// );
// let encoder = create_encoder(&args.system.into());
// let encoded_word = encoder.encode(&word);
// println!("Encoded: [{}]", encoded_word);
// }
// Ok(())
// }
mod app;
mod bootstrap;
mod commands;
mod container;
use anyhow::Result;
use app::Application;

Loading…
Cancel
Save