1use http::uri::{self, Uri};
6
7pub trait HttpUriExt {
8 fn extend_dir_with_path(self, path: &str) -> Result<Uri, Error>;
15
16 fn append_query_parameter(self, key: &str, value: &str) -> Result<Uri, Error>;
21}
22
23impl HttpUriExt for Uri {
24 fn extend_dir_with_path(self, path: &str) -> Result<Uri, Error> {
25 if path.is_empty() {
26 return Ok(self);
27 }
28 let mut base_parts = self.into_parts();
29 let (base_path, query) = match &base_parts.path_and_query {
30 Some(path_and_query) => (path_and_query.path(), path_and_query.query()),
31 None => ("/", None),
32 };
33 let new_path_and_query = if base_path.ends_with("/") {
34 if let Some(query) = query {
35 format!("{}{}?{}", base_path, path, query)
36 } else {
37 format!("{}{}", base_path, path)
38 }
39 } else {
40 if let Some(query) = query {
41 format!("{}/{}?{}", base_path, path, query)
42 } else {
43 format!("{}/{}", base_path, path)
44 }
45 };
46 base_parts.path_and_query = Some(new_path_and_query.parse()?);
47 Ok(Uri::from_parts(base_parts)?)
48 }
49
50 fn append_query_parameter(self, key: &str, value: &str) -> Result<Uri, Error> {
51 let mut base_parts = self.into_parts();
52 let new_path_and_query = match &base_parts.path_and_query {
53 Some(path_and_query) => {
54 if let Some(query) = path_and_query.query() {
55 format!("{}?{query}&{key}={value}", path_and_query.path())
56 } else {
57 format!("{}?{key}={value}", path_and_query.path())
58 }
59 }
60 None => format!("?{key}={value}"),
61 };
62 base_parts.path_and_query = Some(new_path_and_query.parse()?);
63 Ok(Uri::from_parts(base_parts)?)
64 }
65}
66
67#[derive(Debug, thiserror::Error)]
68pub enum Error {
69 #[error("invalid uri: {0}")]
70 InvalidUri(#[from] uri::InvalidUri),
71 #[error("invalid uri parts: {0}")]
72 InvalidUriParts(#[from] uri::InvalidUriParts),
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn make_uri_from_path_and_query(path_and_query: Option<&str>) -> Uri {
80 let mut parts = uri::Parts::default();
81 parts.path_and_query = path_and_query.map(|p| p.parse().unwrap());
82 Uri::from_parts(parts).unwrap()
83 }
84
85 fn assert_expected_path(base: Option<&str>, added: &str, expected: Option<&str>) {
86 let uri = make_uri_from_path_and_query(base).extend_dir_with_path(added).unwrap();
87 assert_eq!(
88 uri.into_parts().path_and_query.map(|p| p.to_string()),
89 expected.map(|s| s.to_string())
90 );
91 }
92
93 #[test]
94 fn no_query_empty_argument() {
95 assert_expected_path(None, "", None);
96 assert_expected_path(Some(""), "", None);
97 assert_expected_path(Some("/"), "", Some("/"));
98 assert_expected_path(Some("/a"), "", Some("/a"));
99 assert_expected_path(Some("/a/"), "", Some("/a/"));
100 }
101
102 #[test]
103 fn has_query_empty_argument() {
104 assert_expected_path(Some("?k=v"), "", Some("/?k=v"));
105 assert_expected_path(Some("/?k=v"), "", Some("/?k=v"));
106 assert_expected_path(Some("/a?k=v"), "", Some("/a?k=v"));
107 assert_expected_path(Some("/a/?k=v"), "", Some("/a/?k=v"));
108 }
109
110 #[test]
111 fn no_query_has_argument() {
112 assert_expected_path(None, "c", Some("/c"));
113 assert_expected_path(Some(""), "c", Some("/c"));
114 assert_expected_path(Some("/"), "c", Some("/c"));
115 assert_expected_path(Some("/a"), "c", Some("/a/c"));
116 assert_expected_path(Some("/a/"), "c", Some("/a/c"));
117 }
118
119 #[test]
120 fn has_query_has_argument() {
121 assert_expected_path(Some("?k=v"), "c", Some("/c?k=v"));
122 assert_expected_path(Some("/?k=v"), "c", Some("/c?k=v"));
123 assert_expected_path(Some("/a?k=v"), "c", Some("/a/c?k=v"));
124 assert_expected_path(Some("/a/?k=v"), "c", Some("/a/c?k=v"));
125 }
126
127 fn assert_expected_param(base: Option<&str>, key: &str, value: &str, expected: Option<&str>) {
128 let uri = make_uri_from_path_and_query(base).append_query_parameter(key, value).unwrap();
129 assert_eq!(
130 uri.into_parts().path_and_query.map(|p| p.to_string()),
131 expected.map(|s| s.to_string())
132 );
133 }
134
135 #[test]
136 fn new_query() {
137 assert_expected_param(None, "k", "v", Some("/?k=v"));
138 assert_expected_param(Some(""), "k", "v", Some("/?k=v"));
139 assert_expected_param(Some("/"), "k", "v", Some("/?k=v"));
140 assert_expected_param(Some("/a"), "k", "v", Some("/a?k=v"));
141 assert_expected_param(Some("/a/"), "k", "v", Some("/a/?k=v"));
142 }
143
144 #[test]
145 fn append_query() {
146 assert_expected_param(Some("?k=v"), "k2", "v2", Some("/?k=v&k2=v2"));
147 assert_expected_param(Some("/?k=v"), "k2", "v2", Some("/?k=v&k2=v2"));
148 assert_expected_param(Some("/a?k=v"), "k2", "v2", Some("/a?k=v&k2=v2"));
149 assert_expected_param(Some("/a/?k=v"), "k2", "v2", Some("/a/?k=v&k2=v2"));
150 }
151}