torrust_tracker/console/clients/checker/
config.rs1use std::error::Error;
2use std::fmt;
3
4use reqwest::Url as ServiceUrl;
5use serde::Deserialize;
6
7pub fn parse_from_json(json: &str) -> Result<Configuration, ConfigurationError> {
17 let plain_config: PlainConfiguration = serde_json::from_str(json).map_err(ConfigurationError::JsonParseError)?;
18 Configuration::try_from(plain_config)
19}
20
21#[derive(Deserialize)]
25struct PlainConfiguration {
26 pub udp_trackers: Vec<String>,
27 pub http_trackers: Vec<String>,
28 pub health_checks: Vec<String>,
29}
30
31pub struct Configuration {
33 pub udp_trackers: Vec<ServiceUrl>,
34 pub http_trackers: Vec<ServiceUrl>,
35 pub health_checks: Vec<ServiceUrl>,
36}
37
38#[derive(Debug)]
39pub enum ConfigurationError {
40 JsonParseError(serde_json::Error),
41 InvalidUdpAddress(std::net::AddrParseError),
42 InvalidUrl(url::ParseError),
43}
44
45impl Error for ConfigurationError {}
46
47impl fmt::Display for ConfigurationError {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 ConfigurationError::JsonParseError(e) => write!(f, "JSON parse error: {e}"),
51 ConfigurationError::InvalidUdpAddress(e) => write!(f, "Invalid UDP address: {e}"),
52 ConfigurationError::InvalidUrl(e) => write!(f, "Invalid URL: {e}"),
53 }
54 }
55}
56
57impl TryFrom<PlainConfiguration> for Configuration {
58 type Error = ConfigurationError;
59
60 fn try_from(plain_config: PlainConfiguration) -> Result<Self, Self::Error> {
61 let udp_trackers = plain_config
62 .udp_trackers
63 .into_iter()
64 .map(|s| if s.starts_with("udp://") { s } else { format!("udp://{s}") })
65 .map(|s| s.parse::<ServiceUrl>().map_err(ConfigurationError::InvalidUrl))
66 .collect::<Result<Vec<_>, _>>()?;
67
68 let http_trackers = plain_config
69 .http_trackers
70 .into_iter()
71 .map(|s| s.parse::<ServiceUrl>().map_err(ConfigurationError::InvalidUrl))
72 .collect::<Result<Vec<_>, _>>()?;
73
74 let health_checks = plain_config
75 .health_checks
76 .into_iter()
77 .map(|s| s.parse::<ServiceUrl>().map_err(ConfigurationError::InvalidUrl))
78 .collect::<Result<Vec<_>, _>>()?;
79
80 Ok(Configuration {
81 udp_trackers,
82 http_trackers,
83 health_checks,
84 })
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn configuration_should_be_build_from_plain_serializable_configuration() {
94 let dto = PlainConfiguration {
95 udp_trackers: vec!["udp://127.0.0.1:8080".to_string()],
96 http_trackers: vec!["http://127.0.0.1:8080".to_string()],
97 health_checks: vec!["http://127.0.0.1:8080/health".to_string()],
98 };
99
100 let config = Configuration::try_from(dto).expect("A valid configuration");
101
102 assert_eq!(config.udp_trackers, vec![ServiceUrl::parse("udp://127.0.0.1:8080").unwrap()]);
103
104 assert_eq!(
105 config.http_trackers,
106 vec![ServiceUrl::parse("http://127.0.0.1:8080").unwrap()]
107 );
108
109 assert_eq!(
110 config.health_checks,
111 vec![ServiceUrl::parse("http://127.0.0.1:8080/health").unwrap()]
112 );
113 }
114
115 mod building_configuration_from_plain_configuration_for {
116
117 mod udp_trackers {
118 use crate::console::clients::checker::config::{Configuration, PlainConfiguration, ServiceUrl};
119
120 #[test]
148 fn it_should_fail_when_a_tracker_udp_url_is_invalid() {
149 let plain_config = PlainConfiguration {
150 udp_trackers: vec!["invalid URL".to_string()],
151 http_trackers: vec![],
152 health_checks: vec![],
153 };
154
155 assert!(Configuration::try_from(plain_config).is_err());
156 }
157
158 #[test]
159 fn it_should_add_the_udp_scheme_to_the_udp_url_when_it_is_missing() {
160 let plain_config = PlainConfiguration {
161 udp_trackers: vec!["127.0.0.1:6969".to_string()],
162 http_trackers: vec![],
163 health_checks: vec![],
164 };
165
166 let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
167
168 assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969".parse::<ServiceUrl>().unwrap());
169 }
170
171 #[test]
172 fn it_should_allow_using_domains() {
173 let plain_config = PlainConfiguration {
174 udp_trackers: vec!["udp://localhost:6969".to_string()],
175 http_trackers: vec![],
176 health_checks: vec![],
177 };
178
179 let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
180
181 assert_eq!(config.udp_trackers[0], "udp://localhost:6969".parse::<ServiceUrl>().unwrap());
182 }
183
184 #[test]
185 fn it_should_allow_the_url_to_have_an_empty_path() {
186 let plain_config = PlainConfiguration {
187 udp_trackers: vec!["127.0.0.1:6969/".to_string()],
188 http_trackers: vec![],
189 health_checks: vec![],
190 };
191
192 let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
193
194 assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969/".parse::<ServiceUrl>().unwrap());
195 }
196
197 #[test]
198 fn it_should_allow_the_url_to_contain_a_path() {
199 let plain_config = PlainConfiguration {
203 udp_trackers: vec!["127.0.0.1:6969/announce".to_string()],
204 http_trackers: vec![],
205 health_checks: vec![],
206 };
207
208 let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
209
210 assert_eq!(
211 config.udp_trackers[0],
212 "udp://127.0.0.1:6969/announce".parse::<ServiceUrl>().unwrap()
213 );
214 }
215 }
216
217 mod http_trackers {
218 use crate::console::clients::checker::config::{Configuration, PlainConfiguration, ServiceUrl};
219
220 #[test]
221 fn it_should_fail_when_a_tracker_http_url_is_invalid() {
222 let plain_config = PlainConfiguration {
223 udp_trackers: vec![],
224 http_trackers: vec!["invalid URL".to_string()],
225 health_checks: vec![],
226 };
227
228 assert!(Configuration::try_from(plain_config).is_err());
229 }
230
231 #[test]
232 fn it_should_allow_the_url_to_contain_a_path() {
233 let plain_config = PlainConfiguration {
237 udp_trackers: vec![],
238 http_trackers: vec!["http://127.0.0.1:7070/announce".to_string()],
239 health_checks: vec![],
240 };
241
242 let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
243
244 assert_eq!(
245 config.http_trackers[0],
246 "http://127.0.0.1:7070/announce".parse::<ServiceUrl>().unwrap()
247 );
248 }
249
250 #[test]
251 fn it_should_allow_the_url_to_contain_an_empty_path() {
252 let plain_config = PlainConfiguration {
253 udp_trackers: vec![],
254 http_trackers: vec!["http://127.0.0.1:7070/".to_string()],
255 health_checks: vec![],
256 };
257
258 let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
259
260 assert_eq!(
261 config.http_trackers[0],
262 "http://127.0.0.1:7070/".parse::<ServiceUrl>().unwrap()
263 );
264 }
265 }
266
267 mod health_checks {
268 use crate::console::clients::checker::config::{Configuration, PlainConfiguration};
269
270 #[test]
271 fn it_should_fail_when_a_health_check_http_url_is_invalid() {
272 let plain_config = PlainConfiguration {
273 udp_trackers: vec![],
274 http_trackers: vec![],
275 health_checks: vec!["invalid URL".to_string()],
276 };
277
278 assert!(Configuration::try_from(plain_config).is_err());
279 }
280 }
281 }
282}