Skip to main content

cargo_hammerwork/config/
mod.rs

1//! Configuration management for the Hammerwork CLI.
2//!
3//! This module handles loading, saving, and managing configuration for the CLI tool.
4//! Configuration can be loaded from multiple sources with the following precedence:
5//!
6//! 1. Environment variables (highest priority)
7//! 2. Configuration file
8//! 3. Default values (lowest priority)
9//!
10//! # Configuration File Location
11//!
12//! The configuration file is stored at platform-specific locations:
13//!
14//! - **Linux/Mac**: `~/.config/hammerwork/config.toml`
15//! - **Windows**: `%APPDATA%\hammerwork\config.toml`
16//!
17//! # Examples
18//!
19//! ## Loading Configuration
20//!
21//! ```rust,no_run
22//! use cargo_hammerwork::config::Config;
23//!
24//! // Load configuration from file and environment
25//! let config = Config::load().expect("Failed to load config");
26//!
27//! // Access configuration values
28//! if let Some(db_url) = config.get_database_url() {
29//!     println!("Database URL: {}", db_url);
30//! }
31//!
32//! println!("Default queue: {:?}", config.get_default_queue());
33//! println!("Default limit: {}", config.get_default_limit());
34//! ```
35//!
36//! ## Creating and Saving Configuration
37//!
38//! ```rust,no_run
39//! use cargo_hammerwork::config::Config;
40//!
41//! // Create a new configuration with custom values
42//! let mut config = Config::default();
43//! config.database_url = Some("postgresql://localhost/hammerwork".to_string());
44//! config.default_queue = Some("default".to_string());
45//! config.default_limit = Some(100);
46//! config.log_level = Some("debug".to_string());
47//!
48//! // Save configuration to file
49//! config.save().expect("Failed to save config");
50//! ```
51//!
52//! ## Environment Variable Override
53//!
54//! ```rust,no_run
55//! use std::env;
56//! use cargo_hammerwork::config::Config;
57//!
58//! // Set environment variables (unsafe in real code, safe for docs)
59//! unsafe {
60//!     env::set_var("DATABASE_URL", "postgresql://prod-server/hammerwork");
61//!     env::set_var("HAMMERWORK_DEFAULT_QUEUE", "high-priority");
62//!     env::set_var("HAMMERWORK_LOG_LEVEL", "warn");
63//! }
64//!
65//! // Load config - environment variables take precedence
66//! let config = Config::load().expect("Failed to load config");
67//!
68//! assert_eq!(config.get_database_url(), Some("postgresql://prod-server/hammerwork"));
69//! assert_eq!(config.get_default_queue(), Some("high-priority"));
70//! assert_eq!(config.get_log_level(), "warn");
71//! ```
72//!
73//! ## Configuration File Format
74//!
75//! The configuration file uses TOML format:
76//!
77//! ```toml
78//! database_url = "postgresql://localhost/hammerwork"
79//! default_queue = "default"
80//! default_limit = 50
81//! log_level = "info"
82//! connection_pool_size = 5
83//! ```
84//!
85//! # Environment Variables
86//!
87//! The following environment variables are supported:
88//!
89//! - `DATABASE_URL` - Database connection URL
90//! - `HAMMERWORK_DEFAULT_QUEUE` - Default queue name
91//! - `HAMMERWORK_DEFAULT_LIMIT` - Default limit for list operations
92//! - `HAMMERWORK_LOG_LEVEL` - Logging level (error, warn, info, debug, trace)
93//! - `HAMMERWORK_POOL_SIZE` - Database connection pool size
94
95use anyhow::Result;
96use serde::{Deserialize, Serialize};
97use std::env;
98use std::fs;
99use std::path::PathBuf;
100
101/// CLI configuration structure.
102///
103/// This struct holds all configuration options for the Hammerwork CLI.
104/// Fields are optional to allow partial configuration and merging from multiple sources.
105///
106/// # Examples
107///
108/// ```rust
109/// use cargo_hammerwork::config::Config;
110///
111/// // Create configuration with defaults
112/// let config = Config::default();
113/// assert_eq!(config.get_default_limit(), 50);
114/// assert_eq!(config.get_log_level(), "info");
115/// assert_eq!(config.get_connection_pool_size(), 5);
116/// ```
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct Config {
119    /// Database connection URL (e.g., "postgresql://localhost/hammerwork")
120    pub database_url: Option<String>,
121
122    /// Default queue name for operations
123    pub default_queue: Option<String>,
124
125    /// Default limit for list operations
126    pub default_limit: Option<u32>,
127
128    /// Log level (error, warn, info, debug, trace)
129    pub log_level: Option<String>,
130
131    /// Database connection pool size
132    pub connection_pool_size: Option<u32>,
133}
134
135impl Default for Config {
136    fn default() -> Self {
137        Self {
138            database_url: None,
139            default_queue: None,
140            default_limit: Some(50),
141            log_level: Some("info".to_string()),
142            connection_pool_size: Some(5),
143        }
144    }
145}
146
147impl Config {
148    /// Load configuration from file and environment variables.
149    ///
150    /// Configuration is loaded with the following precedence:
151    /// 1. Environment variables (highest priority)
152    /// 2. Configuration file at `~/.config/hammerwork/config.toml`
153    /// 3. Default values (lowest priority)
154    ///
155    /// # Examples
156    ///
157    /// ```rust,no_run
158    /// use cargo_hammerwork::config::Config;
159    ///
160    /// let config = Config::load().expect("Failed to load configuration");
161    /// println!("Database URL: {:?}", config.database_url);
162    /// ```
163    pub fn load() -> Result<Self> {
164        let mut config = Self::default();
165
166        // Try to load from config file
167        if let Ok(config_content) = Self::load_from_file() {
168            config = config_content;
169        }
170
171        // Override with environment variables
172        if let Ok(db_url) = env::var("DATABASE_URL") {
173            config.database_url = Some(db_url);
174        }
175
176        if let Ok(queue) = env::var("HAMMERWORK_DEFAULT_QUEUE") {
177            config.default_queue = Some(queue);
178        }
179
180        if let Ok(limit) = env::var("HAMMERWORK_DEFAULT_LIMIT") {
181            if let Ok(limit_num) = limit.parse() {
182                config.default_limit = Some(limit_num);
183            }
184        }
185
186        if let Ok(log_level) = env::var("HAMMERWORK_LOG_LEVEL") {
187            config.log_level = Some(log_level);
188        }
189
190        if let Ok(pool_size) = env::var("HAMMERWORK_POOL_SIZE") {
191            if let Ok(size_num) = pool_size.parse() {
192                config.connection_pool_size = Some(size_num);
193            }
194        }
195
196        Ok(config)
197    }
198
199    fn load_from_file() -> Result<Self> {
200        let config_path = Self::config_file_path()?;
201        let content = fs::read_to_string(config_path)?;
202        let config: Config = toml::from_str(&content)?;
203        Ok(config)
204    }
205
206    /// Save configuration to file.
207    ///
208    /// The configuration is saved to the platform-specific location:
209    /// - Linux/Mac: `~/.config/hammerwork/config.toml`
210    /// - Windows: `%APPDATA%\hammerwork\config.toml`
211    ///
212    /// # Examples
213    ///
214    /// ```rust,no_run
215    /// use cargo_hammerwork::config::Config;
216    ///
217    /// let mut config = Config::default();
218    /// config.database_url = Some("postgresql://localhost/hammerwork".to_string());
219    /// config.save().expect("Failed to save configuration");
220    /// ```
221    pub fn save(&self) -> Result<()> {
222        let config_path = Self::config_file_path()?;
223
224        // Create parent directory if it doesn't exist
225        if let Some(parent) = config_path.parent() {
226            fs::create_dir_all(parent)?;
227        }
228
229        let content = toml::to_string_pretty(self)?;
230        fs::write(config_path, content)?;
231        Ok(())
232    }
233
234    fn config_file_path() -> Result<PathBuf> {
235        let mut path = dirs::config_dir()
236            .or_else(dirs::home_dir)
237            .ok_or_else(|| anyhow::anyhow!("Cannot find config directory"))?;
238
239        path.push("hammerwork");
240        path.push("config.toml");
241        Ok(path)
242    }
243
244    pub fn get_database_url(&self) -> Option<&str> {
245        self.database_url.as_deref()
246    }
247
248    pub fn get_default_queue(&self) -> Option<&str> {
249        self.default_queue.as_deref()
250    }
251
252    pub fn get_default_limit(&self) -> u32 {
253        self.default_limit.unwrap_or(50)
254    }
255
256    pub fn get_log_level(&self) -> &str {
257        self.log_level.as_deref().unwrap_or("info")
258    }
259
260    pub fn get_connection_pool_size(&self) -> u32 {
261        self.connection_pool_size.unwrap_or(5)
262    }
263}