251 lines
6.5 KiB
Rust
251 lines
6.5 KiB
Rust
//! Worker Configuration Module - TOML-based configuration for Hero workers
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use std::time::Duration;
|
|
|
|
/// Worker configuration loaded from TOML file
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WorkerConfig {
|
|
/// Worker identification
|
|
pub worker_id: String,
|
|
|
|
/// Redis connection URL
|
|
pub redis_url: String,
|
|
|
|
/// Database path for Rhai engine
|
|
pub db_path: String,
|
|
|
|
/// Whether to preserve task details after completion
|
|
#[serde(default = "default_preserve_tasks")]
|
|
pub preserve_tasks: bool,
|
|
|
|
/// Worker type configuration
|
|
pub worker_type: WorkerType,
|
|
|
|
/// Logging configuration
|
|
#[serde(default)]
|
|
pub logging: LoggingConfig,
|
|
}
|
|
|
|
/// Worker type configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum WorkerType {
|
|
/// Synchronous worker configuration
|
|
#[serde(rename = "sync")]
|
|
Sync,
|
|
|
|
/// Asynchronous worker configuration
|
|
#[serde(rename = "async")]
|
|
Async {
|
|
/// Default timeout for jobs in seconds
|
|
#[serde(default = "default_timeout_seconds")]
|
|
default_timeout_seconds: u64,
|
|
},
|
|
}
|
|
|
|
/// Logging configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LoggingConfig {
|
|
/// Whether to include timestamps in log output
|
|
#[serde(default = "default_timestamps")]
|
|
pub timestamps: bool,
|
|
|
|
/// Log level (trace, debug, info, warn, error)
|
|
#[serde(default = "default_log_level")]
|
|
pub level: String,
|
|
}
|
|
|
|
impl Default for LoggingConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
timestamps: default_timestamps(),
|
|
level: default_log_level(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WorkerConfig {
|
|
/// Load configuration from TOML file
|
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
|
|
let content = fs::read_to_string(&path)
|
|
.map_err(|e| ConfigError::IoError(format!("Failed to read config file: {}", e)))?;
|
|
|
|
let config: WorkerConfig = toml::from_str(&content)
|
|
.map_err(|e| ConfigError::ParseError(format!("Failed to parse TOML: {}", e)))?;
|
|
|
|
config.validate()?;
|
|
Ok(config)
|
|
}
|
|
|
|
/// Validate the configuration
|
|
fn validate(&self) -> Result<(), ConfigError> {
|
|
if self.worker_id.is_empty() {
|
|
return Err(ConfigError::ValidationError("worker_id cannot be empty".to_string()));
|
|
}
|
|
|
|
if self.redis_url.is_empty() {
|
|
return Err(ConfigError::ValidationError("redis_url cannot be empty".to_string()));
|
|
}
|
|
|
|
if self.db_path.is_empty() {
|
|
return Err(ConfigError::ValidationError("db_path cannot be empty".to_string()));
|
|
}
|
|
|
|
// Validate log level
|
|
match self.logging.level.to_lowercase().as_str() {
|
|
"trace" | "debug" | "info" | "warn" | "error" => {},
|
|
_ => return Err(ConfigError::ValidationError(
|
|
format!("Invalid log level: {}. Must be one of: trace, debug, info, warn, error", self.logging.level)
|
|
)),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the default timeout duration for async workers
|
|
pub fn get_default_timeout(&self) -> Option<Duration> {
|
|
match &self.worker_type {
|
|
WorkerType::Sync => None,
|
|
WorkerType::Async { default_timeout_seconds } => {
|
|
Some(Duration::from_secs(*default_timeout_seconds))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if this is a sync worker configuration
|
|
pub fn is_sync(&self) -> bool {
|
|
matches!(self.worker_type, WorkerType::Sync)
|
|
}
|
|
|
|
/// Check if this is an async worker configuration
|
|
pub fn is_async(&self) -> bool {
|
|
matches!(self.worker_type, WorkerType::Async { .. })
|
|
}
|
|
}
|
|
|
|
/// Configuration error types
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ConfigError {
|
|
#[error("IO error: {0}")]
|
|
IoError(String),
|
|
|
|
#[error("Parse error: {0}")]
|
|
ParseError(String),
|
|
|
|
#[error("Validation error: {0}")]
|
|
ValidationError(String),
|
|
}
|
|
|
|
// Default value functions for serde
|
|
fn default_preserve_tasks() -> bool {
|
|
false
|
|
}
|
|
|
|
fn default_timeout_seconds() -> u64 {
|
|
300 // 5 minutes
|
|
}
|
|
|
|
fn default_timestamps() -> bool {
|
|
true
|
|
}
|
|
|
|
fn default_log_level() -> String {
|
|
"info".to_string()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use tempfile::NamedTempFile;
|
|
|
|
#[test]
|
|
fn test_sync_worker_config() {
|
|
let config_toml = r#"
|
|
worker_id = "sync_worker_1"
|
|
redis_url = "redis://localhost:6379"
|
|
db_path = "/tmp/worker_db"
|
|
|
|
[worker_type]
|
|
type = "sync"
|
|
|
|
[logging]
|
|
timestamps = false
|
|
level = "debug"
|
|
"#;
|
|
|
|
let config: WorkerConfig = toml::from_str(config_toml).unwrap();
|
|
assert_eq!(config.worker_id, "sync_worker_1");
|
|
assert!(config.is_sync());
|
|
assert!(!config.is_async());
|
|
assert_eq!(config.get_default_timeout(), None);
|
|
assert!(!config.logging.timestamps);
|
|
assert_eq!(config.logging.level, "debug");
|
|
}
|
|
|
|
#[test]
|
|
fn test_async_worker_config() {
|
|
let config_toml = r#"
|
|
worker_id = "async_worker_1"
|
|
redis_url = "redis://localhost:6379"
|
|
db_path = "/tmp/worker_db"
|
|
|
|
[worker_type]
|
|
type = "async"
|
|
default_timeout_seconds = 600
|
|
|
|
[logging]
|
|
timestamps = true
|
|
level = "info"
|
|
"#;
|
|
|
|
let config: WorkerConfig = toml::from_str(config_toml).unwrap();
|
|
assert_eq!(config.worker_id, "async_worker_1");
|
|
assert!(!config.is_sync());
|
|
assert!(config.is_async());
|
|
assert_eq!(config.get_default_timeout(), Some(Duration::from_secs(600)));
|
|
assert!(config.logging.timestamps);
|
|
assert_eq!(config.logging.level, "info");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_from_file() {
|
|
let config_toml = r#"
|
|
worker_id = "test_worker"
|
|
redis_url = "redis://localhost:6379"
|
|
db_path = "/tmp/test_db"
|
|
|
|
[worker_type]
|
|
type = "sync"
|
|
"#;
|
|
|
|
let mut temp_file = NamedTempFile::new().unwrap();
|
|
temp_file.write_all(config_toml.as_bytes()).unwrap();
|
|
|
|
let config = WorkerConfig::from_file(temp_file.path()).unwrap();
|
|
assert_eq!(config.worker_id, "test_worker");
|
|
assert!(config.is_sync());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_validation() {
|
|
let config_toml = r#"
|
|
worker_id = ""
|
|
redis_url = "redis://localhost:6379"
|
|
db_path = "/tmp/test_db"
|
|
|
|
[worker_type]
|
|
type = "sync"
|
|
"#;
|
|
|
|
let result: Result<WorkerConfig, _> = toml::from_str(config_toml);
|
|
assert!(result.is_ok());
|
|
|
|
let config = result.unwrap();
|
|
assert!(config.validate().is_err());
|
|
}
|
|
}
|