|  | 
| 6 | 6 | #![cfg_attr(not(test), deny(clippy::todo))] | 
| 7 | 7 | #![cfg_attr(not(test), deny(clippy::unimplemented))] | 
| 8 | 8 | 
 | 
|  | 9 | +use http::{uri::PathAndQuery, Uri}; | 
| 9 | 10 | use hyper::{ | 
| 10 | 11 |     header::HeaderValue, | 
| 11 | 12 |     http::uri::{self}, | 
| @@ -229,6 +230,40 @@ impl Endpoint { | 
| 229 | 230 |     /// Default value for the timeout field in milliseconds. | 
| 230 | 231 |     pub const DEFAULT_TIMEOUT: u64 = 3_000; | 
| 231 | 232 | 
 | 
|  | 233 | +    /// Add a path to the url of an `Endpoint`. | 
|  | 234 | +    /// | 
|  | 235 | +    /// The given path must start with a slash (e.g. "/v0.4/traces"). | 
|  | 236 | +    /// Returns an error if the path is not valid. | 
|  | 237 | +    pub fn add_path(&mut self, path: &str) -> anyhow::Result<()> { | 
|  | 238 | +        let mut parts = self.url.clone().into_parts(); | 
|  | 239 | +        parts.path_and_query = Some(match parts.path_and_query { | 
|  | 240 | +            Some(pq) => { | 
|  | 241 | +                let p = pq.path(); | 
|  | 242 | +                let mut p = p.strip_suffix('/').unwrap_or(p).to_owned(); | 
|  | 243 | +                p.push_str(path); | 
|  | 244 | +                if let Some(q) = pq.query() { | 
|  | 245 | +                    p.push('?'); | 
|  | 246 | +                    p.push_str(q); | 
|  | 247 | +                } | 
|  | 248 | +                PathAndQuery::from_str(p.as_str()) | 
|  | 249 | +            } | 
|  | 250 | +            None => PathAndQuery::from_str(path), | 
|  | 251 | +        }?); | 
|  | 252 | +        self.url = Uri::from_parts(parts)?; | 
|  | 253 | +        Ok(()) | 
|  | 254 | +    } | 
|  | 255 | + | 
|  | 256 | +    /// Create a new `Endpoint` by extending the url with the given path. | 
|  | 257 | +    /// | 
|  | 258 | +    /// The given path must start with a slash (e.g. "/v0.4/traces"). | 
|  | 259 | +    /// Returns an error if the path is not valid. | 
|  | 260 | +    /// All the other fields are copied. | 
|  | 261 | +    pub fn try_clone_with_subpath(&self, path: &str) -> anyhow::Result<Self> { | 
|  | 262 | +        let mut endpoint = self.clone(); | 
|  | 263 | +        endpoint.add_path(path)?; | 
|  | 264 | +        Ok(endpoint) | 
|  | 265 | +    } | 
|  | 266 | + | 
| 232 | 267 |     /// Return a request builder with the following headers: | 
| 233 | 268 |     /// - User agent | 
| 234 | 269 |     /// - Api key | 
| @@ -286,3 +321,31 @@ impl Endpoint { | 
| 286 | 321 |         } | 
| 287 | 322 |     } | 
| 288 | 323 | } | 
|  | 324 | + | 
|  | 325 | +#[cfg(test)] | 
|  | 326 | +mod tests { | 
|  | 327 | +    use super::*; | 
|  | 328 | + | 
|  | 329 | +    #[test] | 
|  | 330 | +    fn test_add_path() { | 
|  | 331 | +        let test_cases = [ | 
|  | 332 | +            ("http://test.com/", "/foo", "http://test.com/foo"), | 
|  | 333 | +            ("http://test.com/bar", "/foo", "http://test.com/bar/foo"), | 
|  | 334 | +            ( | 
|  | 335 | +                "http://test.com/bar", | 
|  | 336 | +                "/foo/baz", | 
|  | 337 | +                "http://test.com/bar/foo/baz", | 
|  | 338 | +            ), | 
|  | 339 | +            ( | 
|  | 340 | +                "http://test.com/bar?data=dog&product=apm", | 
|  | 341 | +                "/foo/baz", | 
|  | 342 | +                "http://test.com/bar/foo/baz?data=dog&product=apm", | 
|  | 343 | +            ), | 
|  | 344 | +        ]; | 
|  | 345 | +        for (url, path, expected) in test_cases { | 
|  | 346 | +            let mut endpoint = Endpoint::from_url(url.parse().unwrap()); | 
|  | 347 | +            endpoint.add_path(path).unwrap(); | 
|  | 348 | +            assert_eq!(endpoint.url, expected); | 
|  | 349 | +        } | 
|  | 350 | +    } | 
|  | 351 | +} | 
0 commit comments