torrust_tracker/servers/http/v1/
query.rs1use std::panic::Location;
7use std::str::FromStr;
8
9use multimap::MultiMap;
10use thiserror::Error;
11
12type ParamName = String;
13type ParamValue = String;
14
15#[derive(Debug)]
21pub struct Query {
22 params: MultiMap<ParamName, NameValuePair>,
27}
28
29impl Query {
30 #[must_use]
56 pub fn get_param(&self, name: &str) -> Option<String> {
57 self.params.get(name).map(|pair| pair.value.clone())
58 }
59
60 #[must_use]
85 pub fn get_param_vec(&self, name: &str) -> Option<Vec<String>> {
86 self.params.get_vec(name).map(|pairs| {
87 let mut param_values = vec![];
88 for pair in pairs {
89 param_values.push(pair.value.to_string());
90 }
91 param_values
92 })
93 }
94}
95
96#[derive(Error, Debug)]
99pub enum ParseQueryError {
100 #[error("invalid param {raw_param} in {location}")]
103 InvalidParam {
104 location: &'static Location<'static>,
105 raw_param: String,
106 },
107}
108
109impl FromStr for Query {
110 type Err = ParseQueryError;
111
112 fn from_str(raw_query: &str) -> Result<Self, Self::Err> {
113 let mut params: MultiMap<ParamName, NameValuePair> = MultiMap::new();
114
115 let raw_params = raw_query.trim().trim_start_matches('?').split('&').collect::<Vec<&str>>();
116
117 for raw_param in raw_params {
118 let pair: NameValuePair = raw_param.parse()?;
119 let param_name = pair.name.clone();
120 params.insert(param_name, pair);
121 }
122
123 Ok(Self { params })
124 }
125}
126
127impl From<Vec<(&str, &str)>> for Query {
128 fn from(raw_params: Vec<(&str, &str)>) -> Self {
129 let mut params: MultiMap<ParamName, NameValuePair> = MultiMap::new();
130
131 for raw_param in raw_params {
132 params.insert(raw_param.0.to_owned(), NameValuePair::new(raw_param.0, raw_param.1));
133 }
134
135 Self { params }
136 }
137}
138
139impl std::fmt::Display for Query {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 let query = self
142 .params
143 .iter_all()
144 .map(|param| format!("{}", FieldValuePairSet::from_vec(param.1)))
145 .collect::<Vec<String>>()
146 .join("&");
147
148 write!(f, "{query}")
149 }
150}
151
152#[derive(Debug, PartialEq, Clone)]
153struct NameValuePair {
154 name: ParamName,
155 value: ParamValue,
156}
157
158impl NameValuePair {
159 pub fn new(name: &str, value: &str) -> Self {
160 Self {
161 name: name.to_owned(),
162 value: value.to_owned(),
163 }
164 }
165}
166
167impl FromStr for NameValuePair {
168 type Err = ParseQueryError;
169
170 fn from_str(raw_param: &str) -> Result<Self, Self::Err> {
171 let pair = raw_param.split('=').collect::<Vec<&str>>();
172
173 if pair.len() != 2 {
174 return Err(ParseQueryError::InvalidParam {
175 location: Location::caller(),
176 raw_param: raw_param.to_owned(),
177 });
178 }
179
180 Ok(Self {
181 name: pair[0].to_owned(),
182 value: pair[1].to_owned(),
183 })
184 }
185}
186
187impl std::fmt::Display for NameValuePair {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 write!(f, "{}={}", self.name, self.value)
190 }
191}
192
193#[derive(Debug, PartialEq)]
194struct FieldValuePairSet {
195 pairs: Vec<NameValuePair>,
196}
197
198impl FieldValuePairSet {
199 fn from_vec(pair_vec: &Vec<NameValuePair>) -> Self {
200 let mut pairs: Vec<NameValuePair> = vec![];
201
202 for pair in pair_vec {
203 pairs.push(pair.clone());
204 }
205
206 Self { pairs }
207 }
208}
209
210impl std::fmt::Display for FieldValuePairSet {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 let query = self
213 .pairs
214 .iter()
215 .map(|pair| format!("{pair}"))
216 .collect::<Vec<String>>()
217 .join("&");
218
219 write!(f, "{query}")
220 }
221}
222
223#[cfg(test)]
224mod tests {
225
226 mod url_query {
227 use crate::servers::http::v1::query::Query;
228
229 #[test]
230 fn should_parse_the_query_params_from_an_url_query_string() {
231 let raw_query =
232 "info_hash=%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0&peer_id=-qB00000000000000001&port=17548";
233
234 let query = raw_query.parse::<Query>().unwrap();
235
236 assert_eq!(
237 query.get_param("info_hash").unwrap(),
238 "%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0"
239 );
240 assert_eq!(query.get_param("peer_id").unwrap(), "-qB00000000000000001");
241 assert_eq!(query.get_param("port").unwrap(), "17548");
242 }
243
244 #[test]
245 fn should_be_instantiated_from_a_string_pair_vector() {
246 let query = Query::from(vec![("param1", "value1"), ("param2", "value2")]);
247
248 assert_eq!(query.get_param("param1"), Some("value1".to_string()));
249 assert_eq!(query.get_param("param2"), Some("value2".to_string()));
250 }
251
252 #[test]
253 fn should_fail_parsing_an_invalid_query_string() {
254 let invalid_raw_query = "name=value=value";
255
256 let query = invalid_raw_query.parse::<Query>();
257
258 assert!(query.is_err());
259 }
260
261 #[test]
262 fn should_ignore_the_preceding_question_mark_if_it_exists() {
263 let raw_query = "?name=value";
264
265 let query = raw_query.parse::<Query>().unwrap();
266
267 assert_eq!(query.get_param("name"), Some("value".to_string()));
268 }
269
270 #[test]
271 fn should_trim_whitespaces() {
272 let raw_query = " name=value ";
273
274 let query = raw_query.parse::<Query>().unwrap();
275
276 assert_eq!(query.get_param("name"), Some("value".to_string()));
277 }
278
279 mod should_allow_more_than_one_value_for_the_same_param {
280 use crate::servers::http::v1::query::Query;
281
282 #[test]
283 fn instantiated_from_a_vector() {
284 let query1 = Query::from(vec![("param1", "value1"), ("param1", "value2")]);
285 assert_eq!(
286 query1.get_param_vec("param1"),
287 Some(vec!["value1".to_string(), "value2".to_string()])
288 );
289 }
290
291 #[test]
292 fn parsed_from_an_string() {
293 let query2 = "param1=value1¶m1=value2".parse::<Query>().unwrap();
294 assert_eq!(
295 query2.get_param_vec("param1"),
296 Some(vec!["value1".to_string(), "value2".to_string()])
297 );
298 }
299 }
300
301 mod should_be_displayed {
302 use crate::servers::http::v1::query::Query;
303
304 #[test]
305 fn with_one_param() {
306 assert_eq!("param1=value1".parse::<Query>().unwrap().to_string(), "param1=value1");
307 }
308
309 #[test]
310 fn with_multiple_params() {
311 let query = "param1=value1¶m2=value2".parse::<Query>().unwrap().to_string();
312 assert!(query == "param1=value1¶m2=value2" || query == "param2=value2¶m1=value1");
313 }
314
315 #[test]
316 fn with_multiple_values_for_the_same_param() {
317 let query = "param1=value1¶m1=value2".parse::<Query>().unwrap().to_string();
318 assert!(query == "param1=value1¶m1=value2" || query == "param1=value2¶m1=value1");
319 }
320 }
321
322 mod param_name_value_pair {
323 use crate::servers::http::v1::query::NameValuePair;
324
325 #[test]
326 fn should_parse_a_single_query_param() {
327 let raw_param = "name=value";
328
329 let param = raw_param.parse::<NameValuePair>().unwrap();
330
331 assert_eq!(
332 param,
333 NameValuePair {
334 name: "name".to_string(),
335 value: "value".to_string(),
336 }
337 );
338 }
339
340 #[test]
341 fn should_fail_parsing_an_invalid_query_param() {
342 let invalid_raw_param = "name=value=value";
343
344 let query = invalid_raw_param.parse::<NameValuePair>();
345
346 assert!(query.is_err());
347 }
348
349 #[test]
350 fn should_be_displayed() {
351 assert_eq!("name=value".parse::<NameValuePair>().unwrap().to_string(), "name=value");
352 }
353 }
354 }
355}