use config::{Config, Environment, File};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use super::defaults;
use crate::error::{CodegenError, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodegenConfig {
#[serde(default)]
pub schema_file: PathBuf,
#[serde(default = "default_include_tables")]
pub include_tables: String,
#[serde(default = "default_exclude_tables")]
pub exclude_tables: String,
#[serde(default = "default_generate_structs")]
pub generate_structs: bool,
#[serde(default = "default_generate_dao")]
pub generate_dao: bool,
#[serde(default = "default_output_structs_dir")]
pub output_structs_dir: PathBuf,
#[serde(default = "default_output_dao_dir")]
pub output_dao_dir: PathBuf,
#[serde(default = "default_models_module")]
pub models_module: String,
#[serde(default = "default_dao_module")]
pub dao_module: String,
#[serde(default = "default_dry_run")]
pub dry_run: bool,
#[serde(default)]
pub log_level: Option<String>,
}
fn default_include_tables() -> String {
defaults::INCLUDE_TABLES.to_string()
}
fn default_exclude_tables() -> String {
defaults::EXCLUDE_TABLES.to_string()
}
fn default_generate_structs() -> bool {
defaults::GENERATE_STRUCTS
}
fn default_generate_dao() -> bool {
defaults::GENERATE_DAO
}
fn default_output_structs_dir() -> PathBuf {
PathBuf::from(defaults::OUTPUT_STRUCTS_DIR)
}
fn default_output_dao_dir() -> PathBuf {
PathBuf::from(defaults::OUTPUT_DAO_DIR)
}
fn default_models_module() -> String {
defaults::MODELS_MODULE.to_string()
}
fn default_dao_module() -> String {
defaults::DAO_MODULE.to_string()
}
fn default_dry_run() -> bool {
defaults::DRY_RUN
}
impl Default for CodegenConfig {
fn default() -> Self {
Self {
schema_file: PathBuf::new(),
include_tables: default_include_tables(),
exclude_tables: default_exclude_tables(),
generate_structs: default_generate_structs(),
generate_dao: default_generate_dao(),
output_structs_dir: default_output_structs_dir(),
output_dao_dir: default_output_dao_dir(),
models_module: default_models_module(),
dao_module: default_dao_module(),
dry_run: default_dry_run(),
log_level: None,
}
}
}
impl CodegenConfig {
pub fn default_with_schema(schema_file: PathBuf) -> Self {
Self {
schema_file,
..Default::default()
}
}
pub fn from_file(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: CodegenConfig = toml::from_str(&content).map_err(|e| {
CodegenError::ConfigError(format!(
"Failed to parse config file {}: {}",
path.display(),
e
))
})?;
Ok(config)
}
pub fn load(config_path: Option<&Path>) -> Result<Self> {
let mut builder = Config::builder();
if let Some(path) = config_path {
builder = builder.add_source(File::from(path));
} else {
builder = builder.add_source(File::with_name("rdbi-codegen").required(false));
}
builder = builder.add_source(Environment::with_prefix("RDBI_CODEGEN").separator("_"));
let config: CodegenConfig = builder.build()?.try_deserialize()?;
Ok(config)
}
pub fn validate(&self) -> Result<()> {
if self.schema_file.as_os_str().is_empty() {
return Err(CodegenError::ValidationError(
"schema_file is required".into(),
));
}
if !self.schema_file.exists() {
return Err(CodegenError::ValidationError(format!(
"Schema file not found: {}",
self.schema_file.display()
)));
}
if self.generate_structs && self.models_module.is_empty() {
return Err(CodegenError::ValidationError(
"models_module is required when generate_structs is true".into(),
));
}
if self.generate_dao {
if !self.generate_structs {
return Err(CodegenError::ValidationError(
"generate_structs must be true when generate_dao is true (DAOs depend on structs)".into(),
));
}
if self.dao_module.is_empty() {
return Err(CodegenError::ValidationError(
"dao_module is required when generate_dao is true".into(),
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = CodegenConfig::default();
assert_eq!(config.include_tables, "*");
assert!(config.generate_structs);
assert!(config.generate_dao);
assert!(config.log_level.is_none());
}
#[test]
fn test_validation_missing_schema() {
let config = CodegenConfig::default();
assert!(config.validate().is_err());
}
#[test]
fn test_config_with_log_level() {
let toml_content = r#"
schema_file = "test.sql"
log_level = "debug"
"#;
let config: CodegenConfig = toml::from_str(toml_content).unwrap();
assert_eq!(config.log_level, Some("debug".to_string()));
}
}