lychee/src/uri.rs
2021-02-21 16:33:33 +01:00

120 lines
3.2 KiB
Rust

use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::{convert::TryFrom, fmt::Display};
use url::Url;
/// Lychee's own representation of a URI, which encapsulates all support formats
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Uri {
/// Website URL
Website(Url),
/// Mail address
Mail(String),
}
impl Uri {
pub fn as_str(&self) -> &str {
match self {
Uri::Website(url) => url.as_str(),
Uri::Mail(address) => address.as_str(),
}
}
pub fn scheme(&self) -> Option<String> {
match self {
Uri::Website(url) => Some(url.scheme().to_string()),
Uri::Mail(_address) => None,
}
}
pub fn host_ip(&self) -> Option<IpAddr> {
match self {
Self::Website(url) => match url.host()? {
url::Host::Ipv4(v4_addr) => Some(v4_addr.into()),
url::Host::Ipv6(v6_addr) => Some(v6_addr.into()),
_ => None,
},
Self::Mail(_) => None,
}
}
}
impl TryFrom<&str> for Uri {
type Error = anyhow::Error;
fn try_from(s: &str) -> Result<Self> {
// Remove the `mailto` scheme if it exists
// to avoid parsing it as a website URL.
let s = s.trim_start_matches("mailto:");
if let Ok(uri) = Url::parse(s) {
return Ok(Uri::Website(uri));
};
if s.contains('@') {
return Ok(Uri::Mail(s.to_string()));
};
bail!("Cannot convert to Uri")
}
}
impl Display for Uri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod test {
use crate::test_utils::website;
use super::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[test]
fn test_uri_from_str() {
assert!(matches!(Uri::try_from(""), Err(_)));
assert_eq!(
Uri::try_from("http://example.org").unwrap(),
website("http://example.org")
);
assert_eq!(
Uri::try_from("mail@example.org").unwrap(),
Uri::Mail("mail@example.org".to_string())
);
assert_eq!(
Uri::try_from("mailto:mail@example.org").unwrap(),
Uri::Mail("mail@example.org".to_string())
);
}
#[test]
fn test_uri_host_ip_v4() {
let uri = website("http://127.0.0.1");
let ip = uri.host_ip().expect("Expected a valid IPv4");
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
}
#[test]
fn test_uri_host_ip_v6() {
let uri = website("https://[2020::0010]");
let ip = uri.host_ip().expect("Expected a valid IPv6");
assert_eq!(
ip,
IpAddr::V6(Ipv6Addr::new(0x2020, 0, 0, 0, 0, 0, 0, 0x10))
);
}
#[test]
fn test_uri_host_ip_no_ip() {
let uri = website("https://some.cryptic/url");
let ip = uri.host_ip();
assert!(ip.is_none());
}
#[test]
fn test_mail() {
let uri = website("http://127.0.0.1");
let ip = uri.host_ip().expect("Expected a valid IPv4");
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
}
}