#[cfg(test)]
mod tests;
use std::alloc::LayoutError;
use std::borrow::Borrow;
use std::cmp;
use std::fmt::{self, Display};
use std::hash::{self, Hash};
use std::ops::Deref;
use std::ptr::slice_from_raw_parts;
use std::str::{self, FromStr};
use serde::{Deserialize, Serialize};
use simple_dst::{CloneToUninit, Dst, ToOwned};
use thiserror::Error;
const MAX_DOMAIN_LEN: usize = 253;
const MAX_LABEL_LEN: usize = 63;
#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum DomainParseError {
#[error("domain is empty")]
Empty,
#[error("{domain}: domain contains an empty label")]
EmptyLabel { domain: String },
#[error("{domain}: domain has a prefix: {prefix}")]
HasPrefix { domain: String, prefix: String },
#[error("{domain}: domain has no root")]
MissingRoot { domain: String },
#[error("{domain}: domain is missing a suffix")]
MissingSuffix { domain: String },
#[error("{domain}: domain is too long")]
TooLong { domain: String },
#[error("{domain}: domain contains a too-long label: {label}")]
TooLongLabel { domain: String, label: String },
#[error("{domain}: domain has an unknown suffix: {suffix}")]
UnknownSuffix { domain: String, suffix: String },
}
#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum DomainCreateError {
#[error(transparent)]
Parse(#[from] DomainParseError),
#[error(transparent)]
Layout(#[from] LayoutError),
}
fn get_not_fqdn(s: &str) -> &str {
s.strip_suffix('.').unwrap_or(s)
}
fn parse_domain(domain: &str) -> Result<(Option<usize>, usize), DomainParseError> {
let not_fqdn = get_not_fqdn(domain);
if not_fqdn.is_empty() {
return Err(DomainParseError::Empty);
}
if not_fqdn.len() > MAX_DOMAIN_LEN {
return Err(DomainParseError::TooLong {
domain: domain.to_owned(),
});
}
for label in not_fqdn.split('.') {
if label.is_empty() {
return Err(DomainParseError::EmptyLabel {
domain: domain.to_owned(),
});
} else if label.len() > MAX_LABEL_LEN {
return Err(DomainParseError::TooLongLabel {
domain: domain.to_owned(),
label: label.to_owned(),
});
}
}
let suffix =
psl::suffix(not_fqdn.as_bytes()).ok_or_else(|| DomainParseError::MissingSuffix {
domain: domain.to_owned(),
})?;
#[allow(clippy::expect_used, reason = "PSL shouldn't return invalid UTF-8")]
let suffix_str = str::from_utf8(suffix.as_bytes())
.expect("psl crate returned invalid UTF-8 when slicing domain suffix");
if !suffix.is_known() {
return Err(DomainParseError::UnknownSuffix {
domain: domain.to_owned(),
suffix: suffix_str.to_owned(),
});
}
let suffix_len = suffix_str.len();
if not_fqdn.len() == suffix_len {
return Err(DomainParseError::MissingRoot {
domain: domain.to_owned(),
});
}
let suffix_separator_idx = not_fqdn.len() - suffix_len - 1;
let without_suffix = ¬_fqdn[..suffix_separator_idx];
Ok((without_suffix.rfind('.'), suffix_separator_idx))
}
#[repr(transparent)]
#[derive(Debug, Dst, CloneToUninit, ToOwned)]
#[dst(new_unchecked_vis = pub)]
pub struct Root(Domain);
impl Root {
pub fn parse(input: &str) -> Result<Box<Self>, DomainCreateError> {
let (root_separator_idx, suffix_separator_idx) = parse_domain(input)?;
if let Some(root_separator_idx) = root_separator_idx {
return Err(DomainCreateError::Parse(DomainParseError::HasPrefix {
domain: input.to_owned(),
prefix: input[..root_separator_idx].to_string(),
}));
}
Ok({
let domain =
unsafe { Domain::new_unchecked(root_separator_idx, suffix_separator_idx, input) }?;
unsafe { Box::from_raw(Box::into_raw(domain) as *mut Self) }
})
}
#[must_use]
pub fn as_str(&self) -> &str {
let offset = self.0.root_separator_idx.map_or(0, |i| i + 1);
&self.0.domain[offset..]
}
#[must_use]
pub fn suffix(&self) -> &str {
&self.0.domain[self.0.suffix_separator_idx + 1..]
}
#[must_use]
pub fn is_fqdn(&self) -> bool {
self.as_str().ends_with('.')
}
#[must_use]
pub fn not_fqdn(&self) -> &Self {
if !self.is_fqdn() {
return self;
}
unsafe {
let ptr = (&raw const *self).cast::<()>();
#[allow(
clippy::cast_ptr_alignment,
reason = "the pointer points to a valid value already"
)]
&*(slice_from_raw_parts(ptr, self.len() - 1) as *const Self)
}
}
}
impl AsRef<str> for Root {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for Root {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Deref for Root {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl PartialEq for Root {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Root {}
impl PartialOrd for Root {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Root {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl Hash for Root {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl Display for Root {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl FromStr for Box<Root> {
type Err = DomainCreateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Root::parse(s)
}
}
impl TryFrom<&str> for Box<Root> {
type Error = DomainCreateError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Root::parse(value)
}
}
impl<'de> Deserialize<'de> for Box<Root> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Visitor;
struct RootVisitor;
impl Visitor<'_> for RootVisitor {
type Value = Box<Root>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a string representing a domain root")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Root::parse(v).map_err(E::custom)
}
}
deserializer.deserialize_str(RootVisitor)
}
}
impl Serialize for Box<Root> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[repr(C)]
#[derive(Debug, Dst, CloneToUninit, ToOwned)]
#[dst(new_unchecked_vis = pub)]
pub struct Domain {
root_separator_idx: Option<usize>,
suffix_separator_idx: usize,
#[allow(clippy::struct_field_names, reason = "it's the underlying value")]
domain: str,
}
impl Domain {
pub fn parse(input: &str) -> Result<Box<Self>, DomainCreateError> {
let (root_separator_idx, suffix_separator_idx) = parse_domain(input)?;
Ok(unsafe { Self::new_unchecked(root_separator_idx, suffix_separator_idx, input) }?)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.domain
}
#[must_use]
pub fn prefix(&self) -> Option<&str> {
self.root_separator_idx.map(|i| &self.domain[..i])
}
#[must_use]
pub fn root(&self) -> &Root {
unsafe { &*((&raw const *self) as *const Root) }
}
#[must_use]
pub fn root_str(&self) -> &str {
self.root().as_str()
}
#[must_use]
pub fn suffix(&self) -> &str {
&self.domain[self.suffix_separator_idx + 1..]
}
#[must_use]
pub fn is_fqdn(&self) -> bool {
self.as_str().ends_with('.')
}
#[must_use]
pub fn not_fqdn(&self) -> &Self {
if !self.is_fqdn() {
return self;
}
unsafe {
let ptr = (&raw const *self).cast::<()>();
#[allow(
clippy::cast_ptr_alignment,
reason = "the pointer points to a valid value already"
)]
&*(slice_from_raw_parts(ptr, self.len() - 1) as *const Self)
}
}
}
impl AsRef<str> for Domain {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for Domain {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Deref for Domain {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl PartialEq for Domain {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Domain {}
impl PartialOrd for Domain {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Domain {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl Hash for Domain {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl Display for Domain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl FromStr for Box<Domain> {
type Err = DomainCreateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Domain::parse(s)
}
}
impl TryFrom<&str> for Box<Domain> {
type Error = DomainCreateError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Domain::parse(value)
}
}
impl<'de> Deserialize<'de> for Box<Domain> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Visitor;
struct DomainVisitor;
impl Visitor<'_> for DomainVisitor {
type Value = Box<Domain>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a string representing a domain")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Domain::parse(v).map_err(E::custom)
}
}
deserializer.deserialize_str(DomainVisitor)
}
}
impl Serialize for Box<Domain> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}