Skip to content

implements Deref<Target=str>, Display, AsRef, FromStr ... #16

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 13 commits into from
Jun 29, 2023
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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,20 @@ url_parse/servo_parse time: [5.5127 µs 5.6287 µs 5.8046 µs]
Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
```
```

### Implemented traits

`Url` implements the following traits.
| Trait(s) | Description |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **[`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)** | Provides `to_string` and allows for the value to be used in [format!](https://doc.rust-lang.org/std/fmt/fn.format.html) macros (e.g. `println!`). |
| **[`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)** | Allows debugger output in format macros, (`{:?}` syntax) |
| **[`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), [`Eq`](https://doc.rust-lang.org/std/cmp/trait.Eq.html)** | Allows for comparison, `url1 == url2`, `url1.eq(url2)` |
| **[`PartialOrd`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html), [`Ord`](https://doc.rust-lang.org/std/cmp/trait.Ord.html)** | Allows for ordering `url1 < url2`, done so alphabetically. This is also allows `Url` to be used as a key in a [`BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) |
| **[`Hash`](https://doc.rust-lang.org/std/hash/trait.Hash.html)** | Makes it so that `Url` can be hashed based on the string representation. This is important so that `Url` can be used as a key in a [`HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html) |
| **[`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html)** | Allows for use with [`str`'s `parse` method](https://doc.rust-lang.org/std/primitive.str.html#method.parse) |
| **[`TryFrom<String>`, `TryFrom<&str>`](https://doc.rust-lang.org/std/convert/trait.TryFrom.html)** | Provides `try_into` methods for `String` and `&str` |
| **[`Borrow<str>`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html), [`Borrow<[u8]>`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html)** | Used in some crates so that the `Url` can be used as a key. |
| **[`Deref<Target=str>`](https://doc.rust-lang.org/std/ops/trait.Deref.html)** | Allows for `&Url` to dereference as a `&str`. Also provides a [number of string methods](https://doc.rust-lang.org/std/string/struct.String.html#deref-methods-str) |
| **[`AsRef<[u8]>`](https://doc.rust-lang.org/std/convert/trait.AsRef.html), [`AsRef<str>`](https://doc.rust-lang.org/std/convert/trait.AsRef.html)** | Used to do a cheap reference-to-reference conversion. |
212 changes: 211 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub mod ffi {
base: *const c_char,
base_length: usize,
) -> bool;
pub fn ada_get_url_components(url: *mut ada_url) -> ada_url_components;
pub fn ada_get_components(url: *mut ada_url) -> *mut ada_url_components;

// Getters
pub fn ada_get_origin(url: *mut ada_url) -> ada_owned_string;
Expand Down Expand Up @@ -223,6 +223,7 @@ impl Url {
}

/// Return the parsed version of the URL with all components.
///
/// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-href)
pub fn href(&self) -> &str {
unsafe { ffi::ada_get_href(self.url) }.as_str()
Expand Down Expand Up @@ -446,12 +447,221 @@ impl Url {
pub fn has_search(&self) -> bool {
unsafe { ffi::ada_has_search(self.url) }
}
/// Returns the parsed version of the URL with all components.
///
/// For more information, read [WHATWG URL spec](https://url.spec.whatwg.org/#dom-url-href)
pub fn as_str(&self) -> &str {
self.href()
}
}

/// URLs compare like their stringification.
impl PartialEq for Url {
fn eq(&self, other: &Self) -> bool {
self.href() == other.href()
}
}

impl Eq for Url {}

impl PartialOrd for Url {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.href().partial_cmp(other.href())
}
}

impl Ord for Url {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.href().cmp(other.href())
}
}

impl std::hash::Hash for Url {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.href().hash(state)
}
}

impl std::borrow::Borrow<str> for Url {
fn borrow(&self) -> &str {
self.href()
}
}

impl std::borrow::Borrow<[u8]> for Url {
fn borrow(&self) -> &[u8] {
self.href().as_bytes()
}
}

impl std::convert::AsRef<[u8]> for Url {
fn as_ref(&self) -> &[u8] {
self.href().as_bytes()
}
}

impl std::fmt::Debug for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unsafe {
let components = ffi::ada_get_components(self.url).as_ref().unwrap();
let mut debug = f.debug_struct("Url");
debug
.field("href", &self.href())
.field("protocol_end", &components.protocol_end)
.field("host_start", &components.host_start)
.field("host_end", &components.host_end);
let port = if components.port == u32::MAX {
None
} else {
Some(components.port)
};
let username_end = if components.username_end == u32::MAX {
None
} else {
Some(components.username_end)
};
let search_start = if components.search_start == u32::MAX {
None
} else {
Some(components.search_start)
};
let hash_start = if components.hash_start == u32::MAX {
None
} else {
Some(components.hash_start)
};
let pathname_start = if components.pathname_start == u32::MAX {
None
} else {
Some(components.pathname_start)
};

debug
.field("port", &port)
.field("username_end", &username_end)
.field("search_start", &search_start)
.field("hash_start", &hash_start)
.field("pathname_start", &pathname_start)
.finish()
}
}
}

impl TryFrom<&str> for Url {
type Error = Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::parse(value, None)
}
}

impl TryFrom<String> for Url {
type Error = Error;

fn try_from(value: String) -> Result<Self, Self::Error> {
Self::parse(&value, None)
}
}

impl TryFrom<&String> for Url {
type Error = Error;

fn try_from(value: &String) -> Result<Self, Self::Error> {
Self::parse(value, None)
}
}

impl std::ops::Deref for Url {
type Target = str;
fn deref(&self) -> &Self::Target {
self.href()
}
}
impl std::convert::AsRef<str> for Url {
fn as_ref(&self) -> &str {
self.href()
}
}

impl std::fmt::Display for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.href())
}
}

impl std::str::FromStr for Url {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s, None)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn should_display_serialization() {
let tests = [
("http://example.com/", "http://example.com/"),
("HTTP://EXAMPLE.COM", "http://example.com/"),
("http://user:[email protected]", "http://user:[email protected]/"),
];
for (value, expected) in tests {
let url = Url::parse(value, None).expect("Should have parsed url");
assert_eq!(format!("{}", url), expected);
assert_eq!(url.to_string(), expected);
}
}

#[test]
fn should_parse_with_try_from() {
let tests = [("http://example.com/", true), ("invalid url", false)];
for (value, should_parse) in tests {
let url = Url::parse("http://example.com/", None).unwrap();
let parsed = Url::try_from(value);
if should_parse {
assert_eq!(parsed.is_ok(), should_parse);
assert_eq!(url, parsed.unwrap());
} else {
assert!(parsed.is_err());
}
}
}
#[test]
fn should_compare_urls() {
let tests = [
("http://example.com/", "http://example.com/", true),
("http://example.com/", "https://example.com/", false),
("http://example.com#", "https://example.com/#", false),
("http://example.com", "https://example.com#", false),
(
"https://user:[email protected]",
"https://user:[email protected]",
true,
),
];
for (left, right, expected) in tests {
let left_url = Url::parse(left, None).expect("Should have parsed url");
let right_url = Url::parse(right, None).expect("Should have parsed url");
assert_eq!(
left_url == right_url,
expected,
"left: {left}, right: {right}, expected: {expected}",
);
}
}
#[test]
fn should_order_alphabetically() {
let left = Url::parse("https://example.com/", None).expect("Should have parsed url");
let right = Url::parse("https://zoo.tld/", None).expect("Should have parsed url");
assert!(left < right);
let left = Url::parse("https://c.tld/", None).expect("Should have parsed url");
let right = Url::parse("https://a.tld/", None).expect("Should have parsed url");
assert!(right < left);
}

#[test]
fn should_parse_simple_url() {
let mut out = Url::parse(
Expand Down