github_mcp/core/
api_url_builder.rs1use url::Url;
4
5pub fn build_api_url(
9 base_url: &str,
10 path: &str,
11 query: &[(String, String)],
12) -> anyhow::Result<String> {
13 let base = if base_url.ends_with('/') {
14 base_url.to_string()
15 } else {
16 format!("{base_url}/")
17 };
18 let base = Url::parse(&base)?;
19 let mut url = base.join(path.trim_start_matches('/'))?;
20
21 if !query.is_empty() {
25 let mut pairs = url.query_pairs_mut();
26 for (key, value) in query {
27 pairs.append_pair(key, value);
28 }
29 }
30
31 Ok(url.to_string())
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn joins_a_base_url_and_path_without_double_slashes() {
40 let url = build_api_url("https://api.example.com", "/widgets", &[]).unwrap();
41 assert_eq!(url, "https://api.example.com/widgets");
42 }
43
44 #[test]
45 fn handles_a_base_url_without_a_trailing_slash() {
46 let url = build_api_url("https://api.example.com/v1", "widgets", &[]).unwrap();
47 assert_eq!(url, "https://api.example.com/v1/widgets");
48 }
49
50 #[test]
51 fn appends_query_parameters() {
52 let url = build_api_url(
53 "https://api.example.com",
54 "/widgets",
55 &[("limit".to_string(), "10".to_string())],
56 )
57 .unwrap();
58 assert_eq!(url, "https://api.example.com/widgets?limit=10");
59 }
60}