#[derive(Debug)] pub struct Url<'a> { path_segments: Vec<&'a str>, query_params: QueryParams<'a>, } pub type QueryParam<'a> = (&'a str, &'a str); pub type QueryParams<'a> = Vec>; impl Url<'_> { pub fn parse(url: &str) -> Url { let mut parts = url.splitn(2, '?'); let path = parts.next().unwrap_or_default(); let query = parts.next(); Url { path_segments: extract_path_segments(path), query_params: extract_query_params(query), } } pub fn path_segments(&self) -> &[&str] { self.path_segments.as_slice() } pub fn query_params(&self) -> &QueryParams { &self.query_params } } fn extract_path_segments(path: &str) -> Vec<&str> { path.split('/').skip(1).collect() } fn extract_query_params(query: Option<&str>) -> Vec { query .clone() .map(|q| { q.split('&') .filter_map(|part| { if let [key, val] = part.splitn(2, '=').collect::>()[..] { Some((key, val)) } else { None } }) .collect() }) .unwrap_or_default() } #[cfg(test)] mod tests { use super::*; #[test] fn should_extract_path_segments() { let path = "/hello/world"; let segments = extract_path_segments(path); assert_eq!(segments, ["hello", "world"]); } #[test] fn should_extract_tralling_slash_as_empty_segment() { let path = "/hello/world/"; let segments = extract_path_segments(path); assert_eq!(segments, ["hello", "world", ""]); } #[test] fn should_extract_query_params() { let query_params = Some("lang=rus&keys=apple&keys=banana"); let params = extract_query_params(query_params); assert_eq!( params[..], [("lang", "rus"), ("keys", "apple"), ("keys", "banana")] ); } #[test] fn should_parse_whole_url() { let url = Url::parse("/api/ingredients?lang=rus&keys=apple&keys=banana"); assert_eq!(url.path_segments(), ["api", "ingredients"]); assert_eq!( url.query_params()[..], [("lang", "rus"), ("keys", "apple"), ("keys", "banana")] ) } }