1use alloc::string::String;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4
5use crate::error::Result;
6use crate::util::{host, percent};
7use crate::{HttpUrl, HttpUrlError, Scheme};
8
9#[derive(Debug, Clone)]
29pub struct HttpUrlBuilder {
30 pub(crate) scheme: Option<Scheme>,
32 pub(crate) username: String,
34 pub(crate) password: String,
36 pub(crate) host: Option<String>,
38 pub(crate) port: Option<u16>,
40 pub(crate) path_segments: Vec<String>,
42 pub(crate) query_names_and_values: Vec<String>,
44 pub(crate) fragment: Option<String>,
46}
47
48impl HttpUrlBuilder {
49 pub fn new() -> Self {
51 Self {
52 scheme: None,
53 username: String::new(),
54 password: String::new(),
55 host: None,
56 port: None,
57 path_segments: Vec::new(),
58 query_names_and_values: Vec::new(),
59 fragment: None,
60 }
61 }
62
63 pub fn scheme(mut self, scheme: Scheme) -> Self {
65 self.scheme = Some(scheme);
66 self
67 }
68
69 pub fn username(mut self, username: &str) -> Self {
71 self.username = username.to_string();
72 self
73 }
74
75 pub fn password(mut self, password: &str) -> Self {
77 self.password = password.to_string();
78 self
79 }
80
81 pub fn host(mut self, host: &str) -> Self {
83 self.host = Some(host.to_string());
84 self
85 }
86
87 pub fn port(mut self, port: u16) -> Self {
89 self.port = Some(port);
90 self
91 }
92
93 pub fn add_path_segment(mut self, segment: &str) -> Self {
95 self.path_segments.push(segment.to_string());
96 self
97 }
98
99 pub fn add_path_segments(mut self, segments: &[&str]) -> Self {
101 for seg in segments {
102 self.path_segments.push(seg.to_string());
103 }
104 self
105 }
106
107 pub fn set_path(mut self, path: &str) -> Self {
110 self.path_segments.clear();
111 let path = path.trim_start_matches('/');
112 for seg in path.split('/') {
113 if !seg.is_empty() {
114 self.path_segments
115 .push(percent::decode(seg).unwrap_or_else(|_| seg.to_string()));
116 }
117 }
118 self
119 }
120
121 pub fn set_encoded_path(mut self, path: &str) -> Self {
124 self.path_segments.clear();
125 let path = path.trim_start_matches('/');
126 for seg in path.split('/') {
127 if !seg.is_empty() {
128 let decoded = percent::decode(seg).unwrap_or_else(|_| seg.to_string());
129 self.path_segments.push(decoded);
130 }
131 }
132 self
133 }
134
135 pub fn add_query_parameter(mut self, name: &str, value: &str) -> Self {
137 self.query_names_and_values.push(name.to_string());
138 self.query_names_and_values.push(value.to_string());
139 self
140 }
141
142 pub fn remove_query_parameter(mut self, name: &str) -> Self {
144 let mut i = 0;
145 while i < self.query_names_and_values.len() {
146 if self.query_names_and_values[i] == name {
147 self.query_names_and_values.remove(i);
148 if i < self.query_names_and_values.len() {
149 self.query_names_and_values.remove(i);
150 }
151 } else {
152 i += 2;
153 }
154 }
155 self
156 }
157
158 pub fn clear_query_parameters(mut self) -> Self {
160 self.query_names_and_values.clear();
161 self
162 }
163
164 pub fn add_encoded_query_parameter(mut self, name: &str, value: &str) -> Self {
166 let decoded_name = percent::decode(name).unwrap_or_else(|_| name.to_string());
167 let decoded_value = percent::decode(value).unwrap_or_else(|_| value.to_string());
168 self.query_names_and_values.push(decoded_name);
169 self.query_names_and_values.push(decoded_value);
170 self
171 }
172
173 pub fn fragment(mut self, fragment: &str) -> Self {
175 self.fragment = Some(fragment.to_string());
176 self
177 }
178
179 pub fn encoded_fragment(mut self, fragment: &str) -> Self {
181 self.fragment = percent::decode(fragment).ok();
182 self
183 }
184
185 pub fn remove_fragment(mut self) -> Self {
187 self.fragment = None;
188 self
189 }
190
191 pub fn build(self) -> Result<HttpUrl> {
195 let scheme = self
196 .scheme
197 .ok_or_else(|| HttpUrlError::BuilderValidation("scheme is required".to_string()))?;
198
199 let host_str = self
200 .host
201 .ok_or_else(|| HttpUrlError::BuilderValidation("host is required".to_string()))?;
202
203 let host = host::canonicalize_host(&host_str)?;
204
205 let port = self.port.unwrap_or_else(|| scheme.default_port());
206
207 Ok(HttpUrl {
208 scheme,
209 username: self.username,
210 password: self.password,
211 host,
212 port,
213 path_segments: self.path_segments,
214 query_names_and_values: self.query_names_and_values,
215 fragment: self.fragment,
216 })
217 }
218}
219
220impl Default for HttpUrlBuilder {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_builder_basic() {
232 let url = HttpUrl::builder()
233 .scheme(Scheme::Https)
234 .host("example.com")
235 .build()
236 .unwrap();
237 assert_eq!(url.to_string(), "https://example.com/");
238 }
239
240 #[test]
241 fn test_builder_full() {
242 let url = HttpUrl::builder()
243 .scheme(Scheme::Https)
244 .username("user")
245 .password("pass")
246 .host("example.com")
247 .port(8080)
248 .add_path_segment("api")
249 .add_path_segment("v1")
250 .add_query_parameter("key", "value")
251 .fragment("section")
252 .build()
253 .unwrap();
254 assert_eq!(
255 url.to_string(),
256 "https://user:pass@example.com:8080/api/v1?key=value#section"
257 );
258 }
259
260 #[test]
261 fn test_builder_path_segments() {
262 let url = HttpUrl::builder()
263 .scheme(Scheme::Http)
264 .host("example.com")
265 .add_path_segment("a")
266 .add_path_segment("b")
267 .add_path_segment("c")
268 .build()
269 .unwrap();
270 assert_eq!(url.path(), "/a/b/c");
271 assert_eq!(url.encoded_path(), "/a/b/c");
272 }
273
274 #[test]
275 fn test_builder_path_encoding() {
276 let url = HttpUrl::builder()
277 .scheme(Scheme::Http)
278 .host("example.com")
279 .add_path_segment("hello world")
280 .build()
281 .unwrap();
282 assert_eq!(url.encoded_path(), "/hello%20world");
283 assert_eq!(url.path(), "/hello world");
284 }
285
286 #[test]
287 fn test_builder_missing_scheme() {
288 let result = HttpUrl::builder().host("example.com").build();
289 assert!(result.is_err());
290 }
291
292 #[test]
293 fn test_builder_missing_host() {
294 let result = HttpUrl::builder().scheme(Scheme::Http).build();
295 assert!(result.is_err());
296 }
297
298 #[test]
299 fn test_unicode_in_builder() {
300 let url = HttpUrl::builder()
301 .scheme(Scheme::Http)
302 .host("example.com")
303 .add_path_segment("\u{4e2d}\u{6587}")
304 .build()
305 .unwrap();
306 assert_eq!(url.path(), "/\u{4e2d}\u{6587}");
307 }
308
309 #[test]
310 fn test_builder_set_path() {
311 let url = HttpUrl::builder()
312 .scheme(Scheme::Http)
313 .host("example.com")
314 .set_path("/a/b/c")
315 .build()
316 .unwrap();
317 assert_eq!(url.path(), "/a/b/c");
318 }
319
320 #[test]
321 fn test_builder_add_path_segments() {
322 let url = HttpUrl::builder()
323 .scheme(Scheme::Http)
324 .host("example.com")
325 .add_path_segments(&["a", "b", "c"])
326 .build()
327 .unwrap();
328 assert_eq!(url.path(), "/a/b/c");
329 }
330
331 #[test]
332 fn test_builder_clear_query_parameters() {
333 let url = HttpUrl::builder()
334 .scheme(Scheme::Http)
335 .host("example.com")
336 .add_query_parameter("a", "1")
337 .clear_query_parameters()
338 .build()
339 .unwrap();
340 assert_eq!(url.query(), None);
341 }
342
343 #[test]
344 fn test_builder_remove_query_parameter() {
345 let url = HttpUrl::builder()
346 .scheme(Scheme::Https)
347 .host("example.com")
348 .add_query_parameter("a", "1")
349 .add_query_parameter("b", "2")
350 .remove_query_parameter("a")
351 .build()
352 .unwrap();
353 assert_eq!(url.query_parameter("a"), None);
354 assert_eq!(url.query_parameter("b"), Some("2"));
355 }
356}