diff --git a/src/lib.rs b/src/lib.rs index fb7c48bc3..08d32ef55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,6 +137,8 @@ use std::fmt::{self, Formatter}; use std::str; use std::path::{Path, PathBuf}; use std::borrow::Borrow; +use std::hash::{Hash, Hasher}; +use std::cmp::Ordering; #[cfg(feature="serde_serialization")] use std::str::FromStr; @@ -226,7 +228,7 @@ pub enum SchemeData { } /// Components for URLs in a *relative* scheme such as HTTP. -#[derive(PartialEq, Eq, Clone, Debug, Hash, PartialOrd, Ord)] +#[derive(Clone, Debug)] #[cfg_attr(feature="heap_size", derive(HeapSizeOf))] pub struct RelativeSchemeData { /// The username of the URL, as a possibly empty, percent-encoded string. @@ -266,6 +268,45 @@ pub struct RelativeSchemeData { pub path: Vec, } +impl RelativeSchemeData { + fn get_identity_key(&self) -> (&String, &Option, &Host, Option, Option, &Vec) { + ( + &self.username, + &self.password, + &self.host, + self.port.or(self.default_port), + self.default_port, + &self.path + ) + } +} + + +impl PartialEq for RelativeSchemeData { + fn eq(&self, other: &RelativeSchemeData) -> bool { + self.get_identity_key() == other.get_identity_key() + } +} + +impl Eq for RelativeSchemeData {} + +impl Hash for RelativeSchemeData { + fn hash(&self, state: &mut H) { + self.get_identity_key().hash(state) + } +} + +impl PartialOrd for RelativeSchemeData { + fn partial_cmp(&self, other: &RelativeSchemeData) -> Option { + self.get_identity_key().partial_cmp(&other.get_identity_key()) + } +} + +impl Ord for RelativeSchemeData { + fn cmp(&self, other: &Self) -> Ordering { + self.get_identity_key().cmp(&other.get_identity_key()) + } +} impl str::FromStr for Url { type Err = ParseError; diff --git a/src/tests.rs b/src/tests.rs index 146ad3a20..e25500f5c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -301,3 +301,49 @@ fn issue_124() { let url: Url = "file:..".parse().unwrap(); assert_eq!(url.path().unwrap(), [""]); } + +#[test] +fn relative_scheme_data_equality() { + use std::hash::{Hash, Hasher, SipHasher}; + + fn check_eq(a: &Url, b: &Url) { + assert_eq!(a, b); + + let mut h1 = SipHasher::new(); + a.hash(&mut h1); + let mut h2 = SipHasher::new(); + b.hash(&mut h2); + assert_eq!(h1.finish(), h2.finish()); + } + + fn url(s: &str) -> Url { + let rv = s.parse().unwrap(); + check_eq(&rv, &rv); + rv + } + + // Doesn't care if default port is given. + let a: Url = url("https://example.com/"); + let b: Url = url("https://example.com:443/"); + check_eq(&a, &b); + + // Different ports + let a: Url = url("http://example.com/"); + let b: Url = url("http://example.com:8080/"); + assert!(a != b); + + // Different scheme + let a: Url = url("http://example.com/"); + let b: Url = url("https://example.com/"); + assert!(a != b); + + // Different host + let a: Url = url("http://foo.com/"); + let b: Url = url("http://bar.com/"); + assert!(a != b); + + // Missing path, automatically substituted. Semantically the same. + let a: Url = url("http://foo.com"); + let b: Url = url("http://foo.com/"); + check_eq(&a, &b); +}