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}