A library for ATProtocol identities.
1//! URL construction utilities leveraging the `url` crate.
2//!
3//! Provides helpers for building URLs and appending query parameters
4//! without manual string concatenation.
5
6use url::{ParseError, Url};
7
8/// Builds a URL from the provided components.
9/// Returns `Result<Url, ParseError>` to surface parsing errors.
10pub fn build_url<K, V, I>(host: &str, path: &str, params: I) -> Result<Url, ParseError>
11where
12 I: IntoIterator<Item = (K, V)>,
13 K: AsRef<str>,
14 V: AsRef<str>,
15{
16 let mut base = if host.starts_with("http://") || host.starts_with("https://") {
17 Url::parse(host)?
18 } else {
19 Url::parse(&format!("https://{}", host))?
20 };
21
22 if !base.path().ends_with('/') {
23 let mut new_path = base.path().to_string();
24 if !new_path.ends_with('/') {
25 new_path.push('/');
26 }
27 if new_path.is_empty() {
28 new_path.push('/');
29 }
30 base.set_path(&new_path);
31 }
32
33 let mut url = base.join(path.trim_start_matches('/'))?;
34 {
35 let mut pairs = url.query_pairs_mut();
36 for (key, value) in params {
37 pairs.append_pair(key.as_ref(), value.as_ref());
38 }
39 }
40 Ok(url)
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn builds_url_with_params() {
49 let url = build_url(
50 "example.com/api",
51 "resource",
52 [("id", "123"), ("status", "active")],
53 )
54 .expect("url build failed");
55
56 assert_eq!(
57 url.as_str(),
58 "https://example.com/api/resource?id=123&status=active"
59 );
60 }
61}