extern crate alloc;
use crate::char_set::{ASCII_HYPHEN_DIGITS_LETTERS, AllowedAscii};
use alloc::{string::String, vec::Vec};
use core::{
borrow::Borrow,
cmp::Ordering,
convert,
error::Error,
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
iter::FusedIterator,
num::NonZeroU8,
ops::Deref,
str,
};
static RFC_CHARS: &AllowedAscii<[u8; 63]> = &ASCII_HYPHEN_DIGITS_LETTERS;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DomainOrdering {
Less,
Shorter,
Equal,
Longer,
Greater,
}
impl Display for DomainOrdering {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::Less => f.write_str("less since a label was less"),
Self::Shorter => f.write_str("less since there were fewer labels"),
Self::Equal => f.write_str("equal"),
Self::Longer => f.write_str("greater since there were more labels"),
Self::Greater => f.write_str("greater since a label was greater"),
}
}
}
impl From<DomainOrdering> for Ordering {
#[inline]
fn from(value: DomainOrdering) -> Self {
match value {
DomainOrdering::Less | DomainOrdering::Shorter => Self::Less,
DomainOrdering::Equal => Self::Equal,
DomainOrdering::Longer | DomainOrdering::Greater => Self::Greater,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Domain<T> {
value: T,
}
impl<T> Domain<T> {
#[inline]
pub const fn as_inner(&self) -> &T {
&self.value
}
#[inline]
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: AsRef<[u8]>> Domain<T> {
#[expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
reason = "comments explain their correctness"
)]
#[inline]
pub fn contains_trailing_dot(&self) -> bool {
let bytes = self.value.as_ref();
bytes[bytes.len() - 1] == b'.'
}
#[inline]
pub fn as_str(&self) -> &str {
<&str>::from(Domain::<&str>::from(Domain::<&[u8]>::from(self)))
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
<&[u8]>::from(Domain::<&[u8]>::from(self))
}
#[expect(
unsafe_code,
reason = "we enforce nonzero lengths, so NonZeroU8::new_unchecked is fine"
)]
#[expect(
clippy::arithmetic_side_effects,
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "comments justify their correctness"
)]
#[inline]
pub fn len(&self) -> NonZeroU8 {
let len = (self.value.as_ref().len() - usize::from(self.contains_trailing_dot())) as u8;
unsafe { NonZeroU8::new_unchecked(len) }
}
#[expect(
clippy::arithmetic_side_effects,
reason = "comment justifies its correctness"
)]
#[inline]
pub fn try_from_bytes<T2: AsRef<[u8]>>(
v: T,
allowed_ascii: &AllowedAscii<T2>,
) -> Result<Self, DomainErr> {
let val = v.as_ref();
let value = val
.split_last()
.ok_or(DomainErr::Empty)
.and_then(|(lst, rem)| {
if *lst == b'.' {
rem.split_last()
.ok_or(DomainErr::RootDomain)
.and_then(|(lst_2, _)| {
if *lst_2 == b'.' {
Err(DomainErr::EmptyLabel)
} else {
Ok(rem)
}
})
} else {
Ok(val)
}
})?;
if value.len() > 253 {
Err(DomainErr::LenExceeds253(value.len()))
} else {
value
.iter()
.try_fold(0, |label_len, byt| {
let b = *byt;
if b == b'.' {
NonZeroU8::new(label_len).map_or(Err(DomainErr::EmptyLabel), |_| Ok(0))
} else if !allowed_ascii.contains(b) {
Err(DomainErr::InvalidByte(b))
} else if label_len == 63 {
Err(DomainErr::LabelLenExceeds63)
} else {
Ok(label_len + 1)
}
})
.map(|_| Self { value: v })
}
}
#[inline]
pub fn iter(&self) -> LabelIter<'_> {
LabelIter {
domain: self.as_bytes(),
}
}
#[inline]
pub fn same_branch<T2: AsRef<[u8]>>(&self, right: &Domain<T2>) -> bool {
if self == right {
true
} else {
self.iter()
.zip(right)
.try_fold(
(),
|(), (label, label2)| if label == label2 { Ok(()) } else { Err(()) },
)
.is_ok_and(|()| true)
}
}
#[inline]
pub fn cmp_by_domain_ordering<T2: AsRef<[u8]>>(&self, right: &Domain<T2>) -> DomainOrdering {
if self == right {
DomainOrdering::Equal
} else {
let mut right_iter = right.iter();
self.iter()
.try_fold(false, |_, label| {
right_iter
.next()
.map_or(Ok(true), |label2| match label.cmp(&label2) {
Ordering::Less => Err(DomainOrdering::Less),
Ordering::Equal => Ok(false),
Ordering::Greater => Err(DomainOrdering::Greater),
})
})
.map_or_else(convert::identity, |flag| {
if flag {
DomainOrdering::Longer
} else {
DomainOrdering::Shorter
}
})
}
}
#[inline]
pub fn cmp_doms<T2: AsRef<[u8]>>(&self, right: &Domain<T2>) -> Ordering {
self.cmp_by_domain_ordering(right).into()
}
#[expect(clippy::unreachable, reason = "bug in code, so we want to crash")]
#[inline]
pub fn first_label(&self) -> Label<'_> {
self.iter()
.next_back()
.unwrap_or_else(|| unreachable!("there is a bug in Domain::try_from_bytes"))
}
#[expect(clippy::unreachable, reason = "bug in code, so we want to crash")]
#[inline]
pub fn tld(&self) -> Label<'_> {
self.iter()
.next()
.unwrap_or_else(|| unreachable!("there is a bug in Domain::try_from_bytes"))
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Domain<T>> for Domain<T2> {
#[inline]
fn eq(&self, other: &Domain<T>) -> bool {
self.as_bytes().eq_ignore_ascii_case(other.as_bytes())
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Domain<T>> for Domain<T2> {
#[inline]
fn eq(&self, other: &&Domain<T>) -> bool {
*self == **other
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Domain<T>> for &Domain<T2> {
#[inline]
fn eq(&self, other: &Domain<T>) -> bool {
**self == *other
}
}
impl<T: AsRef<[u8]>> Eq for Domain<T> {}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Domain<T>> for Domain<T2> {
#[inline]
fn partial_cmp(&self, other: &Domain<T>) -> Option<Ordering> {
Some(self.cmp_doms(other))
}
}
impl<T: AsRef<[u8]>> Ord for Domain<T> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.cmp_doms(other)
}
}
impl<T: AsRef<[u8]>> Hash for Domain<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().to_ascii_lowercase().hash(state);
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> TryFrom<(T, &AllowedAscii<T2>)> for Domain<T> {
type Error = DomainErr;
#[inline]
fn try_from(value: (T, &AllowedAscii<T2>)) -> Result<Self, Self::Error> {
Self::try_from_bytes(value.0, value.1)
}
}
impl<T: AsRef<[u8]>> Display for Domain<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self)
}
}
impl<T: AsRef<[u8]>> AsRef<str> for Domain<T> {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<T: AsRef<[u8]>> AsRef<[u8]> for Domain<T> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T: AsRef<[u8]>> Deref for Domain<T> {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl From<Domain<Vec<u8>>> for Domain<String> {
#[expect(
unsafe_code,
reason = "we enforce ASCII, so String::from_utf8_unchecked is fine"
)]
#[inline]
fn from(value: Domain<Vec<u8>>) -> Self {
let val = unsafe { String::from_utf8_unchecked(value.value) };
Self { value: val }
}
}
impl<'a: 'b, 'b, T: AsRef<[u8]>> From<&'a Domain<T>> for Domain<&'b [u8]> {
#[inline]
fn from(value: &'a Domain<T>) -> Self {
Self {
value: value.value.as_ref(),
}
}
}
impl<'a: 'b, 'b, T: AsRef<str>> From<&'a Domain<T>> for Domain<&'b str> {
#[inline]
fn from(value: &'a Domain<T>) -> Self {
Self {
value: value.value.as_ref(),
}
}
}
impl From<Domain<String>> for Domain<Vec<u8>> {
#[inline]
fn from(value: Domain<String>) -> Self {
Self {
value: value.value.into_bytes(),
}
}
}
impl<'a: 'b, 'b> From<Domain<&'a [u8]>> for Domain<&'b str> {
#[expect(
unsafe_code,
reason = "we enforce ASCII, so str::from_utf8_unchecked is fine"
)]
#[inline]
fn from(value: Domain<&'a [u8]>) -> Self {
let val = unsafe { str::from_utf8_unchecked(value.value) };
Self { value: val }
}
}
impl<'a: 'b, 'b> From<Domain<&'a str>> for Domain<&'b [u8]> {
#[inline]
fn from(value: Domain<&'a str>) -> Self {
Self {
value: value.value.as_bytes(),
}
}
}
impl From<Domain<Self>> for String {
#[inline]
fn from(value: Domain<Self>) -> Self {
if value.contains_trailing_dot() {
let mut val = value.value;
_ = val.pop();
val
} else {
value.value
}
}
}
impl<'a: 'b, 'b> From<Domain<&'a str>> for &'b str {
#[expect(
unsafe_code,
reason = "we enforce ASCII, so str::from_utf8_unchecked is fine"
)]
#[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")]
#[inline]
fn from(value: Domain<&'a str>) -> Self {
let utf8 = &value.value.as_bytes()[..value.len().get().into()];
unsafe { str::from_utf8_unchecked(utf8) }
}
}
impl From<Domain<Self>> for Vec<u8> {
#[inline]
fn from(value: Domain<Self>) -> Self {
if value.contains_trailing_dot() {
let mut val = value.value;
_ = val.pop();
val
} else {
value.value
}
}
}
impl<'a: 'b, 'b> From<Domain<&'a [u8]>> for &'b [u8] {
#[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")]
#[inline]
fn from(value: Domain<&'a [u8]>) -> Self {
&value.value[..value.len().get().into()]
}
}
#[expect(variant_size_differences, reason = "usize is fine in size")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DomainErr {
Empty,
RootDomain,
LenExceeds253(usize),
EmptyLabel,
LabelLenExceeds63,
InvalidByte(u8),
}
impl Display for DomainErr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::Empty => f.write_str("domain is empty"),
Self::RootDomain => f.write_str("domain is the root domain"),
Self::LenExceeds253(len) => write!(
f,
"domain has length {len} which is greater than the max length of 253"
),
Self::EmptyLabel => f.write_str("domain has an empty label"),
Self::LabelLenExceeds63 => {
f.write_str("domain has a label that exceeds the max length of 63")
}
Self::InvalidByte(byt) => {
write!(f, "domain has a label with the invalid byte value {byt}")
}
}
}
}
impl Error for DomainErr {}
#[derive(Clone, Copy, Debug)]
pub struct Label<'a> {
value: &'a str,
}
impl<'a> Label<'a> {
#[inline]
#[must_use]
pub const fn as_str(self) -> &'a str {
self.value
}
#[inline]
#[must_use]
pub fn is_alphabetic(self) -> bool {
self.value
.as_bytes()
.iter()
.try_fold((), |(), byt| {
if byt.is_ascii_alphabetic() {
Ok(())
} else {
Err(())
}
})
.is_ok()
}
#[inline]
#[must_use]
pub fn is_digits(self) -> bool {
self.value
.as_bytes()
.iter()
.try_fold((), |(), byt| {
if byt.is_ascii_digit() {
Ok(())
} else {
Err(())
}
})
.is_ok()
}
#[inline]
#[must_use]
pub fn is_alphanumeric(self) -> bool {
self.value
.as_bytes()
.iter()
.try_fold((), |(), byt| {
if byt.is_ascii_alphanumeric() {
Ok(())
} else {
Err(())
}
})
.is_ok()
}
#[inline]
#[must_use]
pub fn is_hyphen_or_alphanumeric(self) -> bool {
self.value
.as_bytes()
.iter()
.try_fold((), |(), byt| {
if *byt == b'-' || byt.is_ascii_alphanumeric() {
Ok(())
} else {
Err(())
}
})
.is_ok()
}
#[expect(
unsafe_code,
reason = "we enforce label lengths, so NonZeroU8::new_unchecked is fine"
)]
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "comments justify their correctness"
)]
#[inline]
#[must_use]
pub const fn len(self) -> NonZeroU8 {
let len = self.value.len() as u8;
unsafe { NonZeroU8::new_unchecked(len) }
}
}
impl PartialEq<Label<'_>> for Label<'_> {
#[inline]
fn eq(&self, other: &Label<'_>) -> bool {
self.value.eq_ignore_ascii_case(other.value)
}
}
impl PartialEq<&Label<'_>> for Label<'_> {
#[inline]
fn eq(&self, other: &&Label<'_>) -> bool {
*self == **other
}
}
impl PartialEq<Label<'_>> for &Label<'_> {
#[inline]
fn eq(&self, other: &Label<'_>) -> bool {
**self == *other
}
}
impl Eq for Label<'_> {}
impl PartialOrd<Label<'_>> for Label<'_> {
#[inline]
fn partial_cmp(&self, other: &Label<'_>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Label<'_> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.value
.to_ascii_lowercase()
.cmp(&other.value.to_ascii_lowercase())
}
}
impl Hash for Label<'_> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.to_ascii_lowercase().hash(state);
}
}
impl Display for Label<'_> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.value)
}
}
impl<'a> AsRef<[u8]> for Label<'a> {
#[inline]
fn as_ref(&self) -> &'a [u8] {
self.value.as_bytes()
}
}
impl<'a> AsRef<str> for Label<'a> {
#[inline]
fn as_ref(&self) -> &'a str {
self.value
}
}
impl<'a> Deref for Label<'a> {
type Target = str;
#[inline]
fn deref(&self) -> &'a Self::Target {
self.value
}
}
#[derive(Clone, Debug)]
pub struct LabelIter<'a> {
domain: &'a [u8],
}
impl<'a> Iterator for LabelIter<'a> {
type Item = Label<'a>;
#[expect(
unsafe_code,
reason = "we only allow ASCII, so str::from_utf8_unchecked is fine"
)]
#[expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
reason = "comments justify their correctness"
)]
#[inline]
fn next(&mut self) -> Option<Self::Item> {
(!self.domain.is_empty()).then(|| {
self.domain
.iter()
.rev()
.try_fold(1, |count, byt| {
if *byt == b'.' {
let len = self.domain.len();
let idx = len - count;
let ascii = &self.domain[idx + 1..len];
let value = unsafe { str::from_utf8_unchecked(ascii) };
self.domain = &self.domain[..idx];
Err(Label { value })
} else {
Ok(count + 1)
}
})
.map_or_else(convert::identity, |_| {
let value = unsafe { str::from_utf8_unchecked(self.domain) };
self.domain = &[];
Label { value }
})
})
}
#[inline]
fn last(mut self) -> Option<Self::Item>
where
Self: Sized,
{
self.next_back()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.domain.is_empty() {
(0, Some(0))
} else {
(
(self.domain.len() >> 6).max(1),
Some(self.domain.len().div_ceil(2)),
)
}
}
}
impl FusedIterator for LabelIter<'_> {}
impl DoubleEndedIterator for LabelIter<'_> {
#[expect(
unsafe_code,
reason = "we only allow ASCII, so str::from_utf8_unchecked is fine"
)]
#[expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
reason = "comments justify their correctness"
)]
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
(!self.domain.is_empty()).then(|| {
self.domain
.iter()
.try_fold(0, |count, byt| {
if *byt == b'.' {
let ascii = &self.domain[..count];
let value = unsafe { str::from_utf8_unchecked(ascii) };
self.domain = &self.domain[count + 1..];
Err(Label { value })
} else {
Ok(count + 1)
}
})
.map_or_else(convert::identity, |_| {
let value = unsafe { str::from_utf8_unchecked(self.domain) };
self.domain = &[];
Label { value }
})
})
}
}
impl<'a, T: AsRef<[u8]>> IntoIterator for &'a Domain<T> {
type Item = Label<'a>;
type IntoIter = LabelIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LabelIter {
domain: self.as_bytes(),
}
}
}
impl<'a> IntoIterator for Domain<&'a str> {
type Item = Label<'a>;
type IntoIter = LabelIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LabelIter {
domain: <&str>::from(self).as_bytes(),
}
}
}
impl<'a> IntoIterator for Domain<&'a [u8]> {
type Item = Label<'a>;
type IntoIter = LabelIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LabelIter {
domain: <&[u8]>::from(self),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rfc1123Err {
DomainErr(DomainErr),
LabelStartsWithAHyphen,
LabelEndsWithAHyphen,
InvalidTld,
}
impl Display for Rfc1123Err {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::DomainErr(err) => err.fmt(f),
Self::LabelStartsWithAHyphen => {
f.write_str("a label in the domain starts with a hyphen")
}
Self::LabelEndsWithAHyphen => f.write_str("a label in the domain ends with a hyphen"),
Self::InvalidTld => f.write_str("the TLD in the domain was not all letters nor had length of at least five with the first 4 characters being 'xn--'")
}
}
}
impl Error for Rfc1123Err {}
#[derive(Clone, Copy, Debug)]
pub struct Rfc1123Domain<T> {
dom: Domain<T>,
}
impl<T> Rfc1123Domain<T> {
#[inline]
pub const fn domain(&self) -> &Domain<T> {
&self.dom
}
#[inline]
pub fn into_domain(self) -> Domain<T> {
self.dom
}
}
impl<T: AsRef<[u8]>> Rfc1123Domain<T> {
#[expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
reason = "comments justify their correctness"
)]
#[expect(clippy::redundant_else, reason = "prefer else with else-if")]
#[inline]
pub fn try_from_bytes(v: T) -> Result<Self, Rfc1123Err> {
let val = v.as_ref();
let value = match val.last() {
None => return Err(Rfc1123Err::DomainErr(DomainErr::Empty)),
Some(byt) => {
let b = *byt;
if b == b'.' {
if val.len() == 1 {
return Err(Rfc1123Err::DomainErr(DomainErr::RootDomain));
}
let len = val.len() - 1;
let lst = val[len - 1];
if lst == b'.' {
return Err(Rfc1123Err::DomainErr(DomainErr::EmptyLabel));
} else if lst == b'-' {
return Err(Rfc1123Err::LabelEndsWithAHyphen);
} else {
&val[..len]
}
} else if b == b'-' {
return Err(Rfc1123Err::LabelEndsWithAHyphen);
} else {
val
}
}
};
if value.len() > 253 {
Err(Rfc1123Err::DomainErr(DomainErr::LenExceeds253(value.len())))
} else {
let mut count = 0;
value
.iter()
.try_fold(0, |label_len, byt| {
let b = *byt;
if b == b'.' {
NonZeroU8::new(label_len).map_or(
Err(Rfc1123Err::DomainErr(DomainErr::EmptyLabel)),
|_| {
if value[count - 1] == b'-' {
Err(Rfc1123Err::LabelEndsWithAHyphen)
} else {
Ok(0)
}
},
)
} else if !RFC_CHARS.contains(b) {
Err(Rfc1123Err::DomainErr(DomainErr::InvalidByte(b)))
} else if b == b'-' && label_len == 0 {
Err(Rfc1123Err::LabelStartsWithAHyphen)
} else if label_len == 63 {
Err(Rfc1123Err::DomainErr(DomainErr::LabelLenExceeds63))
} else {
count += 1;
Ok(label_len + 1)
}
})
.and_then(|tld_len| {
let tld = &value[value.len() - usize::from(tld_len)..];
if (tld
.split_at_checked(4)
.is_some_and(|(fst, rem)| !rem.is_empty() && fst == b"xn--"))
|| tld
.iter()
.try_fold((), |(), byt| {
if byt.is_ascii_alphabetic() {
Ok(())
} else {
Err(())
}
})
.is_ok()
{
Ok(())
} else {
Err(Rfc1123Err::InvalidTld)
}
})
.map(|()| Self {
dom: Domain { value: v },
})
}
}
#[inline]
pub fn is_literal_interpretation(&self) -> bool {
self.dom.tld().is_alphabetic()
}
#[inline]
pub fn is_strict_interpretation(&self) -> bool {
let tld = self.dom.tld();
(2..4).contains(&tld.len().get()) && tld.is_alphabetic()
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for Rfc1123Domain<T2> {
#[inline]
fn eq(&self, other: &Rfc1123Domain<T>) -> bool {
self.dom == other.dom
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Rfc1123Domain<T>> for Rfc1123Domain<T2> {
#[inline]
fn eq(&self, other: &&Rfc1123Domain<T>) -> bool {
self.dom == other.dom
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for &Rfc1123Domain<T2> {
#[inline]
fn eq(&self, other: &Rfc1123Domain<T>) -> bool {
self.dom == other.dom
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for Domain<T2> {
#[inline]
fn eq(&self, other: &Rfc1123Domain<T>) -> bool {
*self == other.dom
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for &Domain<T2> {
#[inline]
fn eq(&self, other: &Rfc1123Domain<T>) -> bool {
**self == other.dom
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Rfc1123Domain<T>> for Domain<T2> {
#[inline]
fn eq(&self, other: &&Rfc1123Domain<T>) -> bool {
*self == other.dom
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Domain<T>> for Rfc1123Domain<T2> {
#[inline]
fn eq(&self, other: &Domain<T>) -> bool {
self.dom == *other
}
}
impl<T: AsRef<[u8]>> Eq for Rfc1123Domain<T> {}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Rfc1123Domain<T>> for Rfc1123Domain<T2> {
#[inline]
fn partial_cmp(&self, other: &Rfc1123Domain<T>) -> Option<Ordering> {
self.dom.partial_cmp(&other.dom)
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Rfc1123Domain<T>> for Domain<T2> {
#[inline]
fn partial_cmp(&self, other: &Rfc1123Domain<T>) -> Option<Ordering> {
self.partial_cmp(&other.dom)
}
}
impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Domain<T>> for Rfc1123Domain<T2> {
#[inline]
fn partial_cmp(&self, other: &Domain<T>) -> Option<Ordering> {
self.dom.partial_cmp(other)
}
}
impl<T: AsRef<[u8]>> Ord for Rfc1123Domain<T> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.dom.cmp(&other.dom)
}
}
impl<T: AsRef<[u8]>> Hash for Rfc1123Domain<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.dom.hash(state);
}
}
impl<T> AsRef<Domain<T>> for Rfc1123Domain<T> {
#[inline]
fn as_ref(&self) -> &Domain<T> {
&self.dom
}
}
impl<T> Borrow<Domain<T>> for Rfc1123Domain<T> {
#[inline]
fn borrow(&self) -> &Domain<T> {
&self.dom
}
}
impl<T> Deref for Rfc1123Domain<T> {
type Target = Domain<T>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.dom
}
}
impl<T> From<Rfc1123Domain<T>> for Domain<T> {
#[inline]
fn from(value: Rfc1123Domain<T>) -> Self {
value.dom
}
}
impl From<Rfc1123Domain<Vec<u8>>> for Rfc1123Domain<String> {
#[inline]
fn from(value: Rfc1123Domain<Vec<u8>>) -> Self {
Self {
dom: Domain::<String>::from(value.dom),
}
}
}
impl<'a: 'b, 'b, T: AsRef<[u8]>> From<&'a Rfc1123Domain<T>> for Rfc1123Domain<&'b [u8]> {
#[inline]
fn from(value: &'a Rfc1123Domain<T>) -> Self {
Self {
dom: Domain::<&'b [u8]>::from(&value.dom),
}
}
}
impl<'a: 'b, 'b, T: AsRef<str>> From<&'a Rfc1123Domain<T>> for Rfc1123Domain<&'b str> {
#[inline]
fn from(value: &'a Rfc1123Domain<T>) -> Self {
Self {
dom: Domain::<&'b str>::from(&value.dom),
}
}
}
impl From<Rfc1123Domain<String>> for Rfc1123Domain<Vec<u8>> {
#[inline]
fn from(value: Rfc1123Domain<String>) -> Self {
Self {
dom: Domain::<Vec<u8>>::from(value.dom),
}
}
}
impl<'a: 'b, 'b> From<Rfc1123Domain<&'a [u8]>> for Rfc1123Domain<&'b str> {
#[inline]
fn from(value: Rfc1123Domain<&'a [u8]>) -> Self {
Self {
dom: Domain::<&'b str>::from(value.dom),
}
}
}
impl<'a: 'b, 'b> From<Rfc1123Domain<&'a str>> for Rfc1123Domain<&'b [u8]> {
#[inline]
fn from(value: Rfc1123Domain<&'a str>) -> Self {
Self {
dom: Domain::<&'b [u8]>::from(value.dom),
}
}
}
impl<T: AsRef<[u8]>> TryFrom<Domain<T>> for Rfc1123Domain<T> {
type Error = Rfc1123Err;
#[expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
clippy::unreachable,
reason = "comments explain their correctness"
)]
#[inline]
fn try_from(value: Domain<T>) -> Result<Self, Self::Error> {
let mut labels = value.iter();
let tld = labels
.next()
.unwrap_or_else(|| unreachable!("there is a bug in Domain::try_from_bytes"));
if tld.is_alphabetic()
|| tld
.split_at_checked(4)
.is_some_and(|(fst, rem)| !rem.is_empty() && fst == "xn--")
{
labels
.try_fold((), |(), label| {
let bytes = label.value.as_bytes();
if bytes[0] == b'-' {
Err(Rfc1123Err::LabelStartsWithAHyphen)
} else if bytes[bytes.len() - 1] == b'-' {
Err(Rfc1123Err::LabelEndsWithAHyphen)
} else {
bytes.iter().try_fold((), |(), byt| match *byt {
b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' => Ok(()),
val => Err(Rfc1123Err::DomainErr(DomainErr::InvalidByte(val))),
})
}
})
.map(|()| Self { dom: value })
} else {
Err(Rfc1123Err::InvalidTld)
}
}
}
impl<T: AsRef<[u8]>> Display for Rfc1123Domain<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.dom.fmt(f)
}
}
impl<'a, T: AsRef<[u8]>> IntoIterator for &'a Rfc1123Domain<T> {
type Item = Label<'a>;
type IntoIter = LabelIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LabelIter {
domain: self.dom.as_bytes(),
}
}
}
impl<'a> IntoIterator for Rfc1123Domain<&'a str> {
type Item = Label<'a>;
type IntoIter = LabelIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LabelIter {
domain: <&str>::from(self.dom).as_bytes(),
}
}
}
impl<'a> IntoIterator for Rfc1123Domain<&'a [u8]> {
type Item = Label<'a>;
type IntoIter = LabelIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LabelIter {
domain: <&[u8]>::from(self.dom),
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::{Domain, DomainErr, Rfc1123Domain, Rfc1123Err};
use crate::char_set::{ASCII_FIREFOX, ASCII_HYPHEN_DIGITS_LETTERS, AllowedAscii};
use alloc::borrow::ToOwned;
use core::cmp::Ordering;
use serde_json as _;
#[test]
fn test_dom_parse() {
let allowed_ascii = ASCII_FIREFOX;
assert!(
Domain::try_from_bytes("", &allowed_ascii)
.map_or_else(|e| e == DomainErr::Empty, |_| false)
);
assert!(
Domain::try_from_bytes(".", &allowed_ascii)
.map_or_else(|e| e == DomainErr::RootDomain, |_| false)
);
assert!(
Domain::try_from_bytes("a..com", &allowed_ascii)
.map_or_else(|e| e == DomainErr::EmptyLabel, |_| false)
);
assert!(
Domain::try_from_bytes("a..", &allowed_ascii)
.map_or_else(|e| e == DomainErr::EmptyLabel, |_| false)
);
assert!(
Domain::try_from_bytes("..", &allowed_ascii)
.map_or_else(|e| e == DomainErr::EmptyLabel, |_| false)
);
let val = "www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com";
assert!(val.len() == 72);
assert!(
Domain::try_from_bytes(val, &allowed_ascii)
.map_or_else(|e| e == DomainErr::LabelLenExceeds63, |_| false)
);
assert!(
Domain::try_from_bytes(
"www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
&allowed_ascii
)
.map_or(false, |d| d.len().get() == 71)
);
assert!(Domain::try_from_bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", &allowed_ascii).map_or_else(|e| e == DomainErr::LenExceeds253(254), |_| false));
assert!(Domain::try_from_bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", &allowed_ascii).map_or(false, |d| d.len().get() == 253 ));
assert!(Domain::try_from_bytes("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a", &allowed_ascii).map_or_else(|e| e == DomainErr::LenExceeds253(255), |_| false));
assert!(Domain::try_from_bytes("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a", &allowed_ascii).map_or(false, |d| d.iter().count() == 127 && d.len().get() == 253));
assert!(Domain::try_from_bytes("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.", &allowed_ascii).map_or(false, |d| d.iter().count() == 127 && d.len().get() == 253));
assert!(
Domain::try_from_bytes("com.", &allowed_ascii).map_or(false, |d| d.as_str() == "com")
);
assert!(Domain::try_from_bytes("c", &allowed_ascii).map_or(false, |d| d.as_str() == "c"));
assert!(
Domain::try_from_bytes("wwW.ExAMple.COm", &allowed_ascii).map_or(false, |d| {
Domain::try_from_bytes("www.example.com", &allowed_ascii)
.map_or(false, |d2| d == d2 && d.cmp(&d2) == Ordering::Equal)
})
);
assert!(
Domain::try_from_bytes("ww_W.com", &allowed_ascii).map_or(false, |d| {
Domain::try_from_bytes("Ww_w.com", &allowed_ascii)
.map_or(false, |d2| d == d2 && d.cmp(&d2) == Ordering::Equal)
})
);
let mut input;
let mut counter = 0;
for i in 0..=127 {
input = [i];
match i {
b'!'
| b'$'
| b'&'..=b')'
| b'+'..=b'-'
| b'0'..=b'9'
| b';'
| b'='
| b'A'..=b'Z'
| b'_'..=b'{'
| b'}'..=b'~' => {
counter += 1;
assert!(
Domain::try_from_bytes(input, &allowed_ascii).map_or(false, |d| d
.value
.len()
== 1
&& d.value == input)
)
}
b'.' => {
let input2 = b"a.";
assert!(
Domain::try_from_bytes(input2, &allowed_ascii).map_or(false, |d| d
.len()
.get()
== 1
&& d.value == input2)
)
}
_ => assert!(
Domain::try_from_bytes(input, &allowed_ascii)
.map_or_else(|e| e == DomainErr::InvalidByte(i), |_| false)
),
}
}
assert!(counter == 78);
}
#[test]
fn test_dom_iter() {
let allowed_ascii = ASCII_FIREFOX;
assert!(
Domain::try_from_bytes("www.example.com", &allowed_ascii).map_or(false, |d| {
let mut iter = d.iter();
let Some(l) = iter.next() else {
return false;
};
if l.value != "com" {
return false;
}
let Some(l) = iter.next() else { return false };
if l.value != "example" {
return false;
}
let Some(l) = iter.next() else {
return false;
};
if l.value != "www" {
return false;
}
iter.next().is_none()
})
);
assert!(
Domain::try_from_bytes("www.example.com", &allowed_ascii).map_or(false, |d| {
let mut iter = d.iter();
let Some(l) = iter.next_back() else {
return false;
};
if l.value != "www" {
return false;
}
let Some(l) = iter.next_back() else {
return false;
};
if l.value != "example" {
return false;
}
let Some(l) = iter.next_back() else {
return false;
};
if l.value != "com" {
return false;
}
iter.next_back().is_none()
})
);
assert!(
Domain::try_from_bytes("www.example.com", &allowed_ascii).map_or(false, |d| {
let mut iter = d.iter();
let Some(l) = iter.next_back() else {
return false;
};
if l.value != "www" {
return false;
}
let Some(l) = iter.next() else { return false };
if l.value != "com" {
return false;
}
let Some(l) = iter.next_back() else {
return false;
};
if l.value != "example" {
return false;
}
iter.next().is_none() && iter.next_back().is_none()
})
);
}
#[test]
fn rfc1123() {
assert!(
Domain::try_from_bytes("example.com", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or(false, |dom| dom.as_str() == "example.com")
)
);
assert!(
AllowedAscii::try_from_unique_ascii(b"exampl!co".to_owned()).map_or(false, |ascii| {
Domain::try_from_bytes("exampl!e.com", &ascii).map_or(false, |dom| {
Rfc1123Domain::try_from(dom).map_or_else(
|e| e == Rfc1123Err::DomainErr(DomainErr::InvalidByte(b'!')),
|_| false,
)
})
})
);
assert!(
Domain::try_from_bytes("example-.com", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or_else(|e| e == Rfc1123Err::LabelEndsWithAHyphen, |_| false)
)
);
assert!(
Domain::try_from_bytes("-example.com", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or_else(|e| e == Rfc1123Err::LabelStartsWithAHyphen, |_| false)
)
);
assert!(
Domain::try_from_bytes("example.c1m", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or_else(|e| e == Rfc1123Err::InvalidTld, |_| false)
)
);
assert!(
Domain::try_from_bytes("example.commm", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or(false, |rfc| rfc.is_literal_interpretation())
)
);
assert!(
Domain::try_from_bytes("example.xn--abc", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or(false, |rfc| !rfc.is_literal_interpretation())
)
);
assert!(
Domain::try_from_bytes("example.com", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or(false, |rfc| rfc.is_strict_interpretation())
)
);
assert!(
Domain::try_from_bytes("example.comm", &ASCII_HYPHEN_DIGITS_LETTERS).map_or(
false,
|dom| Rfc1123Domain::try_from(dom)
.map_or(false, |rfc| !rfc.is_strict_interpretation())
)
);
}
#[test]
fn test_tld() {
assert!(
Domain::try_from_bytes("example.com", &ASCII_HYPHEN_DIGITS_LETTERS)
.map_or(false, |dom| dom.tld().as_str() == "com",)
);
}
#[test]
fn test_rfc1123_parse() {
assert!(
Rfc1123Domain::try_from_bytes("")
.map_or_else(|e| e == Rfc1123Err::DomainErr(DomainErr::Empty), |_| false)
);
assert!(Rfc1123Domain::try_from_bytes(".").map_or_else(
|e| e == Rfc1123Err::DomainErr(DomainErr::RootDomain),
|_| false
));
assert!(Rfc1123Domain::try_from_bytes("a..com").map_or_else(
|e| e == Rfc1123Err::DomainErr(DomainErr::EmptyLabel),
|_| false
));
assert!(Rfc1123Domain::try_from_bytes("a..").map_or_else(
|e| e == Rfc1123Err::DomainErr(DomainErr::EmptyLabel),
|_| false
));
assert!(Rfc1123Domain::try_from_bytes("..").map_or_else(
|e| e == Rfc1123Err::DomainErr(DomainErr::EmptyLabel),
|_| false
));
let val = "www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com";
assert!(val.len() == 72);
assert!(Rfc1123Domain::try_from_bytes(val).map_or_else(
|e| e == Rfc1123Err::DomainErr(DomainErr::LabelLenExceeds63),
|_| false
));
assert!(
Rfc1123Domain::try_from_bytes(
"www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
)
.map_or(false, |d| d.len().get() == 71)
);
assert!(Rfc1123Domain::try_from_bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").map_or_else(|e| e == Rfc1123Err::DomainErr(DomainErr::LenExceeds253(254)), |_| false));
assert!(Rfc1123Domain::try_from_bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").map_or(false, |d| d.len().get() == 253 ));
assert!(Rfc1123Domain::try_from_bytes("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").map_or_else(|e| e == Rfc1123Err::DomainErr(DomainErr::LenExceeds253(255)), |_| false));
assert!(Rfc1123Domain::try_from_bytes("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").map_or(false, |d| d.iter().count() == 127 && d.len().get() == 253));
assert!(Rfc1123Domain::try_from_bytes("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.").map_or(false, |d| d.iter().count() == 127 && d.len().get() == 253));
assert!(Rfc1123Domain::try_from_bytes("com.").map_or(false, |d| d.as_str() == "com"));
assert!(Rfc1123Domain::try_from_bytes("c").map_or(false, |d| d.as_str() == "c"));
assert!(
Rfc1123Domain::try_from_bytes("-")
.map_or_else(|err| err == Rfc1123Err::LabelEndsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("-.")
.map_or_else(|err| err == Rfc1123Err::LabelEndsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("a.com.-")
.map_or_else(|err| err == Rfc1123Err::LabelEndsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("a.com-")
.map_or_else(|err| err == Rfc1123Err::LabelEndsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("a-.com")
.map_or_else(|err| err == Rfc1123Err::LabelEndsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("a.-com")
.map_or_else(|err| err == Rfc1123Err::LabelStartsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("-a.com")
.map_or_else(|err| err == Rfc1123Err::LabelStartsWithAHyphen, |_| false)
);
assert!(
Rfc1123Domain::try_from_bytes("wwW.ExAMple.COm").map_or(false, |d| {
Rfc1123Domain::try_from_bytes("www.example.com")
.map_or(false, |d2| d == d2 && d.cmp(&d2) == Ordering::Equal)
})
);
assert!(
Rfc1123Domain::try_from_bytes("ww-W.com").map_or(false, |d| {
Rfc1123Domain::try_from_bytes("Ww-w.com")
.map_or(false, |d2| d == d2 && d.cmp(&d2) == Ordering::Equal)
})
);
assert!(
Rfc1123Domain::try_from_bytes("1.1.1.1")
.map_or_else(|err| err == Rfc1123Err::InvalidTld, |_| false)
);
}
}