Pārlūkot izejas kodu

Merge pull request #11 from michaelsproul/formatter-refactor

Refactor URL formatting code into its own module.
Simon Sapin 12 gadi atpakaļ
vecāks
revīzija
ec468b202a
2 mainītis faili ar 189 papildinājumiem un 49 dzēšanām
  1. 147 0
      src/format.rs
  2. 42 49
      src/lib.rs

+ 147 - 0
src/format.rs

@@ -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://user@user.com/", "http://user@user.com/"),
+            ("http://user:pass@userpass.com/", "http://user:pass@userpass.com/"),
+            ("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());
+        }
+    }
+}

+ 42 - 49
src/lib.rs

@@ -150,6 +150,7 @@ pub use percent_encoding::{
     PASSWORD_ENCODE_SET, USERNAME_ENCODE_SET, FORM_URLENCODED_ENCODE_SET, EncodeSet,
 };
 
+use format::{PathFormatter, UserInfoFormatter, UrlNoFragmentFormatter};
 
 mod host;
 mod parser;
@@ -157,6 +158,7 @@ mod urlutils;
 pub mod percent_encoding;
 pub mod form_urlencoded;
 pub mod punycode;
+pub mod format;
 
 #[cfg(test)]
 mod tests;
@@ -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> {
@@ -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> {
@@ -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)
     }
 }
 
@@ -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() {
@@ -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() {
@@ -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() {
@@ -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() {