Browse Source

Added template app bootstrap modules

develop-refactor
chodak166 5 months ago
parent
commit
150020df89
  1. 3
      .devcontainer/devcontainer.json
  2. 17
      app/Cargo.toml
  3. 70
      app/src/app.rs
  4. 6
      app/src/bootstrap.rs
  5. 57
      app/src/bootstrap/app_config.rs
  6. 17
      app/src/bootstrap/cli.rs
  7. 27
      app/src/bootstrap/defaults.rs
  8. 24
      app/src/container.rs
  9. 119
      app/src/main.rs

3
.devcontainer/devcontainer.json

@ -17,8 +17,7 @@
"tamasfe.even-better-toml",
"vadimcn.vscode-lldb",
"frosticless.monokai-one-darker",
"ms-vscode.cpptools",
"serayuzgur.crates"
"ms-vscode.cpptools"
],
"settings": {
"workbench.colorTheme": "Monokai One Darker",

17
app/Cargo.toml

@ -4,5 +4,20 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.51", features = ["derive"] }
# Internal
libmnemor = { path = "../lib" }
# Runtime & Async
tokio = { version = "1.48", features = ["full"] }
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Configuration & Inputs
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.9.8"
clap = { version = "4.5", features = ["derive", "env"] }
config = "0.15.19"
const_format = "0.2.35"

70
app/src/app.rs

@ -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();
}
}

6
app/src/bootstrap.rs

@ -0,0 +1,6 @@
mod app_config;
mod cli;
mod defaults;
pub use self::app_config::AppConfig;
pub use self::cli::Args as CliArgs;

57
app/src/bootstrap/app_config.rs

@ -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")
}
}

17
app/src/bootstrap/cli.rs

@ -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>,
}

27
app/src/bootstrap/defaults.rs

@ -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())
}

24
app/src/container.rs

@ -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 {})
}
}

119
app/src/main.rs

@ -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>,
// /// 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>> {
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);
}
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…
Cancel
Save