diff --git a/url/src/lib.rs b/url/src/lib.rs index 1959a1213..a42c7f83a 100644 --- a/url/src/lib.rs +++ b/url/src/lib.rs @@ -2637,6 +2637,40 @@ impl Url { Err(()) } + /// Append path segments to the path of a Url, escaping if necesary. + /// + /// This differs from Url::join in that it is insensitive to trailing slashes + /// in the url and leading slashes in the passed string. See documentation of Url::join for discussion + /// of this subtlety. Also, this function cannot change any part of the Url other than the path. + /// + /// Examples: + /// + /// ``` + /// # use url::Url; + /// let mut my_url = Url::parse("http://www.example.com/api/v1").unwrap(); + /// my_url.append_path("system/status").unwrap(); + /// assert_eq!(my_url.as_str(), "http://www.example.com/api/v1/system/status"); + /// ``` + /// + /// Fails if the Url is cannot-be-a-base. + #[allow(clippy::result_unit_err)] + #[inline] + pub fn append_path(&mut self, path: impl AsRef) -> Result<(), ()> { + // This fails if self is cannot-be-a-base but succeeds otherwise. + let mut path_segments_mut = self.path_segments_mut()?; + + // Remove the last segment if it is empty, this makes our code tolerate trailing `/`'s + path_segments_mut.pop_if_empty(); + + // Remove any leading `/` from the path we are appending, this makes our code tolerate leading `/`'s + let path = path.as_ref(); + let path = path.strip_prefix('/').unwrap_or(path); + for segment in path.split('/') { + path_segments_mut.push(segment); + } + Ok(()) + } + // Private helper methods: #[inline] diff --git a/url/tests/unit.rs b/url/tests/unit.rs index afe842beb..4dc757db9 100644 --- a/url/tests/unit.rs +++ b/url/tests/unit.rs @@ -1316,3 +1316,42 @@ fn issue_864() { url.set_path("x"); dbg!(&url); } + +#[test] +/// append_path is an alternative to Url::join addressing issues described in +/// https://github.com/servo/rust-url/issues/333 +fn test_append_path() { + // append_path behaves as expected when path is `/` regardless of trailing & leading slashes + let mut url = Url::parse("http://test.com").unwrap(); + url.append_path("/a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/a/b/c"); + + let mut url = Url::parse("http://test.com").unwrap(); + url.append_path("a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/a/b/c"); + + let mut url = Url::parse("http://test.com/").unwrap(); + url.append_path("/a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/a/b/c"); + + let mut url = Url::parse("http://test.com/").unwrap(); + url.append_path("a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/a/b/c"); + + // append_path behaves as expected when path is `/api/v1` regardless of trailing & leading slashes + let mut url = Url::parse("http://test.com/api/v1").unwrap(); + url.append_path("/a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/api/v1/a/b/c"); + + let mut url = Url::parse("http://test.com/api/v1").unwrap(); + url.append_path("a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/api/v1/a/b/c"); + + let mut url = Url::parse("http://test.com/api/v1/").unwrap(); + url.append_path("/a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/api/v1/a/b/c"); + + let mut url = Url::parse("http://test.com/api/v1/").unwrap(); + url.append_path("a/b/c").unwrap(); + assert_eq!(url.as_str(), "http://test.com/api/v1/a/b/c"); +}