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 libmnemor::{System, create_encoder}; |
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)] |
||||
#[clap(rename_all = "kebab-case")] |
||||
enum SystemCli { |
||||
MajorEn, |
||||
MajorPl, |
||||
} |
||||
// use clap::{Parser, ValueEnum};
|
||||
// use libmnemor::{System, create_encoder};
|
||||
|
||||
impl From<SystemCli> for System { |
||||
fn from(cli_system: SystemCli) -> Self { |
||||
match cli_system { |
||||
SystemCli::MajorEn => System::MajorEn, |
||||
SystemCli::MajorPl => System::MajorPl, |
||||
} |
||||
} |
||||
} |
||||
// #[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
// #[clap(rename_all = "kebab-case")]
|
||||
// enum SystemCli {
|
||||
// MajorEn,
|
||||
// MajorPl,
|
||||
// }
|
||||
|
||||
#[derive(Parser)] |
||||
#[command(author, version, about)] |
||||
struct CliArgs { |
||||
/// System name
|
||||
#[arg(short, long, default_value = "major-pl")] |
||||
system: SystemCli, |
||||
// impl From<SystemCli> for System {
|
||||
// fn from(cli_system: SystemCli) -> Self {
|
||||
// match cli_system {
|
||||
// SystemCli::MajorEn => System::MajorEn,
|
||||
// SystemCli::MajorPl => System::MajorPl,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Encode given word
|
||||
#[arg(short, long)] |
||||
encode: Option<String>, |
||||
// #[derive(Parser)]
|
||||
// #[command(author, version, about)]
|
||||
// struct CliArgs {
|
||||
// /// System name
|
||||
// #[arg(short, long, default_value = "major-pl")]
|
||||
// system: SystemCli,
|
||||
|
||||
/// List supported systems
|
||||
#[arg(long)] |
||||
list_systems: bool, |
||||
} |
||||
// /// Encode given word
|
||||
// #[arg(short, long)]
|
||||
// encode: Option<String>,
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> { |
||||
let args = CliArgs::parse(); |
||||
// /// List supported systems
|
||||
// #[arg(long)]
|
||||
// list_systems: bool,
|
||||
// }
|
||||
|
||||
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(()); |
||||
} |
||||
// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// let args = CliArgs::parse();
|
||||
|
||||
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); |
||||
} |
||||
// 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 container; |
||||
use anyhow::Result; |
||||
use app::Application; |
||||
|
||||
#[tokio::main] |
||||
async fn main() -> Result<()> { |
||||
let app = Application::build().await?; |
||||
app.run().await?; |
||||
Ok(()) |
||||
} |
||||
|
||||
Loading…
Reference in new issue