Skip to content

Commit 4667c49

Browse files
committed
Adds support for working with URL Paths
It is sometimes useful to parse just the path portion of a URL (path, query string and fragment) rather than the entire URL.
1 parent 58eeb07 commit 4667c49

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed

src/libextra/url.rs

+115
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ pub struct Url {
5555
fragment: Option<~str>
5656
}
5757

58+
#[deriving(Clone, Eq)]
59+
pub struct Path {
60+
/// The path component of a URL, for example `/foo/bar`.
61+
path: ~str,
62+
/// The query component of a URL. `~[(~"baz", ~"qux")]` represents the
63+
/// fragment `baz=qux` in the above example.
64+
query: Query,
65+
/// The fragment component, such as `quz`. Doesn't include the leading `#` character.
66+
fragment: Option<~str>
67+
}
68+
5869
/// An optional subcomponent of a URI authority component.
5970
#[deriving(Clone, Eq)]
6071
pub struct UserInfo {
@@ -88,6 +99,19 @@ impl Url {
8899
}
89100
}
90101

102+
impl Path {
103+
pub fn new(path: ~str,
104+
query: Query,
105+
fragment: Option<~str>)
106+
-> Path {
107+
Path {
108+
path: path,
109+
query: query,
110+
fragment: fragment,
111+
}
112+
}
113+
}
114+
91115
impl UserInfo {
92116
#[inline]
93117
pub fn new(user: ~str, pass: Option<~str>) -> UserInfo {
@@ -695,6 +719,21 @@ pub fn from_str(rawurl: &str) -> Result<Url, ~str> {
695719
Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
696720
}
697721

722+
pub fn path_from_str(rawpath: &str) -> Result<Path, ~str> {
723+
let (path, rest) = match get_path(rawpath, false) {
724+
Ok(val) => val,
725+
Err(e) => return Err(e)
726+
};
727+
728+
// query and fragment
729+
let (query, fragment) = match get_query_fragment(rest) {
730+
Ok(val) => val,
731+
Err(e) => return Err(e),
732+
};
733+
734+
Ok(Path{ path: path, query: query, fragment: fragment })
735+
}
736+
698737
impl FromStr for Url {
699738
fn from_str(s: &str) -> Option<Url> {
700739
match from_str(s) {
@@ -704,6 +743,15 @@ impl FromStr for Url {
704743
}
705744
}
706745

746+
impl FromStr for Path {
747+
fn from_str(s: &str) -> Option<Path> {
748+
match path_from_str(s) {
749+
Ok(path) => Some(path),
750+
Err(_) => None
751+
}
752+
}
753+
}
754+
707755
/**
708756
* Format a `url` as a string
709757
*
@@ -749,18 +797,45 @@ pub fn to_str(url: &Url) -> ~str {
749797
format!("{}:{}{}{}{}", url.scheme, authority, url.path, query, fragment)
750798
}
751799

800+
pub fn path_to_str(path: &Path) -> ~str {
801+
let query = if path.query.is_empty() {
802+
~""
803+
} else {
804+
format!("?{}", query_to_str(&path.query))
805+
};
806+
807+
let fragment = match path.fragment {
808+
Some(ref fragment) => format!("\\#{}", encode_component(*fragment)),
809+
None => ~"",
810+
};
811+
812+
format!("{}{}{}", path.path, query, fragment)
813+
}
814+
752815
impl ToStr for Url {
753816
fn to_str(&self) -> ~str {
754817
to_str(self)
755818
}
756819
}
757820

821+
impl ToStr for Path {
822+
fn to_str(&self) -> ~str {
823+
path_to_str(self)
824+
}
825+
}
826+
758827
impl IterBytes for Url {
759828
fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool {
760829
self.to_str().iter_bytes(lsb0, f)
761830
}
762831
}
763832

833+
impl IterBytes for Path {
834+
fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) -> bool {
835+
self.to_str().iter_bytes(lsb0, f)
836+
}
837+
}
838+
764839
// Put a few tests outside of the 'test' module so they can test the internal
765840
// functions and those functions don't need 'pub'
766841

@@ -868,6 +943,17 @@ mod tests {
868943
assert_eq!(&u.fragment, &Some(~"something"));
869944
}
870945
946+
#[test]
947+
fn test_path_parse() {
948+
let path = ~"/doc/~u?s=v#something";
949+
950+
let up = path_from_str(path);
951+
let u = up.unwrap();
952+
assert_eq!(&u.path, &~"/doc/~u");
953+
assert_eq!(&u.query, &~[(~"s", ~"v")]);
954+
assert_eq!(&u.fragment, &Some(~"something"));
955+
}
956+
871957
#[test]
872958
fn test_url_parse_host_slash() {
873959
let urlstr = ~"http://0.42.42.42/";
@@ -876,6 +962,13 @@ mod tests {
876962
assert!(url.path == ~"/");
877963
}
878964
965+
#[test]
966+
fn test_path_parse_host_slash() {
967+
let pathstr = ~"/";
968+
let path = path_from_str(pathstr).unwrap();
969+
assert!(path.path == ~"/");
970+
}
971+
879972
#[test]
880973
fn test_url_host_with_port() {
881974
let urlstr = ~"scheme://host:1234";
@@ -899,13 +992,27 @@ mod tests {
899992
assert!(url.path == ~"/file_name.html");
900993
}
901994
995+
#[test]
996+
fn test_path_with_underscores() {
997+
let pathstr = ~"/file_name.html";
998+
let path = path_from_str(pathstr).unwrap();
999+
assert!(path.path == ~"/file_name.html");
1000+
}
1001+
9021002
#[test]
9031003
fn test_url_with_dashes() {
9041004
let urlstr = ~"http://dotcom.com/file-name.html";
9051005
let url = from_str(urlstr).unwrap();
9061006
assert!(url.path == ~"/file-name.html");
9071007
}
9081008
1009+
#[test]
1010+
fn test_path_with_dashes() {
1011+
let pathstr = ~"/file-name.html";
1012+
let path = path_from_str(pathstr).unwrap();
1013+
assert!(path.path == ~"/file-name.html");
1014+
}
1015+
9091016
#[test]
9101017
fn test_no_scheme() {
9111018
assert!(get_scheme("noschemehere.html").is_err());
@@ -986,6 +1093,14 @@ mod tests {
9861093
assert!(u.query == ~[(~"ba%d ", ~"#&+")]);
9871094
}
9881095
1096+
#[test]
1097+
fn test_path_component_encoding() {
1098+
let path = ~"/doc%20uments?ba%25d%20=%23%26%2B";
1099+
let p = path_from_str(path).unwrap();
1100+
assert!(p.path == ~"/doc uments");
1101+
assert!(p.query == ~[(~"ba%d ", ~"#&+")]);
1102+
}
1103+
9891104
#[test]
9901105
fn test_url_without_authority() {
9911106
let url = ~"mailto:test@email.com";

0 commit comments

Comments
 (0)