1use clap::Parser;
2
3use crate::{LogDestinationConfig, LoggingConfig};
4
5#[allow(missing_docs)]
7#[derive(Parser, Debug)]
8pub struct LogArgs {
9 #[arg(long, value_parser=parse_destination_config)]
30 #[clap(verbatim_doc_comment)]
31 pub log: Vec<Option<LogDestinationConfig>>,
32}
33
34fn parse_destination_config(input: &str) -> Result<Option<LogDestinationConfig>, String> {
35 crate::parser::parse_config_definition(input).map_err(|err| err.to_string())
36}
37
38impl LogArgs {
39 pub fn or_default(&self, default: LoggingConfig) -> LoggingConfig {
42 if self.log.is_empty() {
43 default
45 } else {
46 let destinations = self.log.iter().filter_map(|log| log.clone()).collect();
49 LoggingConfig::new(destinations)
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 mod parse_destination_config {
59 use crate::LogDestination;
60
61 use super::*;
62
63 #[test]
64 fn empty_string() {
65 assert_eq!(
66 parse_destination_config(""),
67 Err(
68 "Invalid empty log destination. Choose stderr, syslog, file, or none"
69 .to_string()
70 )
71 );
72 }
73
74 #[test]
75 fn none() {
76 assert_eq!(parse_destination_config("none"), Ok(None));
77 }
78
79 #[test]
80 fn stderr() {
81 assert_eq!(
82 parse_destination_config("stderr"),
83 Ok(Some(LogDestinationConfig {
84 destination: LogDestination::Stderr,
85 level: None
86 }))
87 );
88 }
89
90 #[test]
91 fn stderr_with_level() {
92 assert_eq!(
93 parse_destination_config("DEBUG:stderr"),
94 Ok(Some(LogDestinationConfig {
95 destination: LogDestination::Stderr,
96 level: Some(log::LevelFilter::Debug)
97 }))
98 );
99 }
100 }
101
102 mod or_default {
103 use crate::LogDestination;
104
105 use super::*;
106
107 #[test]
108 fn no_flags_present_chooses_default() {
109 let args = LogArgs { log: vec![] };
110 let default = vec![LogDestinationConfig {
111 destination: LogDestination::Stderr,
112 level: Some(log::LevelFilter::Info),
113 }];
114 let parsed = args.or_default(LoggingConfig::new(default.clone()));
115 assert_eq!(default, parsed.destinations());
116 }
117
118 #[test]
119 fn none_flag_present() {
120 let args = LogArgs { log: vec![None] };
121 let parsed = args.or_default(LoggingConfig::new(vec![LogDestinationConfig {
122 destination: LogDestination::Stderr,
123 level: Some(log::LevelFilter::Info),
124 }]));
125 assert_eq!(parsed.destinations().len(), 0);
126 }
127
128 #[test]
129 fn one_flag_present() {
130 let destinations = vec![LogDestinationConfig {
131 destination: LogDestination::Stderr,
132 level: Some(log::LevelFilter::Info),
133 }];
134 let args = LogArgs {
135 log: destinations.iter().cloned().map(Some).collect(),
136 };
137 let parsed = args.or_default(LoggingConfig::new(vec![]));
138 assert_eq!(destinations, parsed.destinations());
139 }
140
141 #[test]
142 fn two_flags_present() {
143 let destinations = vec![
144 LogDestinationConfig {
145 destination: LogDestination::Stderr,
146 level: Some(log::LevelFilter::Info),
147 },
148 LogDestinationConfig {
149 destination: LogDestination::File(std::path::PathBuf::from("/tmp/logfile")),
150 level: Some(log::LevelFilter::Debug),
151 },
152 ];
153 let args = LogArgs {
154 log: destinations.iter().cloned().map(Some).collect(),
155 };
156 let parsed = args.or_default(LoggingConfig::new(vec![]));
157 assert_eq!(destinations, parsed.destinations());
158 }
159
160 #[test]
161 fn two_flags_with_one_none_present() {
162 let first_flag = LogDestinationConfig {
163 destination: LogDestination::Stderr,
164 level: Some(log::LevelFilter::Info),
165 };
166 let destinations = vec![Some(first_flag.clone()), None];
167 let args = LogArgs { log: destinations };
168 let parsed = args.or_default(LoggingConfig::new(vec![]));
169 assert_eq!(vec![first_flag], parsed.destinations());
170 }
171 }
172}