@@ -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+ }
0 commit comments