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