]> git.lizzy.rs Git - rust.git/commitdiff
auto merge of #12231 : wycats/rust/url_path_parse, r=alexcrichton
authorbors <bors@rust-lang.org>
Wed, 19 Feb 2014 20:51:48 +0000 (12:51 -0800)
committerbors <bors@rust-lang.org>
Wed, 19 Feb 2014 20:51:48 +0000 (12:51 -0800)
It is sometimes useful to parse just the path portion of a URL (path,
query string and fragment) rather than the entire URL.

In theory I could have made Url embed a Path, but that would be a
breaking change and I assume that Servo uses this API. I would be
happy to update the PR to embed Path in Url if that's what people
wanted.

src/libextra/url.rs

index c76c73dc4325bcab951ee93374537816927ff10f..4580dd93098831b4f18d9e19e0e073f70fd3c4fa 100644 (file)
@@ -55,6 +55,17 @@ pub struct Url {
     fragment: Option<~str>
 }
 
+#[deriving(Clone, Eq)]
+pub struct Path {
+    /// The path component of a URL, for example `/foo/bar`.
+    path: ~str,
+    /// The query component of a URL.  `~[(~"baz", ~"qux")]` represents the
+    /// fragment `baz=qux` in the above example.
+    query: Query,
+    /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
+    fragment: Option<~str>
+}
+
 /// An optional subcomponent of a URI authority component.
 #[deriving(Clone, Eq)]
 pub struct UserInfo {
@@ -88,6 +99,19 @@ pub fn new(scheme: ~str,
     }
 }
 
+impl Path {
+    pub fn new(path: ~str,
+               query: Query,
+               fragment: Option<~str>)
+               -> Path {
+        Path {
+            path: path,
+            query: query,
+            fragment: fragment,
+        }
+    }
+}
+
 impl UserInfo {
     #[inline]
     pub fn new(user: ~str, pass: Option<~str>) -> UserInfo {
@@ -727,6 +751,21 @@ pub fn from_str(rawurl: &str) -> Result<Url, ~str> {
     Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
 }
 
+pub fn path_from_str(rawpath: &str) -> Result<Path, ~str> {
+    let (path, rest) = match get_path(rawpath, false) {
+        Ok(val) => val,
+        Err(e) => return Err(e)
+    };
+
+    // query and fragment
+    let (query, fragment) = match get_query_fragment(rest) {
+        Ok(val) => val,
+        Err(e) => return Err(e),
+    };
+
+    Ok(Path{ path: path, query: query, fragment: fragment })
+}
+
 impl FromStr for Url {
     fn from_str(s: &str) -> Option<Url> {
         match from_str(s) {
@@ -736,6 +775,15 @@ fn from_str(s: &str) -> Option<Url> {
     }
 }
 
+impl FromStr for Path {
+    fn from_str(s: &str) -> Option<Path> {
+        match path_from_str(s) {
+            Ok(path) => Some(path),
+            Err(_) => None
+        }
+    }
+}
+
 /**
  * Converts a URL from `Url` to string representation.
  *
@@ -780,18 +828,45 @@ pub fn to_str(url: &Url) -> ~str {
     format!("{}:{}{}{}{}", url.scheme, authority, url.path, query, fragment)
 }
 
+pub fn path_to_str(path: &Path) -> ~str {
+    let query = if path.query.is_empty() {
+        ~""
+    } else {
+        format!("?{}", query_to_str(&path.query))
+    };
+
+    let fragment = match path.fragment {
+        Some(ref fragment) => format!("\\#{}", encode_component(*fragment)),
+        None => ~"",
+    };
+
+    format!("{}{}{}", path.path, query, fragment)
+}
+
 impl ToStr for Url {
     fn to_str(&self) -> ~str {
         to_str(self)
     }
 }
 
+impl ToStr for Path {
+    fn to_str(&self) -> ~str {
+        path_to_str(self)
+    }
+}
+
 impl IterBytes for Url {
     fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool {
         self.to_str().iter_bytes(lsb0, f)
     }
 }
 
+impl IterBytes for Path {
+    fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool {
+        self.to_str().iter_bytes(lsb0, f)
+    }
+}
+
 // Put a few tests outside of the 'test' module so they can test the internal
 // functions and those functions don't need 'pub'
 
@@ -899,6 +974,17 @@ fn test_url_parse() {
         assert_eq!(&u.fragment, &Some(~"something"));
     }
 
+    #[test]
+    fn test_path_parse() {
+        let path = ~"/doc/~u?s=v#something";
+
+        let up = path_from_str(path);
+        let u = up.unwrap();
+        assert_eq!(&u.path, &~"/doc/~u");
+        assert_eq!(&u.query, &~[(~"s", ~"v")]);
+        assert_eq!(&u.fragment, &Some(~"something"));
+    }
+
     #[test]
     fn test_url_parse_host_slash() {
         let urlstr = ~"http://0.42.42.42/";
@@ -907,6 +993,13 @@ fn test_url_parse_host_slash() {
         assert!(url.path == ~"/");
     }
 
+    #[test]
+    fn test_path_parse_host_slash() {
+        let pathstr = ~"/";
+        let path = path_from_str(pathstr).unwrap();
+        assert!(path.path == ~"/");
+    }
+
     #[test]
     fn test_url_host_with_port() {
         let urlstr = ~"scheme://host:1234";
@@ -930,6 +1023,13 @@ fn test_url_with_underscores() {
         assert!(url.path == ~"/file_name.html");
     }
 
+    #[test]
+    fn test_path_with_underscores() {
+        let pathstr = ~"/file_name.html";
+        let path = path_from_str(pathstr).unwrap();
+        assert!(path.path == ~"/file_name.html");
+    }
+
     #[test]
     fn test_url_with_dashes() {
         let urlstr = ~"http://dotcom.com/file-name.html";
@@ -937,6 +1037,13 @@ fn test_url_with_dashes() {
         assert!(url.path == ~"/file-name.html");
     }
 
+    #[test]
+    fn test_path_with_dashes() {
+        let pathstr = ~"/file-name.html";
+        let path = path_from_str(pathstr).unwrap();
+        assert!(path.path == ~"/file-name.html");
+    }
+
     #[test]
     fn test_no_scheme() {
         assert!(get_scheme("noschemehere.html").is_err());
@@ -1017,6 +1124,14 @@ fn test_url_component_encoding() {
         assert!(u.query == ~[(~"ba%d ", ~"#&+")]);
     }
 
+    #[test]
+    fn test_path_component_encoding() {
+        let path = ~"/doc%20uments?ba%25d%20=%23%26%2B";
+        let p = path_from_str(path).unwrap();
+        assert!(p.path == ~"/doc uments");
+        assert!(p.query == ~[(~"ba%d ", ~"#&+")]);
+    }
+
     #[test]
     fn test_url_without_authority() {
         let url = ~"mailto:test@email.com";