Skip to content

Commit 5a4d5ea

Browse files
antkmsftCopilotheaths
authored
Add append_path() for Url (Azure#3161)
Usage in codegen: Azure/typespec-rust#635 --------- Co-authored-by: Anton Kolesnyk <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Heath Stewart <[email protected]>
1 parent 8f7f030 commit 5a4d5ea

File tree

5 files changed

+160
-1
lines changed

5 files changed

+160
-1
lines changed

sdk/core/azure_core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added `UrlExt::append_path()`.
8+
79
### Breaking Changes
810

911
- Moved deserializers and serializers for optional base64-encoded bytes to `base64::option` module. `base64` module now deserializes or serializes non-optional fields congruent with the `time` module.

sdk/core/azure_core/src/http/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use response::{AsyncResponse, BufResponse, RawResponse, Response};
2323
pub use typespec_client_core::http::response;
2424
pub use typespec_client_core::http::{
2525
new_http_client, AppendToUrlQuery, Context, DeserializeWith, Format, HttpClient, JsonFormat,
26-
Method, NoFormat, StatusCode, Url,
26+
Method, NoFormat, StatusCode, Url, UrlExt,
2727
};
2828

2929
pub use crate::error::check_success;

sdk/core/typespec_client_core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added `UrlExt::append_path()`.
8+
79
### Breaking Changes
810

911
- Moved deserializers and serializers for optional base64-encoded bytes to `base64::option` module. `base64` module now deserializes or serializes non-optional fields congruent with the `time` module.

sdk/core/typespec_client_core/src/http/mod.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,153 @@ where
5959
}
6060
}
6161
}
62+
63+
/// Extension trait for [`Url`] to provide additional URL manipulation methods.
64+
pub trait UrlExt: crate::private::Sealed {
65+
/// Appends a path segment to the URL's path, handling slashes appropriately and preserving query parameters.
66+
///
67+
/// This always assumes the existing URL terminates with a directory, and the `path` you pass in is a separate directory or file segment.
68+
///
69+
/// # Examples
70+
///
71+
/// ```
72+
/// use typespec_client_core::http::{Url, UrlExt as _};
73+
///
74+
/// let mut url: Url = "https://contoso.com/foo?a=1".parse().unwrap();
75+
/// url.append_path("bar");
76+
/// assert_eq!(url.as_str(), "https://contoso.com/foo/bar?a=1");
77+
/// ```
78+
fn append_path(&mut self, path: impl AsRef<str>);
79+
}
80+
81+
impl UrlExt for Url {
82+
fn append_path(&mut self, p: impl AsRef<str>) {
83+
let path = p.as_ref().trim_start_matches('/');
84+
if self.path() == "/" {
85+
self.set_path(path);
86+
return;
87+
}
88+
if path.is_empty() {
89+
return;
90+
}
91+
let needs_separator = !self.path().ends_with('/');
92+
let mut new_len = self.path().len() + path.len();
93+
if needs_separator {
94+
new_len += 1;
95+
}
96+
let mut new_path = String::with_capacity(new_len);
97+
debug_assert_eq!(new_path.capacity(), new_len);
98+
new_path.push_str(self.path());
99+
if needs_separator {
100+
new_path.push('/');
101+
}
102+
new_path.push_str(path);
103+
debug_assert_eq!(new_path.capacity(), new_len);
104+
105+
self.set_path(&new_path);
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod test {
111+
use super::*;
112+
113+
#[test]
114+
fn url_append_path() {
115+
let mut url = Url::parse("https://www.microsoft.com?q=q").unwrap();
116+
url.append_path("foo");
117+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo?q=q");
118+
119+
url = Url::parse("https://www.microsoft.com/?q=q").unwrap();
120+
url.append_path("foo");
121+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo?q=q");
122+
123+
url = Url::parse("https://www.microsoft.com?q=q").unwrap();
124+
url.append_path("/foo");
125+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo?q=q");
126+
127+
url = Url::parse("https://www.microsoft.com/?q=q").unwrap();
128+
url.append_path("/foo");
129+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo?q=q");
130+
131+
url = Url::parse("https://www.microsoft.com?q=q").unwrap();
132+
url.append_path("foo/");
133+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/?q=q");
134+
135+
url = Url::parse("https://www.microsoft.com/?q=q").unwrap();
136+
url.append_path("foo/");
137+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/?q=q");
138+
139+
url = Url::parse("https://www.microsoft.com?q=q").unwrap();
140+
url.append_path("/foo/");
141+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/?q=q");
142+
143+
url = Url::parse("https://www.microsoft.com/?q=q").unwrap();
144+
url.append_path("/foo/");
145+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/?q=q");
146+
147+
url = Url::parse("https://www.microsoft.com/foo?q=q").unwrap();
148+
url.append_path("bar");
149+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar?q=q");
150+
151+
url = Url::parse("https://www.microsoft.com/foo/?q=q").unwrap();
152+
url.append_path("bar");
153+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar?q=q");
154+
155+
url = Url::parse("https://www.microsoft.com/foo?q=q").unwrap();
156+
url.append_path("/bar");
157+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar?q=q");
158+
159+
url = Url::parse("https://www.microsoft.com/foo/?q=q").unwrap();
160+
url.append_path("/bar");
161+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar?q=q");
162+
163+
url = Url::parse("https://www.microsoft.com/foo?q=q").unwrap();
164+
url.append_path("bar/");
165+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar/?q=q");
166+
167+
url = Url::parse("https://www.microsoft.com/foo/?q=q").unwrap();
168+
url.append_path("bar/");
169+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar/?q=q");
170+
171+
url = Url::parse("https://www.microsoft.com/foo?q=q").unwrap();
172+
url.append_path("/bar/");
173+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar/?q=q");
174+
175+
url = Url::parse("https://www.microsoft.com/foo/?q=q").unwrap();
176+
url.append_path("/bar/");
177+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/bar/?q=q");
178+
179+
url = Url::parse("https://www.microsoft.com?q=q").unwrap();
180+
url.append_path("/");
181+
assert_eq!(url.as_str(), "https://www.microsoft.com/?q=q");
182+
183+
url = Url::parse("https://www.microsoft.com/?q=q").unwrap();
184+
url.append_path("/");
185+
assert_eq!(url.as_str(), "https://www.microsoft.com/?q=q");
186+
187+
url = Url::parse("https://www.microsoft.com?q=q").unwrap();
188+
url.append_path("");
189+
assert_eq!(url.as_str(), "https://www.microsoft.com/?q=q");
190+
191+
url = Url::parse("https://www.microsoft.com?q=q").unwrap();
192+
url.append_path("");
193+
assert_eq!(url.as_str(), "https://www.microsoft.com/?q=q");
194+
195+
url = Url::parse("https://www.microsoft.com/foo?q=q").unwrap();
196+
url.append_path("/");
197+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo?q=q");
198+
199+
url = Url::parse("https://www.microsoft.com/foo/?q=q").unwrap();
200+
url.append_path("/");
201+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/?q=q");
202+
203+
url = Url::parse("https://www.microsoft.com/foo?q=q").unwrap();
204+
url.append_path("");
205+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo?q=q");
206+
207+
url = Url::parse("https://www.microsoft.com/foo/?q=q").unwrap();
208+
url.append_path("");
209+
assert_eq!(url.as_str(), "https://www.microsoft.com/foo/?q=q");
210+
}
211+
}

sdk/core/typespec_client_core/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ pub use typespec::Bytes;
2626
pub use uuid::Uuid;
2727

2828
pub use sleep::sleep;
29+
30+
mod private {
31+
pub trait Sealed {}
32+
impl Sealed for crate::http::Url {}
33+
}

0 commit comments

Comments
 (0)