Skip to main content

kellnr_settings/
log.rs

1use std::fmt::Display;
2
3use clap::ValueEnum;
4use serde::{Deserialize, Deserializer, Serialize};
5
6use crate::deserialize_with::DeserializeWith;
7
8#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
9#[serde(default)]
10pub struct Log {
11    #[serde(deserialize_with = "LogFormat::deserialize_with")]
12    pub format: LogFormat,
13    #[serde(deserialize_with = "LogLevel::deserialize_with")]
14    pub level: LogLevel,
15    #[serde(deserialize_with = "LogLevel::deserialize_with")]
16    pub level_web_server: LogLevel,
17}
18
19impl Default for Log {
20    fn default() -> Self {
21        Self {
22            format: LogFormat::Compact,
23            level: LogLevel::Info,
24            level_web_server: LogLevel::Warn,
25        }
26    }
27}
28
29#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
30pub enum LogFormat {
31    Compact,
32    Pretty,
33    Json,
34}
35
36impl DeserializeWith for LogFormat {
37    fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
38    where
39        D: Deserializer<'de>,
40    {
41        let s = String::deserialize(de)?.to_lowercase();
42
43        match s.as_ref() {
44            "compact" => Ok(LogFormat::Compact),
45            "pretty" => Ok(LogFormat::Pretty),
46            "json" => Ok(LogFormat::Json),
47            _ => Err(serde::de::Error::custom(
48                "error trying to deserialize log format: {s}",
49            )),
50        }
51    }
52}
53
54impl Serialize for LogFormat {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: serde::Serializer,
58    {
59        serializer.serialize_str(&self.to_string())
60    }
61}
62
63impl Display for LogFormat {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        match self {
66            LogFormat::Compact => write!(f, "compact"),
67            LogFormat::Pretty => write!(f, "pretty"),
68            LogFormat::Json => write!(f, "json"),
69        }
70    }
71}
72
73#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum)]
74pub enum LogLevel {
75    Trace,
76    Debug,
77    Info,
78    Warn,
79    Error,
80}
81
82impl Display for LogLevel {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            LogLevel::Trace => write!(f, "trace"),
86            LogLevel::Debug => write!(f, "debug"),
87            LogLevel::Info => write!(f, "info"),
88            LogLevel::Warn => write!(f, "warn"),
89            LogLevel::Error => write!(f, "error"),
90        }
91    }
92}
93
94impl Serialize for LogLevel {
95    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
96    where
97        S: serde::Serializer,
98    {
99        serializer.serialize_str(&self.to_string())
100    }
101}
102
103impl DeserializeWith for LogLevel {
104    fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
105    where
106        D: Deserializer<'de>,
107    {
108        let s = String::deserialize(de)?.to_lowercase();
109
110        match s.as_ref() {
111            "trace" => Ok(LogLevel::Trace),
112            "debug" => Ok(LogLevel::Debug),
113            "info" => Ok(LogLevel::Info),
114            "warn" => Ok(LogLevel::Warn),
115            "error" => Ok(LogLevel::Error),
116            _ => Err(serde::de::Error::custom(
117                "error trying to deserialize log level: {s}",
118            )),
119        }
120    }
121}
122
123impl From<LogLevel> for tracing::Level {
124    fn from(value: LogLevel) -> Self {
125        match value {
126            LogLevel::Trace => tracing::Level::TRACE,
127            LogLevel::Debug => tracing::Level::DEBUG,
128            LogLevel::Info => tracing::Level::INFO,
129            LogLevel::Warn => tracing::Level::WARN,
130            LogLevel::Error => tracing::Level::ERROR,
131        }
132    }
133}
134
135impl From<LogLevel> for tracing::level_filters::LevelFilter {
136    fn from(value: LogLevel) -> Self {
137        Self::from_level(value.into())
138    }
139}
140
141#[cfg(test)]
142mod log_format_tests {
143    use serde::Deserialize;
144
145    use super::*;
146
147    #[derive(Debug, Deserialize)]
148    struct Settings {
149        #[serde(deserialize_with = "LogFormat::deserialize_with")]
150        log_format: LogFormat,
151    }
152
153    #[test]
154    fn test_deserialize_log_format_compact() {
155        let toml = r#"
156            log_format = "compact"
157        "#;
158
159        let settings: Settings = toml::from_str(toml).unwrap();
160        assert_eq!(settings.log_format, LogFormat::Compact);
161    }
162
163    #[test]
164    fn test_deserialize_log_format_pretty() {
165        let toml = r#"
166            log_format = "pretty"
167        "#;
168
169        let settings: Settings = toml::from_str(toml).unwrap();
170        assert_eq!(settings.log_format, LogFormat::Pretty);
171    }
172
173    #[test]
174    fn test_deserialize_log_format_json() {
175        let toml = r#"
176            log_format = "json"
177        "#;
178
179        let settings: Settings = toml::from_str(toml).unwrap();
180        assert_eq!(settings.log_format, LogFormat::Json);
181    }
182
183    #[test]
184    fn test_deserialize_log_format_invalid() {
185        let toml = r#"
186        log_level = "no_log_format"
187        "#;
188
189        let settings: Result<Settings, toml::de::Error> = toml::from_str(toml);
190        assert!(settings.is_err());
191    }
192}
193
194#[cfg(test)]
195mod log_level_tests {
196    use serde::Deserialize;
197
198    use super::*;
199
200    #[derive(Debug, Deserialize)]
201    struct Settings {
202        #[serde(deserialize_with = "LogLevel::deserialize_with")]
203        log_level: LogLevel,
204    }
205
206    #[test]
207    fn test_deserialize_log_level_trace() {
208        let toml = r#"
209            log_level = "trace"
210        "#;
211
212        let settings: Settings = toml::from_str(toml).unwrap();
213        assert_eq!(settings.log_level, LogLevel::Trace);
214    }
215
216    #[test]
217    fn test_deserialize_log_level_debug() {
218        let toml = r#"
219            log_level = "debug"
220        "#;
221
222        let settings: Settings = toml::from_str(toml).unwrap();
223        assert_eq!(settings.log_level, LogLevel::Debug);
224    }
225
226    #[test]
227    fn test_deserialize_log_level_info() {
228        let toml = r#"
229            log_level = "info"
230        "#;
231
232        let settings: Settings = toml::from_str(toml).unwrap();
233        assert_eq!(settings.log_level, LogLevel::Info);
234    }
235
236    #[test]
237    fn test_deserialize_log_level_warn() {
238        let toml = r#"
239            log_level = "warn"
240        "#;
241
242        let settings: Settings = toml::from_str(toml).unwrap();
243        assert_eq!(settings.log_level, LogLevel::Warn);
244    }
245
246    #[test]
247    fn test_deserialize_log_level_error() {
248        let toml = r#"
249            log_level = "error"
250        "#;
251
252        let settings: Settings = toml::from_str(toml).unwrap();
253        assert_eq!(settings.log_level, LogLevel::Error);
254    }
255
256    #[test]
257    fn test_deserialize_log_level_uppercase() {
258        let toml = r#"
259        log_level = "DEBUG"
260        "#;
261
262        let settings: Settings = toml::from_str(toml).unwrap();
263        assert_eq!(settings.log_level, LogLevel::Debug);
264    }
265
266    #[test]
267    fn test_deserialize_log_level_invalid() {
268        let toml = r#"
269        log_level = "no_log_level"
270        "#;
271
272        let settings: Result<Settings, toml::de::Error> = toml::from_str(toml);
273        assert!(settings.is_err());
274    }
275}
276
277#[cfg(test)]
278mod log_tests {
279    use super::*;
280
281    #[derive(Debug, Deserialize)]
282    struct Settings {
283        pub log: Log,
284    }
285
286    #[test]
287    fn test_deserialize_whole_log() {
288        let toml = r#"
289        [no_log]
290        foo = "bar"
291
292        [log]
293        level = "trace"
294        level_web_server = "debug"
295        format = "compact"
296        "#;
297
298        let settings: Settings = toml::from_str(toml).unwrap();
299        assert_eq!(settings.log.level, LogLevel::Trace);
300        assert_eq!(settings.log.level_web_server, LogLevel::Debug);
301        assert_eq!(settings.log.format, LogFormat::Compact);
302    }
303}