Skip to main content

rdbi_codegen/config/
settings.rs

1//! Configuration settings for rdbi-codegen
2
3use config::{Config, Environment, File};
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6
7use super::defaults;
8use crate::error::{CodegenError, Result};
9
10/// Main configuration struct for code generation
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CodegenConfig {
13    /// Path to the SQL schema file
14    #[serde(default)]
15    pub schema_file: PathBuf,
16
17    /// Tables to include (comma-separated, or "*" for all)
18    #[serde(default = "default_include_tables")]
19    pub include_tables: String,
20
21    /// Tables to exclude (comma-separated)
22    #[serde(default = "default_exclude_tables")]
23    pub exclude_tables: String,
24
25    /// Whether to generate struct files
26    #[serde(default = "default_generate_structs")]
27    pub generate_structs: bool,
28
29    /// Whether to generate DAO files
30    #[serde(default = "default_generate_dao")]
31    pub generate_dao: bool,
32
33    /// Output directory for generated structs
34    #[serde(default = "default_output_structs_dir")]
35    pub output_structs_dir: PathBuf,
36
37    /// Output directory for generated DAOs
38    #[serde(default = "default_output_dao_dir")]
39    pub output_dao_dir: PathBuf,
40
41    /// Module name for structs
42    #[serde(default = "default_models_module")]
43    pub models_module: String,
44
45    /// Module name for DAOs
46    #[serde(default = "default_dao_module")]
47    pub dao_module: String,
48
49    /// Dry run mode - preview without writing files
50    #[serde(default = "default_dry_run")]
51    pub dry_run: bool,
52
53    /// Log level (trace, debug, info, warn, error)
54    /// Can be overridden by RUST_LOG env var
55    #[serde(default)]
56    pub log_level: Option<String>,
57}
58
59// Default value functions for serde
60fn default_include_tables() -> String {
61    defaults::INCLUDE_TABLES.to_string()
62}
63fn default_exclude_tables() -> String {
64    defaults::EXCLUDE_TABLES.to_string()
65}
66fn default_generate_structs() -> bool {
67    defaults::GENERATE_STRUCTS
68}
69fn default_generate_dao() -> bool {
70    defaults::GENERATE_DAO
71}
72fn default_output_structs_dir() -> PathBuf {
73    PathBuf::from(defaults::OUTPUT_STRUCTS_DIR)
74}
75fn default_output_dao_dir() -> PathBuf {
76    PathBuf::from(defaults::OUTPUT_DAO_DIR)
77}
78fn default_models_module() -> String {
79    defaults::MODELS_MODULE.to_string()
80}
81fn default_dao_module() -> String {
82    defaults::DAO_MODULE.to_string()
83}
84fn default_dry_run() -> bool {
85    defaults::DRY_RUN
86}
87
88impl Default for CodegenConfig {
89    fn default() -> Self {
90        Self {
91            schema_file: PathBuf::new(),
92            include_tables: default_include_tables(),
93            exclude_tables: default_exclude_tables(),
94            generate_structs: default_generate_structs(),
95            generate_dao: default_generate_dao(),
96            output_structs_dir: default_output_structs_dir(),
97            output_dao_dir: default_output_dao_dir(),
98            models_module: default_models_module(),
99            dao_module: default_dao_module(),
100            dry_run: default_dry_run(),
101            log_level: None,
102        }
103    }
104}
105
106impl CodegenConfig {
107    /// Create a default config with the given schema file
108    pub fn default_with_schema(schema_file: PathBuf) -> Self {
109        Self {
110            schema_file,
111            ..Default::default()
112        }
113    }
114
115    /// Load configuration from a TOML file
116    pub fn from_file(path: &Path) -> Result<Self> {
117        let content = std::fs::read_to_string(path)?;
118        let config: CodegenConfig = toml::from_str(&content).map_err(|e| {
119            CodegenError::ConfigError(format!(
120                "Failed to parse config file {}: {}",
121                path.display(),
122                e
123            ))
124        })?;
125        Ok(config)
126    }
127
128    /// Load configuration using config-rs (file + environment variables)
129    pub fn load(config_path: Option<&Path>) -> Result<Self> {
130        let mut builder = Config::builder();
131
132        // Load from config file if specified
133        if let Some(path) = config_path {
134            builder = builder.add_source(File::from(path));
135        } else {
136            // Try default locations
137            builder = builder.add_source(File::with_name("rdbi-codegen").required(false));
138        }
139
140        // Override with environment variables (RDBI_CODEGEN_*)
141        builder = builder.add_source(Environment::with_prefix("RDBI_CODEGEN").separator("_"));
142
143        let config: CodegenConfig = builder.build()?.try_deserialize()?;
144
145        Ok(config)
146    }
147
148    /// Validate the configuration
149    pub fn validate(&self) -> Result<()> {
150        if self.schema_file.as_os_str().is_empty() {
151            return Err(CodegenError::ValidationError(
152                "schema_file is required".into(),
153            ));
154        }
155
156        if !self.schema_file.exists() {
157            return Err(CodegenError::ValidationError(format!(
158                "Schema file not found: {}",
159                self.schema_file.display()
160            )));
161        }
162
163        if self.generate_structs && self.models_module.is_empty() {
164            return Err(CodegenError::ValidationError(
165                "models_module is required when generate_structs is true".into(),
166            ));
167        }
168
169        if self.generate_dao {
170            if !self.generate_structs {
171                return Err(CodegenError::ValidationError(
172                    "generate_structs must be true when generate_dao is true (DAOs depend on structs)".into(),
173                ));
174            }
175            if self.dao_module.is_empty() {
176                return Err(CodegenError::ValidationError(
177                    "dao_module is required when generate_dao is true".into(),
178                ));
179            }
180        }
181
182        Ok(())
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_default_config() {
192        let config = CodegenConfig::default();
193        assert_eq!(config.include_tables, "*");
194        assert!(config.generate_structs);
195        assert!(config.generate_dao);
196        assert!(config.log_level.is_none());
197    }
198
199    #[test]
200    fn test_validation_missing_schema() {
201        let config = CodegenConfig::default();
202        assert!(config.validate().is_err());
203    }
204
205    #[test]
206    fn test_config_with_log_level() {
207        let toml_content = r#"
208            schema_file = "test.sql"
209            log_level = "debug"
210        "#;
211        let config: CodegenConfig = toml::from_str(toml_content).unwrap();
212        assert_eq!(config.log_level, Some("debug".to_string()));
213    }
214}