Skip to content

Commit ec468b2

Browse files
committed
Merge pull request #11 from michaelsproul/formatter-refactor
Refactor URL formatting code into its own module.
2 parents 63e5d0b + 653e685 commit ec468b2

File tree

2 files changed

+189
-49
lines changed

2 files changed

+189
-49
lines changed

src/format.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright 2013-2014 Simon Sapin.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
//! Formatting utilities for URLs.
10+
//!
11+
//! These formatters can be used to coerce various URL parts into strings.
12+
//!
13+
//! You can use `<formatter>.to_string()`, as the formatters implement `Show`.
14+
15+
use std::fmt::{Show, Formatter, FormatError};
16+
use super::Url;
17+
18+
/// Formatter and serializer for URL path data.
19+
pub struct PathFormatter<'a, T> {
20+
/// The path as a slice of string-like objects (String or &str).
21+
pub path: &'a [T]
22+
}
23+
24+
impl<'a, T: Str + Show> Show for PathFormatter<'a, T> {
25+
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
26+
if self.path.is_empty() {
27+
formatter.write(b"/")
28+
} else {
29+
for path_part in self.path.iter() {
30+
try!("/".fmt(formatter));
31+
try!(path_part.fmt(formatter));
32+
}
33+
Ok(())
34+
}
35+
}
36+
}
37+
38+
39+
/// Formatter and serializer for URL username and password data.
40+
pub struct UserInfoFormatter<'a> {
41+
/// URL username as a string slice.
42+
pub username: &'a str,
43+
44+
/// URL password as an optional string slice.
45+
///
46+
/// You can convert an `Option<String>` with `.as_ref().map(|s| s.as_slice())`.
47+
pub password: Option<&'a str>
48+
}
49+
50+
impl<'a> Show for UserInfoFormatter<'a> {
51+
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
52+
if !self.username.is_empty() || self.password.is_some() {
53+
try!(formatter.write(self.username.as_bytes()));
54+
match self.password {
55+
None => (),
56+
Some(password) => {
57+
try!(formatter.write(b":"));
58+
try!(formatter.write(password.as_bytes()));
59+
}
60+
}
61+
try!(formatter.write(b"@"));
62+
}
63+
Ok(())
64+
}
65+
}
66+
67+
68+
/// Formatter for URLs which ignores the fragment field.
69+
pub struct UrlNoFragmentFormatter<'a> {
70+
pub url: &'a Url
71+
}
72+
73+
impl<'a> Show for UrlNoFragmentFormatter<'a> {
74+
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
75+
try!(formatter.write(self.url.scheme.as_bytes()));
76+
try!(formatter.write(b":"));
77+
try!(self.url.scheme_data.fmt(formatter));
78+
match self.url.query {
79+
None => (),
80+
Some(ref query) => {
81+
try!(formatter.write(b"?"));
82+
try!(formatter.write(query.as_bytes()));
83+
}
84+
}
85+
Ok(())
86+
}
87+
}
88+
89+
90+
/// Formatting Tests
91+
#[cfg(test)]
92+
mod tests {
93+
use super::super::Url;
94+
use super::{PathFormatter, UserInfoFormatter};
95+
96+
#[test]
97+
fn path_formatting() {
98+
let data = [
99+
(vec![], "/"),
100+
(vec![""], "/"),
101+
(vec!["test", "path"], "/test/path"),
102+
(vec!["test", "path", ""], "/test/path/")
103+
];
104+
for &(ref path, result) in data.iter() {
105+
assert_eq!(PathFormatter {
106+
path: path.as_slice()
107+
}.to_string(), result.to_string());
108+
}
109+
}
110+
111+
#[test]
112+
fn userinfo_formatting() {
113+
// Test data as (username, password, result) tuples.
114+
let data = [
115+
("", None, ""),
116+
("", Some(""), ":@"),
117+
("", Some("password"), ":password@"),
118+
("username", None, "username@"),
119+
("username", Some(""), "username:@"),
120+
("username", Some("password"), "username:password@")
121+
];
122+
for &(username, password, result) in data.iter() {
123+
assert_eq!(UserInfoFormatter {
124+
username: username,
125+
password: password
126+
}.to_string(), result.to_string());
127+
}
128+
}
129+
130+
#[test]
131+
fn relative_scheme_url_formatting() {
132+
let data = [
133+
("http://example.com/", "http://example.com/"),
134+
("http://addslash.com", "http://addslash.com/"),
135+
("http://@emptyuser.com/", "http://emptyuser.com/"),
136+
("http://:@emptypass.com/", "http://:@emptypass.com/"),
137+
("http://[email protected]/", "http://[email protected]/"),
138+
("http://user:[email protected]/", "http://user:[email protected]/"),
139+
("http://slashquery.com/path/?q=something", "http://slashquery.com/path/?q=something"),
140+
("http://noslashquery.com/path?q=something", "http://noslashquery.com/path?q=something")
141+
];
142+
for &(input, result) in data.iter() {
143+
let url = Url::parse(input).unwrap();
144+
assert_eq!(url.to_string(), result.to_string());
145+
}
146+
}
147+
}

src/lib.rs

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,15 @@ pub use percent_encoding::{
150150
PASSWORD_ENCODE_SET, USERNAME_ENCODE_SET, FORM_URLENCODED_ENCODE_SET, EncodeSet,
151151
};
152152

153+
use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};
153154

154155
mod host;
155156
mod parser;
156157
mod urlutils;
157158
pub mod percent_encoding;
158159
pub mod form_urlencoded;
159160
pub mod punycode;
161+
pub mod format;
160162

161163
#[cfg(test)]
162164
mod tests;
@@ -583,6 +585,14 @@ impl Url {
583585
scheme_data.lossy_percent_decode_password())
584586
}
585587

588+
/// Serialize the URL's username and password, if any.
589+
///
590+
/// Format: "<username>:<password>@"
591+
#[inline]
592+
pub fn serialize_userinfo<'a>(&'a mut self) -> Option<String> {
593+
self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_userinfo())
594+
}
595+
586596
/// If the URL is in a *relative scheme*, return its structured host.
587597
#[inline]
588598
pub fn host<'a>(&'a self) -> Option<&'a Host> {
@@ -698,26 +708,6 @@ impl Show for Url {
698708
}
699709
}
700710

701-
struct UrlNoFragmentFormatter<'a> {
702-
url: &'a Url
703-
}
704-
705-
impl<'a> Show for UrlNoFragmentFormatter<'a> {
706-
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
707-
try!(formatter.write(self.url.scheme.as_bytes()));
708-
try!(formatter.write(b":"));
709-
try!(self.url.scheme_data.fmt(formatter));
710-
match self.url.query {
711-
None => (),
712-
Some(ref query) => {
713-
try!(formatter.write(b"?"));
714-
try!(formatter.write(query.as_bytes()));
715-
}
716-
}
717-
Ok(())
718-
}
719-
}
720-
721711

722712
impl Show for SchemeData {
723713
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
@@ -800,50 +790,49 @@ impl RelativeSchemeData {
800790
/// The returned string starts with a "/" slash, and components are separated by slashes.
801791
/// A trailing slash represents an empty last component.
802792
pub fn serialize_path(&self) -> String {
803-
PathFormatter { path: &self.path }.to_string()
793+
PathFormatter {
794+
path: self.path.as_slice()
795+
}.to_string()
804796
}
805-
}
806-
807-
struct PathFormatter<'a> {
808-
path: &'a Vec<String>
809-
}
810797

811-
impl<'a> Show for PathFormatter<'a> {
812-
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
813-
if self.path.is_empty() {
814-
formatter.write(b"/")
815-
} else {
816-
for path_part in self.path.iter() {
817-
try!(formatter.write(b"/"));
818-
try!(formatter.write(path_part.as_bytes()));
819-
}
820-
Ok(())
821-
}
798+
/// Serialize the userinfo as a string.
799+
///
800+
/// Format: "<username>:<password>@".
801+
pub fn serialize_userinfo(&self) -> String {
802+
UserInfoFormatter {
803+
username: self.username.as_slice(),
804+
password: self.password.as_ref().map(|s| s.as_slice())
805+
}.to_string()
822806
}
823807
}
824808

809+
825810
impl Show for RelativeSchemeData {
826811
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
812+
// Write the scheme-trailing double slashes.
827813
try!(formatter.write(b"//"));
828-
if !self.username.is_empty() || self.password.is_some() {
829-
try!(formatter.write(self.username.as_bytes()));
830-
match self.password {
831-
None => (),
832-
Some(ref password) => {
833-
try!(formatter.write(b":"));
834-
try!(formatter.write(password.as_bytes()));
835-
}
836-
}
837-
try!(formatter.write(b"@"));
838-
}
814+
815+
// Write the user info.
816+
try!(UserInfoFormatter {
817+
username: self.username.as_slice(),
818+
password: self.password.as_ref().map(|s| s.as_slice())
819+
}.fmt(formatter));
820+
821+
// Write the host.
839822
try!(self.host.fmt(formatter));
823+
824+
// Write the port.
840825
match self.port {
841826
Some(port) => {
842827
try!(write!(formatter, ":{}", port));
843828
},
844829
None => {}
845830
}
846-
PathFormatter { path: &self.path }.fmt(formatter)
831+
832+
// Write the path.
833+
PathFormatter {
834+
path: self.path.as_slice()
835+
}.fmt(formatter)
847836
}
848837
}
849838

@@ -852,6 +841,7 @@ trait ToUrlPath {
852841
fn to_url_path(&self) -> Result<Vec<String>, ()>;
853842
}
854843

844+
855845
impl ToUrlPath for path::posix::Path {
856846
fn to_url_path(&self) -> Result<Vec<String>, ()> {
857847
if !self.is_absolute() {
@@ -861,6 +851,7 @@ impl ToUrlPath for path::posix::Path {
861851
}
862852
}
863853

854+
864855
impl ToUrlPath for path::windows::Path {
865856
fn to_url_path(&self) -> Result<Vec<String>, ()> {
866857
if !self.is_absolute() {
@@ -885,6 +876,7 @@ trait FromUrlPath {
885876
fn from_url_path(path: &[String]) -> Result<Self, ()>;
886877
}
887878

879+
888880
impl FromUrlPath for path::posix::Path {
889881
fn from_url_path(path: &[String]) -> Result<path::posix::Path, ()> {
890882
if path.is_empty() {
@@ -906,6 +898,7 @@ impl FromUrlPath for path::posix::Path {
906898
}
907899
}
908900

901+
909902
impl FromUrlPath for path::windows::Path {
910903
fn from_url_path(path: &[String]) -> Result<path::windows::Path, ()> {
911904
if path.is_empty() {

0 commit comments

Comments
 (0)