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