dm_database_sqllog2db/
config.rs1use crate::constants::LOG_LEVELS;
2use crate::error::{ConfigError, Error, Result};
3pub use crate::features::{FeaturesConfig, FiltersFeature};
4use serde::Deserialize;
5use std::path::{Path, PathBuf};
6
7#[cfg(feature = "sqlite")]
9fn default_table_name() -> String {
10 "sqllog_records".to_string()
11}
12
13fn default_true() -> bool {
15 true
16}
17
18#[cfg_attr(feature = "csv", derive(Default))]
19#[derive(Debug, Deserialize, Clone)]
20pub struct Config {
21 #[serde(default)]
23 pub sqllog: SqllogConfig,
24 #[serde(default)]
25 pub error: ErrorConfig,
26 #[serde(default)]
27 pub logging: LoggingConfig,
28 #[serde(default)]
29 pub features: FeaturesConfig,
30 #[serde(default)]
31 pub exporter: ExporterConfig,
32}
33
34impl Config {
35 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
37 let path = path.as_ref();
38 let content = std::fs::read_to_string(path)
39 .map_err(|_| Error::Config(ConfigError::NotFound(path.to_path_buf())))?;
40 Self::from_str(&content, path.to_path_buf())
41 }
42
43 pub fn from_str(content: &str, path: PathBuf) -> Result<Self> {
45 let config: Config = toml::from_str(content).map_err(|e| {
46 Error::Config(ConfigError::ParseFailed {
47 path,
48 reason: e.to_string(),
49 })
50 })?;
51
52 config.validate()?;
54
55 Ok(config)
56 }
57
58 pub fn validate(&self) -> Result<()> {
60 self.logging.validate()?;
62
63 self.exporter.validate()?;
65
66 self.sqllog.validate()?;
68
69 FeaturesConfig::validate();
71
72 Ok(())
73 }
74}
75
76#[derive(Debug, Deserialize, Clone)]
78pub struct SqllogConfig {
79 pub directory: String,
81}
82
83impl Default for SqllogConfig {
84 fn default() -> Self {
85 Self {
86 directory: "sqllogs".to_string(),
87 }
88 }
89}
90
91impl SqllogConfig {
92 #[must_use]
94 pub fn directory(&self) -> &str {
95 &self.directory
96 }
97
98 pub fn validate(&self) -> Result<()> {
100 if self.directory.trim().is_empty() {
101 return Err(Error::Config(ConfigError::InvalidValue {
102 field: "sqllog.directory".to_string(),
103 value: self.directory.clone(),
104 reason: "Input directory cannot be empty".to_string(),
105 }));
106 }
107 Ok(())
108 }
109}
110
111#[derive(Debug, Deserialize, Clone)]
112pub struct ErrorConfig {
113 #[serde(default = "default_error_file")]
115 pub file: String,
116}
117
118fn default_error_file() -> String {
119 "export/errors.log".to_string()
120}
121
122impl ErrorConfig {
123 #[must_use]
125 pub fn file(&self) -> &str {
126 &self.file
127 }
128}
129
130impl Default for ErrorConfig {
131 fn default() -> Self {
132 Self {
133 file: "export/errors.log".to_string(),
134 }
135 }
136}
137
138#[derive(Debug, Deserialize, Clone)]
139pub struct LoggingConfig {
140 #[serde(default = "default_logging_file")]
142 pub file: String,
143 #[serde(default = "default_logging_level")]
144 pub level: String,
145 #[serde(default = "default_retention_days")]
146 pub retention_days: usize,
147}
148
149fn default_logging_file() -> String {
150 "logs/sqllog2db.log".to_string()
151}
152
153fn default_logging_level() -> String {
154 "info".to_string()
155}
156
157fn default_retention_days() -> usize {
158 7
159}
160
161impl LoggingConfig {
162 #[must_use]
164 pub fn file(&self) -> &str {
165 &self.file
166 }
167
168 #[must_use]
170 pub fn level(&self) -> &str {
171 &self.level
172 }
173
174 #[must_use]
176 pub fn retention_days(&self) -> usize {
177 self.retention_days
178 }
179
180 pub fn validate(&self) -> Result<()> {
182 if !LOG_LEVELS
183 .iter()
184 .any(|&l| l.eq_ignore_ascii_case(self.level.as_str()))
185 {
186 return Err(Error::Config(ConfigError::InvalidLogLevel {
187 level: self.level.clone(),
188 valid_levels: LOG_LEVELS.iter().map(|s| (*s).to_string()).collect(),
189 }));
190 }
191
192 if self.retention_days == 0 || self.retention_days > 365 {
194 return Err(Error::Config(ConfigError::InvalidValue {
195 field: "logging.retention_days".to_string(),
196 value: self.retention_days.to_string(),
197 reason: "Retention days must be between 1 and 365".to_string(),
198 }));
199 }
200
201 Ok(())
202 }
203}
204
205impl Default for LoggingConfig {
206 fn default() -> Self {
207 Self {
208 file: "logs/sqllog2db.log".to_string(),
209 level: "info".to_string(),
210 retention_days: 7,
211 }
212 }
213}
214
215#[derive(Debug, Deserialize, Clone)]
216pub struct ExporterConfig {
217 #[cfg(feature = "csv")]
218 pub csv: Option<CsvExporter>,
219 #[cfg(feature = "jsonl")]
220 pub jsonl: Option<JsonlExporter>,
221 #[cfg(feature = "sqlite")]
222 pub sqlite: Option<SqliteExporter>,
223}
224
225impl ExporterConfig {
226 #[cfg(feature = "csv")]
228 #[must_use]
229 pub fn csv(&self) -> Option<&CsvExporter> {
230 self.csv.as_ref()
231 }
232
233 #[cfg(feature = "jsonl")]
234 #[must_use]
236 pub fn jsonl(&self) -> Option<&JsonlExporter> {
237 self.jsonl.as_ref()
238 }
239
240 #[cfg(feature = "sqlite")]
241 #[must_use]
243 pub fn sqlite(&self) -> Option<&SqliteExporter> {
244 self.sqlite.as_ref()
245 }
246
247 #[must_use]
249 pub fn has_exporters(&self) -> bool {
250 let mut found = false;
251 #[cfg(feature = "csv")]
252 {
253 found = found || self.csv.is_some();
254 }
255 #[cfg(feature = "jsonl")]
256 {
257 found = found || self.jsonl.is_some();
258 }
259 #[cfg(feature = "sqlite")]
260 {
261 found = found || self.sqlite.is_some();
262 }
263 found
264 }
265
266 #[must_use]
268 pub fn total_exporters(&self) -> usize {
269 let mut count = 0;
270 #[cfg(feature = "csv")]
271 {
272 if self.csv.is_some() {
273 count += 1;
274 }
275 }
276 #[cfg(feature = "jsonl")]
277 {
278 if self.jsonl.is_some() {
279 count += 1;
280 }
281 }
282 #[cfg(feature = "sqlite")]
283 {
284 if self.sqlite.is_some() {
285 count += 1;
286 }
287 }
288 count
289 }
290
291 pub fn validate(&self) -> Result<()> {
293 if !self.has_exporters() {
294 return Err(Error::Config(ConfigError::NoExporters));
295 }
296
297 let total = self.total_exporters();
298 if total > 1 {
299 eprintln!("Warning: {total} exporters configured, but only one is supported.");
300 eprintln!("Will use the first exporter by priority: CSV > JSONL > SQLite");
301 }
302
303 Ok(())
304 }
305}
306
307impl Default for ExporterConfig {
308 fn default() -> Self {
309 Self {
310 #[cfg(feature = "csv")]
311 csv: Some(CsvExporter::default()),
312 #[cfg(feature = "jsonl")]
313 jsonl: None,
314 #[cfg(feature = "sqlite")]
315 sqlite: None,
316 }
317 }
318}
319
320#[cfg(feature = "jsonl")]
321#[derive(Debug, Deserialize, Clone)]
322pub struct JsonlExporter {
323 pub file: String,
325 #[serde(default = "default_true")]
327 pub overwrite: bool,
328 #[serde(default)]
330 pub append: bool,
331}
332
333#[cfg(feature = "jsonl")]
334impl Default for JsonlExporter {
335 fn default() -> Self {
336 Self {
337 file: "export/sqllog2db.jsonl".to_string(),
338 overwrite: true,
339 append: false,
340 }
341 }
342}
343
344#[cfg(feature = "sqlite")]
345#[derive(Debug, Deserialize, Clone)]
346pub struct SqliteExporter {
347 pub database_url: String,
349 #[serde(default = "default_table_name")]
351 pub table_name: String,
352 #[serde(default = "default_true")]
354 pub overwrite: bool,
355 #[serde(default)]
357 pub append: bool,
358}
359
360#[cfg(feature = "sqlite")]
361impl Default for SqliteExporter {
362 fn default() -> Self {
363 Self {
364 database_url: "export/sqllog2db.db".to_string(),
365 table_name: "sqllog_records".to_string(),
366 overwrite: true,
367 append: false,
368 }
369 }
370}
371
372#[cfg(feature = "csv")]
373#[derive(Debug, Deserialize, Clone)]
374pub struct CsvExporter {
375 pub file: String,
377 #[serde(default = "default_true")]
379 pub overwrite: bool,
380 #[serde(default)]
382 pub append: bool,
383}
384
385#[cfg(feature = "csv")]
386impl Default for CsvExporter {
387 fn default() -> Self {
388 Self {
389 file: "outputs/sqllog.csv".to_string(),
390 overwrite: true,
391 append: false,
392 }
393 }
394}