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