Skip to content

Refactor URL formatting code into its own module. #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions src/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2013-2014 Simon Sapin.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Formatting utilities for URLs.
//!
//! These formatters can be used to coerce various URL parts into strings.
//!
//! You can use `<formatter>.to_string()`, as the formatters implement `Show`.

use std::fmt::{Show, Formatter, FormatError};
use super::Url;

/// Formatter and serializer for URL path data.
pub struct PathFormatter<'a, T> {
/// The path as a slice of string-like objects (String or &str).
pub path: &'a [T]
}

impl<'a, T: Str + Show> Show for PathFormatter<'a, T> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
if self.path.is_empty() {
formatter.write(b"/")
} else {
for path_part in self.path.iter() {
try!("/".fmt(formatter));
try!(path_part.fmt(formatter));
}
Ok(())
}
}
}


/// Formatter and serializer for URL username and password data.
pub struct UserInfoFormatter<'a> {
/// URL username as a string slice.
pub username: &'a str,

/// URL password as an optional string slice.
///
/// You can convert an `Option<String>` with `.as_ref().map(|s| s.as_slice())`.
pub password: Option<&'a str>
}

impl<'a> Show for UserInfoFormatter<'a> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
if !self.username.is_empty() || self.password.is_some() {
try!(formatter.write(self.username.as_bytes()));
match self.password {
None => (),
Some(password) => {
try!(formatter.write(b":"));
try!(formatter.write(password.as_bytes()));
}
}
try!(formatter.write(b"@"));
}
Ok(())
}
}


/// Formatter for URLs which ignores the fragment field.
pub struct UrlNoFragmentFormatter<'a> {
pub url: &'a Url
}

impl<'a> Show for UrlNoFragmentFormatter<'a> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
try!(formatter.write(self.url.scheme.as_bytes()));
try!(formatter.write(b":"));
try!(self.url.scheme_data.fmt(formatter));
match self.url.query {
None => (),
Some(ref query) => {
try!(formatter.write(b"?"));
try!(formatter.write(query.as_bytes()));
}
}
Ok(())
}
}


/// Formatting Tests
#[cfg(test)]
mod tests {
use super::super::Url;
use super::{PathFormatter, UserInfoFormatter};

#[test]
fn path_formatting() {
let data = [
(vec![], "/"),
(vec![""], "/"),
(vec!["test", "path"], "/test/path"),
(vec!["test", "path", ""], "/test/path/")
];
for &(ref path, result) in data.iter() {
assert_eq!(PathFormatter {
path: path.as_slice()
}.to_string(), result.to_string());
}
}

#[test]
fn userinfo_formatting() {
// Test data as (username, password, result) tuples.
let data = [
("", None, ""),
("", Some(""), ":@"),
("", Some("password"), ":password@"),
("username", None, "username@"),
("username", Some(""), "username:@"),
("username", Some("password"), "username:password@")
];
for &(username, password, result) in data.iter() {
assert_eq!(UserInfoFormatter {
username: username,
password: password
}.to_string(), result.to_string());
}
}

#[test]
fn relative_scheme_url_formatting() {
let data = [
("http://example.com/", "http://example.com/"),
("http://addslash.com", "http://addslash.com/"),
("http://@emptyuser.com/", "http://emptyuser.com/"),
("http://:@emptypass.com/", "http://:@emptypass.com/"),
("http://[email protected]/", "http://[email protected]/"),
("http://user:[email protected]/", "http://user:[email protected]/"),
("http://slashquery.com/path/?q=something", "http://slashquery.com/path/?q=something"),
("http://noslashquery.com/path?q=something", "http://noslashquery.com/path?q=something")
];
for &(input, result) in data.iter() {
let url = Url::parse(input).unwrap();
assert_eq!(url.to_string(), result.to_string());
}
}
}
91 changes: 42 additions & 49 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,15 @@ pub use percent_encoding::{
PASSWORD_ENCODE_SET, USERNAME_ENCODE_SET, FORM_URLENCODED_ENCODE_SET, EncodeSet,
};

use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};

mod host;
mod parser;
mod urlutils;
pub mod percent_encoding;
pub mod form_urlencoded;
pub mod punycode;
pub mod format;

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -583,6 +585,14 @@ impl Url {
scheme_data.lossy_percent_decode_password())
}

/// Serialize the URL's username and password, if any.
///
/// Format: "<username>:<password>@"
#[inline]
pub fn serialize_userinfo<'a>(&'a mut self) -> Option<String> {
self.relative_scheme_data().map(|scheme_data| scheme_data.serialize_userinfo())
}

/// If the URL is in a *relative scheme*, return its structured host.
#[inline]
pub fn host<'a>(&'a self) -> Option<&'a Host> {
Expand Down Expand Up @@ -698,26 +708,6 @@ impl Show for Url {
}
}

struct UrlNoFragmentFormatter<'a> {
url: &'a Url
}

impl<'a> Show for UrlNoFragmentFormatter<'a> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
try!(formatter.write(self.url.scheme.as_bytes()));
try!(formatter.write(b":"));
try!(self.url.scheme_data.fmt(formatter));
match self.url.query {
None => (),
Some(ref query) => {
try!(formatter.write(b"?"));
try!(formatter.write(query.as_bytes()));
}
}
Ok(())
}
}


impl Show for SchemeData {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
Expand Down Expand Up @@ -800,50 +790,49 @@ impl RelativeSchemeData {
/// The returned string starts with a "/" slash, and components are separated by slashes.
/// A trailing slash represents an empty last component.
pub fn serialize_path(&self) -> String {
PathFormatter { path: &self.path }.to_string()
PathFormatter {
path: self.path.as_slice()
}.to_string()
}
}

struct PathFormatter<'a> {
path: &'a Vec<String>
}

impl<'a> Show for PathFormatter<'a> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
if self.path.is_empty() {
formatter.write(b"/")
} else {
for path_part in self.path.iter() {
try!(formatter.write(b"/"));
try!(formatter.write(path_part.as_bytes()));
}
Ok(())
}
/// Serialize the userinfo as a string.
///
/// Format: "<username>:<password>@".
pub fn serialize_userinfo(&self) -> String {
UserInfoFormatter {
username: self.username.as_slice(),
password: self.password.as_ref().map(|s| s.as_slice())
}.to_string()
}
}


impl Show for RelativeSchemeData {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
// Write the scheme-trailing double slashes.
try!(formatter.write(b"//"));
if !self.username.is_empty() || self.password.is_some() {
try!(formatter.write(self.username.as_bytes()));
match self.password {
None => (),
Some(ref password) => {
try!(formatter.write(b":"));
try!(formatter.write(password.as_bytes()));
}
}
try!(formatter.write(b"@"));
}

// Write the user info.
try!(UserInfoFormatter {
username: self.username.as_slice(),
password: self.password.as_ref().map(|s| s.as_slice())
}.fmt(formatter));

// Write the host.
try!(self.host.fmt(formatter));

// Write the port.
match self.port {
Some(port) => {
try!(write!(formatter, ":{}", port));
},
None => {}
}
PathFormatter { path: &self.path }.fmt(formatter)

// Write the path.
PathFormatter {
path: self.path.as_slice()
}.fmt(formatter)
}
}

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


impl ToUrlPath for path::posix::Path {
fn to_url_path(&self) -> Result<Vec<String>, ()> {
if !self.is_absolute() {
Expand All @@ -861,6 +851,7 @@ impl ToUrlPath for path::posix::Path {
}
}


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


impl FromUrlPath for path::posix::Path {
fn from_url_path(path: &[String]) -> Result<path::posix::Path, ()> {
if path.is_empty() {
Expand All @@ -906,6 +898,7 @@ impl FromUrlPath for path::posix::Path {
}
}


impl FromUrlPath for path::windows::Path {
fn from_url_path(path: &[String]) -> Result<path::windows::Path, ()> {
if path.is_empty() {
Expand Down