9 changed files with 284 additions and 56 deletions
@ -0,0 +1,70 @@ |
|||||||
|
use crate::bootstrap::{AppConfig, CliArgs}; |
||||||
|
use crate::container::Container; |
||||||
|
use anyhow::Result; |
||||||
|
use clap::Parser; |
||||||
|
use tokio::signal; |
||||||
|
use tracing::{info, warn}; |
||||||
|
|
||||||
|
/// The Main Application entry point.
|
||||||
|
pub struct Application { |
||||||
|
config: AppConfig, |
||||||
|
container: Container, |
||||||
|
} |
||||||
|
|
||||||
|
impl Application { |
||||||
|
/// Bootstrap: Loads config, initializes logging, wires dependencies.
|
||||||
|
pub async fn build() -> Result<Self> { |
||||||
|
let args = CliArgs::parse(); |
||||||
|
|
||||||
|
// 1. Load Config
|
||||||
|
let config = AppConfig::build(&args)?; |
||||||
|
|
||||||
|
// 2. Init Logging (Tracing)
|
||||||
|
tracing_subscriber::fmt() |
||||||
|
.with_env_filter(&config.log_level) |
||||||
|
.init(); |
||||||
|
|
||||||
|
info!("Bootstrapping application..."); |
||||||
|
|
||||||
|
// 3. Wire Dependencies
|
||||||
|
let container = Container::new(&config).await?; |
||||||
|
|
||||||
|
Ok(Self { config, container }) |
||||||
|
} |
||||||
|
|
||||||
|
/// 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); |
||||||
|
|
||||||
|
// 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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 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();
|
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
mod app_config; |
||||||
|
mod cli; |
||||||
|
mod defaults; |
||||||
|
|
||||||
|
pub use self::app_config::AppConfig; |
||||||
|
pub use self::cli::Args as CliArgs; |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
use anyhow::{Context, Result}; |
||||||
|
use config::{Config, Environment, File}; |
||||||
|
use serde::Deserialize; |
||||||
|
|
||||||
|
use super::cli::Args; |
||||||
|
use super::defaults::set_defaults; |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)] |
||||||
|
pub struct AppConfig { |
||||||
|
pub server: ServerConfig, |
||||||
|
pub database: DatabaseConfig, |
||||||
|
pub log_level: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)] |
||||||
|
pub struct ServerConfig { |
||||||
|
pub host: String, |
||||||
|
pub port: u16, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)] |
||||||
|
pub struct DatabaseConfig { |
||||||
|
pub url: String, |
||||||
|
pub max_connections: u32, |
||||||
|
} |
||||||
|
|
||||||
|
impl AppConfig { |
||||||
|
pub fn build(args: &Args) -> Result<Self> { |
||||||
|
let mut builder = Config::builder(); |
||||||
|
|
||||||
|
// Defaults
|
||||||
|
builder = set_defaults(builder)?; |
||||||
|
|
||||||
|
// File Layer
|
||||||
|
let config_path = &args.config; |
||||||
|
let is_default_path = config_path.to_str() == Some("config.toml"); |
||||||
|
|
||||||
|
builder = builder.add_source(File::from(config_path.as_path()).required(!is_default_path)); |
||||||
|
|
||||||
|
// Environment Layer (APP__SERVER__PORT)
|
||||||
|
builder = builder.add_source(Environment::with_prefix("APP").separator("__")); |
||||||
|
|
||||||
|
// CLI Overrides Layer
|
||||||
|
if let Some(port) = args.port { |
||||||
|
builder = builder.set_override("server.port", port)?; |
||||||
|
} |
||||||
|
if let Some(ref level) = args.log_level { |
||||||
|
builder = builder.set_override("log_level", level.clone())?; |
||||||
|
} |
||||||
|
|
||||||
|
builder |
||||||
|
.build() |
||||||
|
.context("Failed to build configuration layers")? |
||||||
|
.try_deserialize() |
||||||
|
.context("Failed to deserialize Config") |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
use super::defaults; |
||||||
|
use clap::Parser; |
||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
|
#[derive(Parser, Debug)] |
||||||
|
#[command(author, version, about)] |
||||||
|
pub struct Args { |
||||||
|
/// Path to config file
|
||||||
|
#[arg(short, long, default_value = "config.toml")] |
||||||
|
pub config: PathBuf, |
||||||
|
|
||||||
|
#[arg(short, long, help = defaults::HELP_PORT)] |
||||||
|
pub port: Option<u16>, |
||||||
|
|
||||||
|
#[arg(long, help = defaults::HELP_LOG)] |
||||||
|
pub log_level: Option<String>, |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
use anyhow::Result; |
||||||
|
pub use config::ConfigBuilder; |
||||||
|
|
||||||
|
use const_format::formatcp; |
||||||
|
|
||||||
|
pub const HOST: &str = "127.0.0.1"; |
||||||
|
pub const PORT: u16 = 8080; |
||||||
|
pub const LOG_LEVEL: &str = "info"; |
||||||
|
pub const DB_URL: &str = "postgres://localhost:5432/myapp"; |
||||||
|
pub const DB_MAX_CONNS: u32 = 5; |
||||||
|
|
||||||
|
pub const HELP_PORT: &str = formatcp!("Override Port [default: {}]", PORT); |
||||||
|
pub const HELP_LOG: &str = formatcp!("Override Log Level [default: {}]", LOG_LEVEL); |
||||||
|
|
||||||
|
pub fn set_defaults( |
||||||
|
builder: ConfigBuilder<config::builder::DefaultState>, |
||||||
|
) -> Result<ConfigBuilder<config::builder::DefaultState>> { |
||||||
|
builder |
||||||
|
.set_default("log_level", LOG_LEVEL)? |
||||||
|
// Server
|
||||||
|
.set_default("server.host", HOST)? |
||||||
|
.set_default("server.port", PORT)? |
||||||
|
// Database
|
||||||
|
.set_default("database.url", DB_URL)? |
||||||
|
.set_default("database.max_connections", DB_MAX_CONNS) |
||||||
|
.map_err(|e| e.into()) |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
use crate::bootstrap::AppConfig; |
||||||
|
// use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Clone)] |
||||||
|
pub struct Container { |
||||||
|
// pub user_service: Arc<UserService>,
|
||||||
|
// Add other services here (e.g., EmailService, MetricsService)
|
||||||
|
} |
||||||
|
|
||||||
|
impl Container { |
||||||
|
pub async fn new(config: &AppConfig) -> anyhow::Result<Self> { |
||||||
|
// 1. Infrastructure / Adapters
|
||||||
|
// Using the config to initialize connections
|
||||||
|
// let user_repo = PostgresUserRepository::new(&AppConfig.database.url).await?;
|
||||||
|
// let user_repo = Arc::new(user_repo);
|
||||||
|
|
||||||
|
// // 2. Application / Domain Services
|
||||||
|
// // Injecting the repository into the service
|
||||||
|
// let user_service = UserService::new(user_repo);
|
||||||
|
// let user_service = Arc::new(user_service);
|
||||||
|
|
||||||
|
Ok(Self {}) |
||||||
|
} |
||||||
|
} |
||||||
@ -1,61 +1,74 @@ |
|||||||
use clap::{Parser, ValueEnum}; |
// use clap::{Parser, ValueEnum};
|
||||||
use libmnemor::{System, create_encoder}; |
// use libmnemor::{System, create_encoder};
|
||||||
|
|
||||||
#[derive(ValueEnum, Debug, Clone, Copy)] |
|
||||||
#[clap(rename_all = "kebab-case")] |
|
||||||
enum SystemCli { |
|
||||||
MajorEn, |
|
||||||
MajorPl, |
|
||||||
} |
|
||||||
|
|
||||||
impl From<SystemCli> for System { |
// #[derive(ValueEnum, Debug, Clone, Copy)]
|
||||||
fn from(cli_system: SystemCli) -> Self { |
// #[clap(rename_all = "kebab-case")]
|
||||||
match cli_system { |
// enum SystemCli {
|
||||||
SystemCli::MajorEn => System::MajorEn, |
// MajorEn,
|
||||||
SystemCli::MajorPl => System::MajorPl, |
// MajorPl,
|
||||||
} |
// }
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Parser)] |
// impl From<SystemCli> for System {
|
||||||
#[command(author, version, about)] |
// fn from(cli_system: SystemCli) -> Self {
|
||||||
struct CliArgs { |
// match cli_system {
|
||||||
/// System name
|
// SystemCli::MajorEn => System::MajorEn,
|
||||||
#[arg(short, long, default_value = "major-pl")] |
// SystemCli::MajorPl => System::MajorPl,
|
||||||
system: SystemCli, |
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
/// Encode given word
|
// #[derive(Parser)]
|
||||||
#[arg(short, long)] |
// #[command(author, version, about)]
|
||||||
encode: Option<String>, |
// struct CliArgs {
|
||||||
|
// /// System name
|
||||||
|
// #[arg(short, long, default_value = "major-pl")]
|
||||||
|
// system: SystemCli,
|
||||||
|
|
||||||
/// List supported systems
|
// /// Encode given word
|
||||||
#[arg(long)] |
// #[arg(short, long)]
|
||||||
list_systems: bool, |
// 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(())
|
||||||
|
// }
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> { |
mod app; |
||||||
let args = CliArgs::parse(); |
mod bootstrap; |
||||||
|
mod container; |
||||||
if args.list_systems { |
use anyhow::Result; |
||||||
println!("Supported systems:"); |
use app::Application; |
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
|
#[tokio::main] |
||||||
|
async fn main() -> Result<()> { |
||||||
|
let app = Application::build().await?; |
||||||
|
app.run().await?; |
||||||
Ok(()) |
Ok(()) |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue