Skip to main content

dm_database_sqllog2db/
config.rs

1use crate::constants::LOG_LEVELS;
2use crate::error::{ConfigError, Error, Result};
3pub use crate::features::FeaturesConfig;
4#[cfg(feature = "filters")]
5#[allow(unused_imports)]
6pub use crate::features::FiltersFeature;
7use serde::Deserialize;
8use std::path::Path;
9
10#[cfg_attr(feature = "csv", derive(Default))]
11#[derive(Debug, Deserialize, Clone)]
12pub struct Config {
13    #[serde(default)]
14    pub sqllog: SqllogConfig,
15    #[serde(default)]
16    pub error: ErrorConfig,
17    #[serde(default)]
18    pub logging: LoggingConfig,
19    #[serde(default)]
20    #[cfg_attr(not(feature = "filters"), allow(dead_code))]
21    pub features: FeaturesConfig,
22    #[serde(default)]
23    pub exporter: ExporterConfig,
24}
25
26impl Config {
27    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
28        let path = path.as_ref();
29        let content = std::fs::read_to_string(path)
30            .map_err(|_| Error::Config(ConfigError::NotFound(path.to_path_buf())))?;
31        toml::from_str(&content).map_err(|e| {
32            Error::Config(ConfigError::ParseFailed {
33                path: path.to_path_buf(),
34                reason: e.to_string(),
35            })
36        })
37    }
38
39    pub fn validate(&self) -> Result<()> {
40        self.logging.validate()?;
41        self.exporter.validate()?;
42        self.sqllog.validate()?;
43        FeaturesConfig::validate();
44        Ok(())
45    }
46}
47
48#[derive(Debug, Deserialize, Clone)]
49pub struct SqllogConfig {
50    pub directory: String,
51}
52
53impl Default for SqllogConfig {
54    fn default() -> Self {
55        Self {
56            directory: "sqllogs".to_string(),
57        }
58    }
59}
60
61impl SqllogConfig {
62    pub fn validate(&self) -> Result<()> {
63        if self.directory.trim().is_empty() {
64            return Err(Error::Config(ConfigError::InvalidValue {
65                field: "sqllog.directory".to_string(),
66                value: self.directory.clone(),
67                reason: "Input directory cannot be empty".to_string(),
68            }));
69        }
70        Ok(())
71    }
72}
73
74#[derive(Debug, Deserialize, Clone)]
75pub struct ErrorConfig {
76    #[serde(default = "default_error_file")]
77    pub file: String,
78}
79
80fn default_error_file() -> String {
81    "export/errors.log".to_string()
82}
83
84impl Default for ErrorConfig {
85    fn default() -> Self {
86        Self {
87            file: "export/errors.log".to_string(),
88        }
89    }
90}
91
92#[derive(Debug, Deserialize, Clone)]
93pub struct LoggingConfig {
94    #[serde(default = "default_logging_file")]
95    pub file: String,
96    #[serde(default = "default_logging_level")]
97    pub level: String,
98    #[serde(default = "default_retention_days")]
99    pub retention_days: usize,
100}
101
102fn default_logging_file() -> String {
103    "logs/sqllog2db.log".to_string()
104}
105fn default_logging_level() -> String {
106    "info".to_string()
107}
108fn default_retention_days() -> usize {
109    7
110}
111
112impl Default for LoggingConfig {
113    fn default() -> Self {
114        Self {
115            file: "logs/sqllog2db.log".to_string(),
116            level: "info".to_string(),
117            retention_days: 7,
118        }
119    }
120}
121
122impl LoggingConfig {
123    pub fn validate(&self) -> Result<()> {
124        if !LOG_LEVELS
125            .iter()
126            .any(|&l| l.eq_ignore_ascii_case(&self.level))
127        {
128            return Err(Error::Config(ConfigError::InvalidLogLevel {
129                level: self.level.clone(),
130                valid_levels: LOG_LEVELS.iter().map(|s| (*s).to_string()).collect(),
131            }));
132        }
133        if self.retention_days == 0 || self.retention_days > 365 {
134            return Err(Error::Config(ConfigError::InvalidValue {
135                field: "logging.retention_days".to_string(),
136                value: self.retention_days.to_string(),
137                reason: "Retention days must be between 1 and 365".to_string(),
138            }));
139        }
140        Ok(())
141    }
142}
143
144#[derive(Debug, Deserialize, Clone)]
145pub struct ExporterConfig {
146    #[cfg(feature = "csv")]
147    pub csv: Option<CsvExporter>,
148    #[cfg(feature = "jsonl")]
149    pub jsonl: Option<JsonlExporter>,
150    #[cfg(feature = "sqlite")]
151    pub sqlite: Option<SqliteExporter>,
152}
153
154impl ExporterConfig {
155    fn has_any(&self) -> bool {
156        #[cfg(feature = "csv")]
157        if self.csv.is_some() {
158            return true;
159        }
160        #[cfg(feature = "jsonl")]
161        if self.jsonl.is_some() {
162            return true;
163        }
164        #[cfg(feature = "sqlite")]
165        if self.sqlite.is_some() {
166            return true;
167        }
168        false
169    }
170
171    pub fn validate(&self) -> Result<()> {
172        if !self.has_any() {
173            return Err(Error::Config(ConfigError::NoExporters));
174        }
175        Ok(())
176    }
177}
178
179impl Default for ExporterConfig {
180    fn default() -> Self {
181        Self {
182            #[cfg(feature = "csv")]
183            csv: Some(CsvExporter::default()),
184            #[cfg(feature = "jsonl")]
185            jsonl: None,
186            #[cfg(feature = "sqlite")]
187            sqlite: None,
188        }
189    }
190}
191
192#[cfg(feature = "csv")]
193#[derive(Debug, Deserialize, Clone)]
194pub struct CsvExporter {
195    pub file: String,
196    #[serde(default = "default_true")]
197    pub overwrite: bool,
198    #[serde(default)]
199    pub append: bool,
200}
201
202#[cfg(feature = "csv")]
203impl Default for CsvExporter {
204    fn default() -> Self {
205        Self {
206            file: "outputs/sqllog.csv".to_string(),
207            overwrite: true,
208            append: false,
209        }
210    }
211}
212
213#[cfg(feature = "jsonl")]
214#[derive(Debug, Deserialize, Clone)]
215pub struct JsonlExporter {
216    pub file: String,
217    #[serde(default = "default_true")]
218    pub overwrite: bool,
219    #[serde(default)]
220    pub append: bool,
221}
222
223#[cfg(feature = "jsonl")]
224impl Default for JsonlExporter {
225    fn default() -> Self {
226        Self {
227            file: "export/sqllog2db.jsonl".to_string(),
228            overwrite: true,
229            append: false,
230        }
231    }
232}
233
234#[cfg(feature = "sqlite")]
235#[derive(Debug, Deserialize, Clone)]
236pub struct SqliteExporter {
237    pub database_url: String,
238    #[serde(default = "default_table_name")]
239    pub table_name: String,
240    #[serde(default = "default_true")]
241    pub overwrite: bool,
242    #[serde(default)]
243    pub append: bool,
244}
245
246#[cfg(feature = "sqlite")]
247fn default_table_name() -> String {
248    "sqllog_records".to_string()
249}
250
251#[cfg(feature = "sqlite")]
252impl Default for SqliteExporter {
253    fn default() -> Self {
254        Self {
255            database_url: "export/sqllog2db.db".to_string(),
256            table_name: "sqllog_records".to_string(),
257            overwrite: true,
258            append: false,
259        }
260    }
261}
262
263fn default_true() -> bool {
264    true
265}