dm_database_sqllog2db/
config.rs1use 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}