ascii_domain

Domains whose labels are only ASCII.
git clone https://git.philomathiclife.com/repos/ascii_domain
Log | Files | Refs | README

commit e64e9a57bf7ee0554ed1bfe21d49a1ed0a6124f3
parent 4fa6b9277beeff563a195796b4d0a6f016234b36
Author: Zack Newman <zack@philomathiclife.com>
Date:   Mon,  5 Feb 2024 11:36:48 -0700

make domain more compact and simpler

Diffstat:
MCargo.toml | 2+-
Msrc/char_set.rs | 318++++++++++++++++++++++++++++++-------------------------------------------------
Msrc/dom.rs | 660++++++++++++++++++-------------------------------------------------------------
3 files changed, 272 insertions(+), 708 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "ascii_domain" readme = "README.md" repository = "https://git.philomathiclife.com/repos/ascii_domain/" -version = "0.2.0" +version = "0.3.0" [lib] name = "ascii_domain" diff --git a/src/char_set.rs b/src/char_set.rs @@ -1,19 +1,15 @@ use core::{ - array::IntoIter, - borrow::Borrow, fmt::{self, Display, Formatter}, - ops::Deref, - slice::Iter, str, }; use std::error::Error; -/// Error returned from [`AllowedAscii::try_from`]. +/// Error returned from [`AllowedAscii::try_from_unique_ascii`]. #[allow(clippy::exhaustive_enums)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum AsciiErr { - /// Since `AllowedAscii` only allows unique ASCII characters, the maximum `COUNT` is 128. - /// This variant is returned when the `COUNT` exceeds that. - CountTooLarge, + /// Since `AllowedAscii` only allows unique ASCII characters and doesn't allow `b'.'`, the maximum count is + /// 127. This variant is returned when the count exceeds that. + CountTooLarge(usize), /// The contained `u8` is not valid ASCII (i.e., it is strictly greater than 127). InvalidByte(u8), /// `b'.'` was in the allowed ASCII. It is the only ASCII value not allowed since it is always used @@ -27,7 +23,9 @@ impl Display for AsciiErr { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - Self::CountTooLarge => f.write_str("allowed ASCII is larger than 128"), + Self::CountTooLarge(byt) => { + write!(f, "the allowed ASCII had {byt} values, but 127 is the max") + } Self::InvalidByte(byt) => { write!(f, "allowed ASCII was passed the invalid byte value {byt}") } @@ -44,116 +42,130 @@ impl Display for AsciiErr { } } impl Error for AsciiErr {} -// This can be large so we don't implement `Copy`. -/// Container of the `COUNT` ASCII `u8`s that are allowed to appear in a [`crate::dom::Label`]. -/// Note that while [`crate::dom::Domain`] treats ASCII uppercase letters as lowercase, -/// it still depends on such `u8`s being included. For example if `b'A'` is not -/// included, then `b'A'` is not allowed even if `b'a'` is included. +/// Container of the ASCII `u8`s that are allowed to appear in a [`crate::dom::Label`]. Note that while +/// [`crate::dom::Domain`] treats ASCII uppercase letters as lowercase, it still depends on such `u8`s being +/// included. For example if `b'A'` is not included, then `b'A'` is not allowed even if `b'a'` is included. /// /// It is _highly_ unlikely that non-printable ASCII nor `b'\\'` should be used since such ASCII would almost /// certainly require being escaped. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct AllowedAscii<const COUNT: usize> { +pub struct AllowedAscii<T> { /// The allowed ASCII `u8`s. - allowed: [u8; COUNT], + allowed: T, } -impl<const COUNT: usize> AllowedAscii<COUNT> { - /// Returns `true` iff `val` is an allowed ASCII value in a [`crate::dom::Label`]. +impl<T> AllowedAscii<T> { + /// Returns a reference to the contained value. /// /// # Example /// /// ``` /// use ascii_domain::char_set; - /// assert!(char_set::ASCII_LETTERS.contains(b'a')); + /// assert!(char_set::ASCII_LETTERS.as_inner().len() == 52); /// ``` #[inline] - #[must_use] - pub fn contains(&self, val: u8) -> bool { - self.allowed.binary_search(&val).is_ok() + pub const fn as_inner(&self) -> &T { + &self.allowed } - /// Returns an [`Iterator`] of the allowed ASCII `u8`s without consuming `self`. + /// Returns the contained value consuming `self`. /// /// # Example /// /// ``` /// use ascii_domain::char_set; - /// assert!(char_set::ASCII_LETTERS.into_iter().next() == Some(b'A')); + /// assert!(char_set::ASCII_LETTERS.into_inner().len() == 52); /// ``` #[inline] - pub fn iter(&self) -> Iter<'_, u8> { - self.into_iter() - } -} -impl<const COUNT: usize> AsRef<[u8]> for AllowedAscii<COUNT> { - #[inline] - fn as_ref(&self) -> &[u8] { - self.allowed.as_slice() - } -} -impl<const COUNT: usize> AsRef<str> for AllowedAscii<COUNT> { - #[allow(unsafe_code)] - #[inline] - fn as_ref(&self) -> &str { - // SAFETY: - // `allowed` only contains ASCII which is a valid subset of `str`s. - // `AllowedAscii` does not publicly expose `allowed` and is immutable - // so this invariant will hold. - unsafe { str::from_utf8_unchecked(self.allowed.as_slice()) } + pub fn into_inner(self) -> T { + self.allowed } } -impl<const COUNT: usize> Borrow<[u8]> for AllowedAscii<COUNT> { - #[inline] - fn borrow(&self) -> &[u8] { - self.allowed.as_slice() - } -} -impl<const COUNT: usize> Borrow<str> for AllowedAscii<COUNT> { +impl<T: AsRef<[u8]>> AllowedAscii<T> { + /// Returns `true` iff `val` is an allowed ASCII value in a [`crate::dom::Label`]. + /// + /// # Example + /// + /// ``` + /// use ascii_domain::char_set; + /// assert!(char_set::ASCII_LETTERS.contains(b'a')); + /// ``` #[inline] - fn borrow(&self) -> &str { - self.as_ref() + #[must_use] + pub fn contains(&self, val: u8) -> bool { + // We sort `allowed` in `try_from_unique_ascii`, so `binary_search` is fine. + self.allowed.as_ref().binary_search(&val).is_ok() } -} -impl<const COUNT: usize> Deref for AllowedAscii<COUNT> { - type Target = [u8]; + /// Returns the number of allowed ASCII characters. + /// + /// # Example + /// + /// ``` + /// use ascii_domain::char_set; + /// assert!(char_set::ASCII_LETTERS.len() == 52); + /// ``` + #[allow(clippy::as_conversions, clippy::cast_possible_truncation)] #[inline] - fn deref(&self) -> &Self::Target { - self.allowed.as_slice() + #[must_use] + pub fn len(&self) -> u8 { + // We enforce only unique non `b'.'` ASCII in `try_from_unique_ascii` which among other things means + // the max count is 127 so truncation will not occur + self.allowed.as_ref().len() as u8 } -} -impl<const COUNT: usize> From<AllowedAscii<COUNT>> for [u8; COUNT] { + /// Returns `true` iff `self` does not contain any ASCII. + /// + /// # Examples + /// + /// ``` + /// use ascii_domain::char_set::{self, AllowedAscii}; + /// assert!(!char_set::ASCII_LETTERS.is_empty()); + /// assert!(AllowedAscii::try_from_unique_ascii([]).unwrap().is_empty()); + /// ``` #[inline] - fn from(value: AllowedAscii<COUNT>) -> Self { - value.allowed + #[must_use] + pub fn is_empty(&self) -> bool { + self.allowed.as_ref().is_empty() } } -impl<const COUNT: usize> TryFrom<[u8; COUNT]> for AllowedAscii<COUNT> { - type Error = AsciiErr; - /// `allowed` must contain unique ASCII `u8`s. Note it is likely - /// `allowed` should be a subset of [`PRINTABLE_ASCII`] since any other ASCII - /// would likely require some form of escape character logic. Additionally, - /// it is likely `COUNT` should be greater than 0; otherwise the returned `AllowedAscii` - /// will always cause [`crate::dom::Domain::try_from_bytes`] to error since `Domain` requires +impl<T: AsMut<[u8]>> AllowedAscii<T> { + /// `allowed` must contain unique ASCII `u8`s. Note it is likely `allowed` should be a subset of + /// [`PRINTABLE_ASCII`] since any other ASCII would likely require some form of escape character logic. + /// Additionally, it is likely `allowed.as_mut().len()` should be greater than 0; otherwise the returned + /// `AllowedAscii` will always cause [`crate::dom::Domain::try_from_bytes`] to error since `Domain` requires /// at least one non-root [`crate::dom::Label`]. + /// + /// `allowed` is mutated such that `allowed.as_mut()` is sorted in order. + /// + /// # Errors + /// + /// Returns `AsciiError` iff `allowed` does not contain a set of unique ASCII `u8`s or contains `b'.'`. + /// + /// # Examples + /// + /// ``` + /// use ascii_domain::char_set::{AllowedAscii, AsciiErr}; + /// assert!(AllowedAscii::try_from_unique_ascii(b"asdfghjkl".to_owned()).map_or(false, |ascii| ascii.contains(b'a') && !ascii.contains(b'A'))); + /// assert!(AllowedAscii::try_from_unique_ascii(b"aa".to_owned()).map_or_else(|err| err == AsciiErr::Duplicate(b'a'), |_| false)); + /// assert!(AllowedAscii::try_from_unique_ascii([255]).map_or_else(|err| err == AsciiErr::InvalidByte(255), |_| false)); + /// assert!(AllowedAscii::try_from_unique_ascii([0; 129]).map_or_else(|err| err == AsciiErr::CountTooLarge(129), |_| false)); + /// assert!(AllowedAscii::try_from_unique_ascii([b'.']).map_or_else(|err| err == AsciiErr::Contains46, |_| false)); + /// ``` #[inline] - fn try_from(mut allowed: [u8; COUNT]) -> Result<Self, Self::Error> { - if COUNT > 128 { - Err(AsciiErr::CountTooLarge) + pub fn try_from_unique_ascii(mut allowed: T) -> Result<Self, AsciiErr> { + let bytes = allowed.as_mut(); + if bytes.len() > 127 { + Err(AsciiErr::CountTooLarge(bytes.len())) } else { - // We must sort in order for `contains` to use `binary_search`. - allowed.sort_unstable(); - // Since `allowed` is sorted, we simply have to check the last value - // to determine if valid ASCII was provided. - if let Some(byt) = allowed.last() { + bytes.sort_unstable(); + // Since `bytes` is sorted, we simply have to check the last value to determine if valid ASCII was + // provided. + if let Some(byt) = bytes.last() { let b = *byt; if b > 127 { return Err(AsciiErr::InvalidByte(b)); } } - allowed + bytes .iter() - // Since `b'.'` is not allowed, we can use it as an initializer. - // Note this means `byt == b'.'` must be the first if-branch. - .try_fold(b'.', |prev, b| { + // 255 is not valid ASCII, so we can use it as an initializer. + .try_fold(255, |prev, b| { let byt = *b; if byt == b'.' { Err(AsciiErr::Contains46) @@ -167,110 +179,10 @@ impl<const COUNT: usize> TryFrom<[u8; COUNT]> for AllowedAscii<COUNT> { } } } -impl<const COUNT: usize> IntoIterator for AllowedAscii<COUNT> { - type Item = u8; - type IntoIter = IntoIter<u8, COUNT>; - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.allowed.into_iter() - } -} -impl<'a, const COUNT: usize> IntoIterator for &'a AllowedAscii<COUNT> { - type Item = &'a u8; - type IntoIter = Iter<'a, u8>; - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.allowed.iter() - } -} -impl<const COUNT: usize> PartialEq<AllowedAscii<COUNT>> for &AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &AllowedAscii<COUNT>) -> bool { - **self == *other - } -} -impl<const COUNT: usize> PartialEq<&Self> for AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &&Self) -> bool { - *self == **other - } -} -impl<const COUNT: usize> PartialEq<[u8]> for AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &[u8]) -> bool { - *self.allowed.as_slice() == *other - } -} -impl<const COUNT: usize> PartialEq<AllowedAscii<COUNT>> for [u8] { - #[inline] - fn eq(&self, other: &AllowedAscii<COUNT>) -> bool { - *self == *other.allowed.as_slice() - } -} -impl<const COUNT: usize> PartialEq<[u8]> for &AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &[u8]) -> bool { - *self.allowed.as_slice() == *other - } -} -impl<const COUNT: usize> PartialEq<&AllowedAscii<COUNT>> for [u8] { - #[inline] - fn eq(&self, other: &&AllowedAscii<COUNT>) -> bool { - *self == *other.allowed.as_slice() - } -} -impl<const COUNT: usize> PartialEq<&[u8]> for AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &&[u8]) -> bool { - *self.allowed.as_slice() == **other - } -} -impl<const COUNT: usize> PartialEq<AllowedAscii<COUNT>> for &[u8] { - #[inline] - fn eq(&self, other: &AllowedAscii<COUNT>) -> bool { - **self == *other.allowed.as_slice() - } -} -impl<const COUNT: usize> PartialEq<[u8; COUNT]> for AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &[u8; COUNT]) -> bool { - *self.allowed.as_slice() == *other.as_slice() - } -} -impl<const COUNT: usize> PartialEq<AllowedAscii<COUNT>> for [u8; COUNT] { - #[inline] - fn eq(&self, other: &AllowedAscii<COUNT>) -> bool { - *self.as_slice() == *other.allowed.as_slice() - } -} -impl<const COUNT: usize> PartialEq<[u8; COUNT]> for &AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &[u8; COUNT]) -> bool { - *self.allowed.as_slice() == *other.as_slice() - } -} -impl<const COUNT: usize> PartialEq<&AllowedAscii<COUNT>> for [u8; COUNT] { - #[inline] - fn eq(&self, other: &&AllowedAscii<COUNT>) -> bool { - *self.as_slice() == *other.allowed.as_slice() - } -} -impl<const COUNT: usize> PartialEq<&[u8; COUNT]> for AllowedAscii<COUNT> { - #[inline] - fn eq(&self, other: &&[u8; COUNT]) -> bool { - *self.allowed.as_slice() == *other.as_slice() - } -} -impl<const COUNT: usize> PartialEq<AllowedAscii<COUNT>> for &[u8; COUNT] { - #[inline] - fn eq(&self, other: &AllowedAscii<COUNT>) -> bool { - *self.as_slice() == *other.allowed.as_slice() - } -} /// Printable ASCII that should not need to be "escaped". That is to say /// printable ASCII excluding space (i.e., 32), dot (i.e. 46), and backslash (i.e., 92). /// This returns all `u8`s inclusively between 33 and 126 except 46 and 92. -pub const PRINTABLE_ASCII: AllowedAscii<92> = AllowedAscii { +pub const PRINTABLE_ASCII: AllowedAscii<[u8; 92]> = AllowedAscii { allowed: [ b'!', b'"', b'#', b'$', b'%', b'&', b'\'', b'(', b')', b'*', b'+', b',', b'-', b'/', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', @@ -285,7 +197,7 @@ pub const PRINTABLE_ASCII: AllowedAscii<92> = AllowedAscii { /// This contains the following `u8`s: /// /// 33, 35–39, 42–43, 45, 47–57, 61, 63, 65–90, and 94–126. -pub const RFC5322_ATEXT: AllowedAscii<81> = AllowedAscii { +pub const RFC5322_ATEXT: AllowedAscii<[u8; 81]> = AllowedAscii { allowed: [ b'!', b'#', b'$', b'%', b'&', b'\'', b'*', b'+', b'-', b'/', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'=', b'?', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', @@ -300,7 +212,7 @@ pub const RFC5322_ATEXT: AllowedAscii<81> = AllowedAscii { /// This contains the following `u8`s: /// /// 33, 36, 38–41, 43–45, 48–57, 59, 61, 65–90, 95–123, and 125–126. -pub const ASCII_FIREFOX: AllowedAscii<78> = AllowedAscii { +pub const ASCII_FIREFOX: AllowedAscii<[u8; 78]> = AllowedAscii { allowed: [ b'!', b'$', b'&', b'\'', b'(', b')', b'+', b',', b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b';', b'=', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', @@ -312,7 +224,7 @@ pub const ASCII_FIREFOX: AllowedAscii<78> = AllowedAscii { }; /// ASCII hyphen, digits, and letters. /// This contains 45 and all `u8`s inclusively between 48 and 57, 65 and 90, and 97 and 122. -pub const ASCII_HYPHEN_DIGITS_LETTERS: AllowedAscii<63> = AllowedAscii { +pub const ASCII_HYPHEN_DIGITS_LETTERS: AllowedAscii<[u8; 63]> = AllowedAscii { allowed: [ b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', @@ -323,7 +235,7 @@ pub const ASCII_HYPHEN_DIGITS_LETTERS: AllowedAscii<63> = AllowedAscii { }; /// ASCII digits and letters. /// This contains all `u8`s inclusively between 48 and 57, 65 and 90, and 97 and 122. -pub const ASCII_DIGITS_LETTERS: AllowedAscii<62> = AllowedAscii { +pub const ASCII_DIGITS_LETTERS: AllowedAscii<[u8; 62]> = AllowedAscii { allowed: [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', @@ -334,7 +246,7 @@ pub const ASCII_DIGITS_LETTERS: AllowedAscii<62> = AllowedAscii { }; /// ASCII letters. /// This contains all `u8`s inclusively between 65 and 90 and 97 and 122. -pub const ASCII_LETTERS: AllowedAscii<52> = AllowedAscii { +pub const ASCII_LETTERS: AllowedAscii<[u8; 52]> = AllowedAscii { allowed: [ b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', @@ -344,7 +256,7 @@ pub const ASCII_LETTERS: AllowedAscii<52> = AllowedAscii { }; /// ASCII hyphen, digits, and uppercase letters. /// This contains 45 and all `u8`s inclusively between 48 and 57 and 65 and 90. -pub const ASCII_HYPHEN_DIGITS_UPPERCASE: AllowedAscii<37> = AllowedAscii { +pub const ASCII_HYPHEN_DIGITS_UPPERCASE: AllowedAscii<[u8; 37]> = AllowedAscii { allowed: [ b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', @@ -353,7 +265,7 @@ pub const ASCII_HYPHEN_DIGITS_UPPERCASE: AllowedAscii<37> = AllowedAscii { }; /// ASCII hyphen, digits, and lowercase letters. /// This contains 45 and all `u8`s inclusively between 48 and 57 and 97 and 122. -pub const ASCII_HYPHEN_DIGITS_LOWERCASE: AllowedAscii<37> = AllowedAscii { +pub const ASCII_HYPHEN_DIGITS_LOWERCASE: AllowedAscii<[u8; 37]> = AllowedAscii { allowed: [ b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', @@ -362,7 +274,7 @@ pub const ASCII_HYPHEN_DIGITS_LOWERCASE: AllowedAscii<37> = AllowedAscii { }; /// ASCII digits and uppercase letters. /// This contains all `u8`s inclusively between 48 and 57 and 65 and 90. -pub const ASCII_DIGITS_UPPERCASE: AllowedAscii<36> = AllowedAscii { +pub const ASCII_DIGITS_UPPERCASE: AllowedAscii<[u8; 36]> = AllowedAscii { allowed: [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', @@ -371,7 +283,7 @@ pub const ASCII_DIGITS_UPPERCASE: AllowedAscii<36> = AllowedAscii { }; /// ASCII digits and lowercase letters. /// This contains all `u8`s inclusively between 48 and 57 and 97 and 122. -pub const ASCII_DIGITS_LOWERCASE: AllowedAscii<36> = AllowedAscii { +pub const ASCII_DIGITS_LOWERCASE: AllowedAscii<[u8; 36]> = AllowedAscii { allowed: [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', @@ -380,7 +292,7 @@ pub const ASCII_DIGITS_LOWERCASE: AllowedAscii<36> = AllowedAscii { }; /// ASCII uppercase letters. /// This contains all `u8`s inclusively between 65 and 90. -pub const ASCII_UPPERCASE: AllowedAscii<26> = AllowedAscii { +pub const ASCII_UPPERCASE: AllowedAscii<[u8; 26]> = AllowedAscii { allowed: [ b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', @@ -388,7 +300,7 @@ pub const ASCII_UPPERCASE: AllowedAscii<26> = AllowedAscii { }; /// ASCII lowercase letters. /// This contains all `u8`s inclusively between 97 and 122. -pub const ASCII_LOWERCASE: AllowedAscii<26> = AllowedAscii { +pub const ASCII_LOWERCASE: AllowedAscii<[u8; 26]> = AllowedAscii { allowed: [ b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', @@ -396,7 +308,7 @@ pub const ASCII_LOWERCASE: AllowedAscii<26> = AllowedAscii { }; /// ASCII digits. /// This contains all `u8`s inclusively between 48 and 57. -pub const ASCII_DIGITS: AllowedAscii<10> = AllowedAscii { +pub const ASCII_DIGITS: AllowedAscii<[u8; 10]> = AllowedAscii { allowed: [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9'], }; #[cfg(test)] @@ -410,15 +322,25 @@ mod tests { #[test] fn try_from() { // Empty is allowed. - assert!(AllowedAscii::try_from([]).is_ok()); + assert!(AllowedAscii::try_from_unique_ascii([]).is_ok()); // Duplicates are not allowed. - assert!(AllowedAscii::try_from(b"aba".to_owned()) + assert!(AllowedAscii::try_from_unique_ascii(b"aba".to_owned()) .map_or_else(|e| e == AsciiErr::Duplicate(b'a'), |_| false)); // `b'.'` is not allowed. - assert!(AllowedAscii::try_from(b"a.c".to_owned()) + assert!(AllowedAscii::try_from_unique_ascii(b"a.c".to_owned()) .map_or_else(|e| e == AsciiErr::Contains46, |_| false)); + // At most 127 bytes are allowed. + assert!(AllowedAscii::try_from_unique_ascii([0; 128]) + .map_or_else(|e| e == AsciiErr::CountTooLarge(128), |_| false)); + let mut all_ascii = (0..b'.').collect::<Vec<u8>>(); + let next = b'.' + 1; + all_ascii.extend(next..=127); + assert!(AllowedAscii::try_from_unique_ascii(all_ascii).is_ok()); + // Only ASCII is allowed. + assert!(AllowedAscii::try_from_unique_ascii([255]) + .map_or_else(|e| e == AsciiErr::InvalidByte(255), |_| false)); assert!( - AllowedAscii::try_from(b"abcdef".to_owned()).map_or(false, |bytes| bytes + AllowedAscii::try_from_unique_ascii(b"abcdef".to_owned()).map_or(false, |bytes| bytes .contains(b'a') && bytes.contains(b'b') && bytes.contains(b'c') diff --git a/src/dom.rs b/src/dom.rs @@ -1,4 +1,4 @@ -use crate::char_set::{AllowedAscii, ASCII_HYPHEN_DIGITS_LETTERS}; +use crate::char_set::AllowedAscii; use core::{ borrow::Borrow, cmp::Ordering, @@ -53,82 +53,10 @@ impl From<DomainOrdering> for Ordering { } } } -/// A flag used to indicate information about the characters in a [`Domain`]. This flag is used -/// to perform more efficient comparisons that can potentially avoid temporary memory allocations -/// to treat uppercase letters as if they were lowercase. -/// -/// The reason `b'\\'`, `b']'`, `b'^'`, `b'_'`, and `` b'`' `` need to be tracked in addition to letters -/// is to ensure uppercase letters are considered greater since lowercase letters are. As the documentation -/// of `Domain` states, "uppercase letters are treated as lowercase". -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -enum CharFlag { - /// No ASCII letters, `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``. - None = 0, - /// ASCII lowercase letters but no uppercase letters, `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``. - Lower = 1, - /// ASCII uppercase letters but no lowercase letters, `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``. - Upper = 2, - /// ASCII lowercase and uppercase letters but no `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``. - LowerUpper = 3, - /// 'b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``; but no ASCII letters. - Between = 4, - /// ASCII lowercase letters and `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``; but no uppercase letters. - LowerBetween = 5, - /// ASCII uppercase letters and `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``; but no lowercase letters. - UpperBetween = 6, - /// ASCII lowercase letters, uppercase letters, and `b'\\'`, `b']'`, `b'^'`, `b'_'`, or `` b'`' ``. - All = 7, -} -impl CharFlag { - /// Returns a `bool` that indicates if equivalence must be done in a case insensitive way. - #[inline] - #[must_use] - const fn eq_ignore_case(self, other: Self) -> bool { - match self { - Self::None | Self::Between => false, - Self::Lower | Self::LowerBetween => !matches!( - other, - Self::None | Self::Between | Self::Lower | Self::LowerBetween - ), - Self::Upper | Self::UpperBetween => !matches!( - other, - Self::None | Self::Between | Self::Upper | Self::UpperBetween - ), - Self::LowerUpper | Self::All => !matches!(other, Self::None | Self::Between), - } - } -} -impl Display for CharFlag { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match *self { - Self::None => f.write_str("No ASCII letters, '\\', ']', '^', '_', or '`'"), - Self::Lower => f.write_str( - "ASCII lowercase letters; but no uppercase letters, '\\', ']', '^', '_', or '`'", - ), - Self::Upper => f.write_str( - "ASCII uppercase letters; but no lowercase letters, '\\', ']', '^', '_', or '`'", - ), - Self::Between => f.write_str("ASCII '\\', ']', '^', '_', or '`'; but no letters"), - Self::LowerUpper => f.write_str( - "ASCII lowercase and uppercase letters; but no '\\', ']', '^', '_', or '`'", - ), - Self::LowerBetween => f.write_str( - "ASCII lowercase letters and '\\', ']', '^', '_', or '`'; but no uppercase letters", - ), - Self::UpperBetween => f.write_str( - "ASCII uppercase letters and '\\', ']', '^', '_', or '`'; but no lowercase letters", - ), - Self::All => f.write_str( - "ASCII lowercase letters, uppercase letters, and '\\', ']', '^', '_', or '`'", - ), - } - } -} /// A domain that consists of at least one [`Label`]. With each `Label` only containing the ASCII `u8`s in -/// [`Self::allowed_ascii`]. The total length of a `Domain` is at most 253 bytes[^note] in length including the -/// `b'.'` separator. The trailing `b'.'`, if one exists, is always ignored. +/// the [`AllowedAscii`] passed to [`Self::try_from_bytes`]. The total length of a `Domain` is at most +/// 253 bytes[^note] in length including the `b'.'` separator. The trailing `b'.'`, if one exists, is always +/// ignored. /// /// This is more restrictive than what a domain is allowed to be per the /// [Domain Name System (DNS)](https://www.rfc-editor.org/rfc/rfc2181) since all octets/`u8`s are allowed in a @@ -144,22 +72,18 @@ impl Display for CharFlag { /// 254. This is due to the fact that there is no way to explicitly represent the root label which in wire format /// contributes one byte due to each label being preceded by the octet that represents its length. #[derive(Clone, Debug)] -pub struct Domain<'a, const ALLOWED_COUNT: usize, T> { +pub struct Domain<T> { /// The domain value. `value.as_ref().len()` is guaranteed to have length between 1 and 253 when the last `u8` /// is not `b'.'`; otherwise the length is between 2 and 254. /// Guaranteed to only contain `b'.'` and the ASCII `u8`s in `allowed_ascii`. value: T, - /// The allowed ASCII `u8`s a `Label` can have. - allowed_ascii: &'a AllowedAscii<ALLOWED_COUNT>, /// The lengths of each label. Guaranteed to have length between 1 and 127 with each value being /// between 1 and 63. /// The sum of each value plus the length is guaranteed to be 1 greater than `value.as_ref().len()` when /// the last `u8` in `value` is not `b'.'`; otherwise it will be the same. label_lens: Vec<NonZeroU8>, - /// Flag that contains information about the kind of ASCII `u8`s in `value`. - flag: CharFlag, } -impl<'a, 'b, const ALLOWED_COUNT: usize> Domain<'a, ALLOWED_COUNT, &'b [u8]> { +impl<'a> Domain<&'a [u8]> { /// Same as [`Self::as_bytes`] except the lifetime is tied to the slice and not `self`. /// /// # Example @@ -171,13 +95,13 @@ impl<'a, 'b, const ALLOWED_COUNT: usize> Domain<'a, ALLOWED_COUNT, &'b [u8]> { #[allow(clippy::as_conversions, clippy::indexing_slicing)] #[inline] #[must_use] - pub fn domain_without_trailing_dot(&self) -> &'b [u8] { + pub fn domain_without_trailing_dot(&self) -> &'a [u8] { // `self.len().get() as usize` is fine since it's a positive `u8`. // Indexing won't `panic` since `self.len()` is at most as long as `self.value`. &self.value[..self.len().get() as usize] } } -impl<'a, 'b, const ALLOWED_COUNT: usize> Domain<'a, ALLOWED_COUNT, &'b str> { +impl<'a> Domain<&'a str> { /// Same as [`Self::as_str`] except the lifetime is tied to the `str` and not `self`. /// /// # Example @@ -189,7 +113,7 @@ impl<'a, 'b, const ALLOWED_COUNT: usize> Domain<'a, ALLOWED_COUNT, &'b str> { #[allow(unsafe_code, clippy::as_conversions, clippy::indexing_slicing)] #[inline] #[must_use] - pub fn domain_without_trailing_dot(&self) -> &'b str { + pub fn domain_without_trailing_dot(&self) -> &'a str { // `self.len().get() as usize` is fine since it's a positive `u8`. // Indexing won't `panic` since `self.len()` is at most as long as `self.value`. let utf8 = &self.value.as_bytes()[..self.len().get() as usize]; @@ -198,67 +122,51 @@ impl<'a, 'b, const ALLOWED_COUNT: usize> Domain<'a, ALLOWED_COUNT, &'b str> { unsafe { str::from_utf8_unchecked(utf8) } } } -impl<'a: 'b, 'b, const ALLOWED_COUNT: usize> From<Domain<'a, ALLOWED_COUNT, Vec<u8>>> - for Domain<'b, ALLOWED_COUNT, String> -{ +impl From<Domain<Vec<u8>>> for Domain<String> { #[allow(unsafe_code)] #[inline] - fn from(value: Domain<'a, ALLOWED_COUNT, Vec<u8>>) -> Self { + fn from(value: Domain<Vec<u8>>) -> Self { // SAFETY: // We only allow ASCII, so this is fine. let val = unsafe { String::from_utf8_unchecked(value.value) }; Self { value: val, - allowed_ascii: value.allowed_ascii, label_lens: value.label_lens, - flag: value.flag, } } } -impl<'a: 'b, 'b, const ALLOWED_COUNT: usize> From<Domain<'a, ALLOWED_COUNT, String>> - for Domain<'b, ALLOWED_COUNT, Vec<u8>> -{ +impl From<Domain<String>> for Domain<Vec<u8>> { #[inline] - fn from(value: Domain<'a, ALLOWED_COUNT, String>) -> Self { + fn from(value: Domain<String>) -> Self { Self { value: value.value.into_bytes(), - allowed_ascii: value.allowed_ascii, label_lens: value.label_lens, - flag: value.flag, } } } -impl<'a: 'b, 'b, 'c: 'd, 'd, const ALLOWED_COUNT: usize> From<Domain<'a, ALLOWED_COUNT, &'c [u8]>> - for Domain<'b, ALLOWED_COUNT, &'d str> -{ +impl<'a: 'b, 'b> From<Domain<&'a [u8]>> for Domain<&'b str> { #[allow(unsafe_code)] #[inline] - fn from(value: Domain<'a, ALLOWED_COUNT, &'c [u8]>) -> Self { + fn from(value: Domain<&'a [u8]>) -> Self { // SAFETY: // We only allow ASCII, so this is fine. let val = unsafe { str::from_utf8_unchecked(value.value) }; Self { value: val, - allowed_ascii: value.allowed_ascii, label_lens: value.label_lens, - flag: value.flag, } } } -impl<'a: 'b, 'b, 'c: 'd, 'd, const ALLOWED_COUNT: usize> From<Domain<'a, ALLOWED_COUNT, &'c str>> - for Domain<'b, ALLOWED_COUNT, &'d [u8]> -{ +impl<'a: 'b, 'b> From<Domain<&'a str>> for Domain<&'b [u8]> { #[inline] - fn from(value: Domain<'a, ALLOWED_COUNT, &'c str>) -> Self { + fn from(value: Domain<&'a str>) -> Self { Self { value: value.value.as_bytes(), - allowed_ascii: value.allowed_ascii, label_lens: value.label_lens, - flag: value.flag, } } } -impl<'a, const ALLOWED_COUNT: usize, T> Domain<'a, ALLOWED_COUNT, T> { +impl<T> Domain<T> { /// The maximum length of a `Domain` which is 253. // SAFETY: 0 < 253 < 256. #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)] @@ -309,20 +217,8 @@ impl<'a, const ALLOWED_COUNT: usize, T> Domain<'a, ALLOWED_COUNT, T> { pub fn into_inner(self) -> T { self.value } - /// Returns the ASCII `u8`s that are allowed in a [`Label`]. - /// - /// # Example - /// - /// ``` - /// use ascii_domain::{dom::Domain, char_set::ASCII_LOWERCASE}; - /// assert!(Domain::try_from_bytes("example.com", &ASCII_LOWERCASE).unwrap().allowed_ascii() == ASCII_LOWERCASE); - /// ``` - #[inline] - pub const fn allowed_ascii(&self) -> &'a AllowedAscii<ALLOWED_COUNT> { - self.allowed_ascii - } } -impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> Domain<T> { /// Returns `true` iff the domain contains a trailing `b'.'`. /// /// # Example @@ -420,13 +316,12 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T clippy::arithmetic_side_effects, clippy::as_conversions, clippy::indexing_slicing, - clippy::into_iter_on_ref, - clippy::unreachable + clippy::into_iter_on_ref )] #[inline] - pub fn try_from_bytes<'b: 'a>( + pub fn try_from_bytes<T2: AsRef<[u8]>>( v: T, - allowed_ascii: &'b AllowedAscii<ALLOWED_COUNT>, + allowed_ascii: &AllowedAscii<T2>, ) -> Result<Self, DomainErr> { let val = v.as_ref(); let value = match val.last() { @@ -451,32 +346,19 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T } else { let mut label_lens = Vec::with_capacity(3); let mut label_len = 0; - // This flag is the `u8` representation of `CharFlag`. - let mut flag = 0u8; value .into_iter() .try_fold((), |(), byt| { let b = *byt; if b == b'.' { - return NonZeroU8::new(label_len).map_or( - Err(DomainErr::EmptyLabel), - |length| { - label_lens.push(length); - label_len = 0; - Ok(()) - }, - ); - } else if allowed_ascii.contains(b) { - match b { - b'A'..=b'Z' => flag |= 2, - b'['..=b'`' => flag |= 4, - b'a'..=b'z' => flag |= 1, - _ => (), - } - } else { - return Err(DomainErr::InvalidByte(b)); - } - if label_len == 63 { + NonZeroU8::new(label_len).map_or(Err(DomainErr::EmptyLabel), |length| { + label_lens.push(length); + label_len = 0; + Ok(()) + }) + } else if !allowed_ascii.contains(b) { + Err(DomainErr::InvalidByte(b)) + } else if label_len == 63 { Err(DomainErr::LabelLenExceeds63) } else { // This is less than 63 due to the above check, so this won't overflow; @@ -491,19 +373,7 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T label_lens.push(length); Self { value: v, - allowed_ascii, label_lens, - flag: match flag { - 0 => CharFlag::None, - 1 => CharFlag::Lower, - 2 => CharFlag::Upper, - 3 => CharFlag::LowerUpper, - 4 => CharFlag::Between, - 5 => CharFlag::LowerBetween, - 6 => CharFlag::UpperBetween, - 7 => CharFlag::All, - _ => unreachable!("there is a bug in Domain::try_from_bytes"), - }, } }) }) @@ -517,7 +387,7 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T /// assert!(Domain::try_from_bytes("example.com", &ASCII_LOWERCASE).unwrap().iter().next().unwrap().as_str() == "com"); /// ``` #[inline] - pub fn iter(&self) -> LabelIter<'_, '_, ALLOWED_COUNT, T> { + pub fn iter(&self) -> LabelIter<T> { LabelIter::new(self) } /// Returns `true` iff `self` and `right` are part of the same branch in the DNS hierarchy. @@ -538,41 +408,17 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T /// assert!(!dom1.same_branch(&dom3)); /// ``` #[inline] - pub fn same_branch<const ALLOWED_COUNT2: usize, T2: AsRef<[u8]>>( - &self, - right: &Domain<ALLOWED_COUNT2, T2>, - ) -> bool { - /// Compares `left` and `right` ignoring ASCII case such that `Ok` is returned iff `true` - /// and `Err` is returned iff `false`. - #[inline] - fn eq_ignore(left: &str, right: &str) -> Result<(), ()> { - if left.eq_ignore_ascii_case(right) { - Ok(()) - } else { - Err(()) - } - } - /// Compares `left` and `right` such that `Ok` is returned iff `true` and `Err` is returned iff `false`. - #[inline] - fn eq(left: &str, right: &str) -> Result<(), ()> { - if left == right { - Ok(()) - } else { - Err(()) - } - } + pub fn same_branch<T2: AsRef<[u8]>>(&self, right: &Domain<T2>) -> bool { // Faster to check the values as bytes and not iterate each `Label`. if self == right { true } else { - let f = if self.flag.eq_ignore_case(right.flag) { - eq_ignore - } else { - eq - }; self.into_iter() .zip(right) - .try_fold((), |(), (label, label2)| f(label.value, label2.value)) + .try_fold( + (), + |(), (label, label2)| if label == label2 { Ok(()) } else { Err(()) }, + ) .map_or(false, |()| true) } } @@ -601,151 +447,30 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T /// assert!(matches!(dom1.cmp_by_domain_ordering(&dom3), DomainOrdering::Less)); /// assert!(matches!(dom3.cmp_by_domain_ordering(&dom1), DomainOrdering::Greater)); /// ``` - #[allow(clippy::too_many_lines, clippy::unreachable)] + #[allow(clippy::unreachable)] #[inline] - pub fn cmp_by_domain_ordering<const ALLOWED_COUNT2: usize, T2: AsRef<[u8]>>( - &self, - right: &Domain<ALLOWED_COUNT2, T2>, - ) -> DomainOrdering { - // Faster to compare the entire value when we can. + pub fn cmp_by_domain_ordering<T2: AsRef<[u8]>>(&self, right: &Domain<T2>) -> DomainOrdering { + // Faster to compare the entire value when we can instead each `Label`. if self == right { - return DomainOrdering::Equal; - } - let left_input; - let right_input; - let left_dom; - let right_dom; - // We try to avoid needless converting to lowercase or uppercase. - // Note that `CharFlag` does not need to be perfect. It only needs to be correct "enough" for comparisons. - // For example, if we convert a `Domain` that contains uppercase letters to lowercase; and we compare - // said `Domain` to one that only contains lowercase letters, `CharFlag::Lower`, `CharFlag::Between`, - // and `CharFlag::LowerBetween` all will lead to a correct comparison. - let (left_ref, right_ref) = match (self.flag, right.flag) { - (CharFlag::None, _) - | (_, CharFlag::None) - | ( - CharFlag::Lower | CharFlag::Between | CharFlag::LowerBetween, - CharFlag::Lower | CharFlag::Between | CharFlag::LowerBetween, - ) - | (CharFlag::Upper, CharFlag::Upper) => { - left_dom = Domain { - value: self.value.as_ref(), - allowed_ascii: self.allowed_ascii, - label_lens: self.label_lens.clone(), - flag: self.flag, - }; - right_dom = Domain { - value: right.value.as_ref(), - allowed_ascii: right.allowed_ascii, - label_lens: right.label_lens.clone(), - flag: right.flag, - }; - (&left_dom, &right_dom) - } - (CharFlag::Lower | CharFlag::LowerBetween | CharFlag::Between, _) => { - left_dom = Domain { - value: self.value.as_ref(), - allowed_ascii: self.allowed_ascii, - label_lens: self.label_lens.clone(), - flag: self.flag, - }; - right_input = right.value.as_ref().to_ascii_lowercase(); - right_dom = Domain { - value: right_input.as_ref(), - allowed_ascii: right.allowed_ascii, - label_lens: right.label_lens.clone(), - flag: CharFlag::LowerBetween, - }; - (&left_dom, &right_dom) - } - (CharFlag::Upper, CharFlag::LowerUpper) => { - left_dom = Domain { - value: self.value.as_ref(), - allowed_ascii: self.allowed_ascii, - label_lens: self.label_lens.clone(), - flag: self.flag, - }; - right_input = right.value.as_ref().to_ascii_uppercase(); - right_dom = Domain { - value: right_input.as_ref(), - allowed_ascii: right.allowed_ascii, - label_lens: right.label_lens.clone(), - flag: CharFlag::Upper, - }; - (&left_dom, &right_dom) - } - (_, CharFlag::Lower | CharFlag::Between | CharFlag::LowerBetween) => { - left_input = self.value.as_ref().to_ascii_lowercase(); - left_dom = Domain { - value: left_input.as_ref(), - allowed_ascii: self.allowed_ascii, - label_lens: self.label_lens.clone(), - flag: CharFlag::LowerBetween, - }; - right_dom = Domain { - value: right.value.as_ref(), - allowed_ascii: right.allowed_ascii, - label_lens: right.label_lens.clone(), - flag: right.flag, - }; - (&left_dom, &right_dom) - } - (CharFlag::LowerUpper, CharFlag::Upper) => { - left_input = self.value.as_ref().to_ascii_uppercase(); - left_dom = Domain { - value: left_input.as_ref(), - allowed_ascii: self.allowed_ascii, - label_lens: self.label_lens.clone(), - flag: CharFlag::Upper, - }; - right_dom = Domain { - value: right.value.as_ref(), - allowed_ascii: right.allowed_ascii, - label_lens: right.label_lens.clone(), - flag: right.flag, - }; - (&left_dom, &right_dom) - } - (_, _) => { - left_input = self.value.as_ref().to_ascii_lowercase(); - left_dom = Domain { - value: left_input.as_ref(), - allowed_ascii: self.allowed_ascii, - label_lens: self.label_lens.clone(), - flag: CharFlag::LowerBetween, - }; - right_input = right.value.as_ref().to_ascii_lowercase(); - right_dom = Domain { - value: right_input.as_ref(), - allowed_ascii: right.allowed_ascii, - label_lens: right.label_lens.clone(), - flag: CharFlag::LowerBetween, - }; - (&left_dom, &right_dom) - } - }; - left_ref - .into_iter() - .zip(right_ref) - .try_fold((), |(), (label, label2)| { - // We don't want to use `Label::cmp` since that always converts both `Label`s to - // lowercase since it is unaware of `CharFlag`s. This is fine due to above where - // we did the conversion already. - match label.value.cmp(label2.value) { + DomainOrdering::Equal + } else { + self.into_iter() + .zip(right) + .try_fold((), |(), (label, label2)| match label.cmp(&label2) { Ordering::Less => Err(DomainOrdering::Less), Ordering::Equal => Ok(()), Ordering::Greater => Err(DomainOrdering::Greater), - } - }) - .map_or_else(convert::identity, |()| { - match self.label_count().cmp(&right.label_count()) { - Ordering::Less => DomainOrdering::Shorter, - Ordering::Equal => { - unreachable!("there is a bug in Domain::cmp_by_domain_ordering") + }) + .map_or_else(convert::identity, |()| { + match self.label_count().cmp(&right.label_count()) { + Ordering::Less => DomainOrdering::Shorter, + Ordering::Equal => { + unreachable!("there is a bug in Domain::cmp_by_domain_ordering") + } + Ordering::Greater => DomainOrdering::Longer, } - Ordering::Greater => DomainOrdering::Longer, - } - }) + }) + } } /// The total order that is defined follows the following hierarchy: /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs. @@ -773,10 +498,7 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T /// assert!(matches!(dom3.cmp_doms(&dom1), Ordering::Greater)); /// ``` #[inline] - pub fn cmp_doms<const ALLOWED_COUNT2: usize, T2: AsRef<[u8]>>( - &self, - right: &Domain<ALLOWED_COUNT2, T2>, - ) -> Ordering { + pub fn cmp_doms<T2: AsRef<[u8]>>(&self, right: &Domain<T2>) -> Ordering { self.cmp_by_domain_ordering(right).into() } /// Returns the last `Label` (i.e., the TLD). @@ -795,118 +517,73 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Domain<'a, ALLOWED_COUNT, T .unwrap_or_else(|| unreachable!("there is a bug in Domain::try_from_bytes")) } } -impl< - 'a, - 'b, - const ALLOWED_COUNT: usize, - const ALLOWED_COUNT2: usize, - T: AsRef<[u8]>, - T2: AsRef<[u8]>, - > PartialEq<Domain<'a, ALLOWED_COUNT, T>> for Domain<'b, ALLOWED_COUNT2, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Domain<T>> for Domain<T2> { /// Ignores the provided [`AllowedAscii`] and simply compares the two `Domain`s as [`Label`]s /// of bytes. Note uppercase ASCII is treated as lowercase ASCII and trailing `b'.'`s are ignored. #[inline] - fn eq(&self, other: &Domain<ALLOWED_COUNT, T>) -> bool { - if self.flag.eq_ignore_case(other.flag) { - self.as_bytes().eq_ignore_ascii_case(other.as_bytes()) - } else { - self.as_bytes() == other.as_bytes() - } + fn eq(&self, other: &Domain<T>) -> bool { + self.as_bytes().eq_ignore_ascii_case(other.as_bytes()) } } -impl< - 'a, - 'b, - const ALLOWED_COUNT: usize, - const ALLOWED_COUNT2: usize, - T: AsRef<[u8]>, - T2: AsRef<[u8]>, - > PartialEq<&Domain<'a, ALLOWED_COUNT, T>> for Domain<'b, ALLOWED_COUNT2, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Domain<T>> for Domain<T2> { #[inline] - fn eq(&self, other: &&Domain<'a, ALLOWED_COUNT, T>) -> bool { + fn eq(&self, other: &&Domain<T>) -> bool { *self == **other } } -impl< - 'a, - 'b, - const ALLOWED_COUNT: usize, - const ALLOWED_COUNT2: usize, - T: AsRef<[u8]>, - T2: AsRef<[u8]>, - > PartialEq<Domain<'a, ALLOWED_COUNT, T>> for &Domain<'b, ALLOWED_COUNT2, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Domain<T>> for &Domain<T2> { #[inline] - fn eq(&self, other: &Domain<'a, ALLOWED_COUNT, T>) -> bool { + fn eq(&self, other: &Domain<T>) -> bool { **self == *other } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Eq for Domain<'_, ALLOWED_COUNT, T> {} -impl< - 'a, - 'b, - const ALLOWED_COUNT: usize, - const ALLOWED_COUNT2: usize, - T: AsRef<[u8]>, - T2: AsRef<[u8]>, - > PartialOrd<Domain<'a, ALLOWED_COUNT, T>> for Domain<'b, ALLOWED_COUNT2, T2> -{ +impl<T: AsRef<[u8]>> Eq for Domain<T> {} +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Domain<T>> for Domain<T2> { /// Consult [`Self::cmp_doms`]. #[inline] - fn partial_cmp(&self, other: &Domain<ALLOWED_COUNT, T>) -> Option<Ordering> { + fn partial_cmp(&self, other: &Domain<T>) -> Option<Ordering> { Some(self.cmp_doms(other)) } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Ord for Domain<'_, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> Ord for Domain<T> { /// Consult [`Self::cmp_doms`]. #[inline] fn cmp(&self, other: &Self) -> Ordering { self.cmp_doms(other) } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Hash for Domain<'_, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> Hash for Domain<T> { #[inline] fn hash<H: Hasher>(&self, state: &mut H) { - match self.flag { - CharFlag::None | CharFlag::Lower | CharFlag::Between | CharFlag::LowerBetween => { - self.as_bytes().hash(state); - } - CharFlag::Upper | CharFlag::LowerUpper | CharFlag::UpperBetween | CharFlag::All => { - self.as_bytes().to_ascii_lowercase().hash(state); - } - } + self.as_bytes().to_ascii_lowercase().hash(state); } } -impl<'a, 'b: 'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> - TryFrom<(T, &'b AllowedAscii<ALLOWED_COUNT>)> for Domain<'a, ALLOWED_COUNT, T> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> TryFrom<(T, &AllowedAscii<T2>)> for Domain<T> { type Error = DomainErr; #[inline] - fn try_from(value: (T, &'b AllowedAscii<ALLOWED_COUNT>)) -> Result<Self, Self::Error> { + fn try_from(value: (T, &AllowedAscii<T2>)) -> Result<Self, Self::Error> { Self::try_from_bytes(value.0, value.1) } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Display for Domain<'_, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> Display for Domain<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> AsRef<str> for Domain<'_, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> AsRef<str> for Domain<T> { #[inline] fn as_ref(&self) -> &str { self.as_str() } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> AsRef<[u8]> for Domain<'_, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> AsRef<[u8]> for Domain<T> { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Deref for Domain<'_, ALLOWED_COUNT, T> { +impl<T: AsRef<[u8]>> Deref for Domain<T> { type Target = str; #[inline] fn deref(&self) -> &Self::Target { @@ -1126,9 +803,9 @@ impl<'a> Deref for Label<'a> { } } /// [`Iterator`] that iterates [`Label`]s from a borrowed [`Domain`] starting from the TLD down. -pub struct LabelIter<'a, 'b: 'a, const ALLOWED_COUNT: usize, T> { +pub struct LabelIter<'a, T> { /// Domain that contains `Label`s to iterate. - domain: &'a Domain<'b, ALLOWED_COUNT, T>, + domain: &'a Domain<T>, /// Starts at domain.label_count().get() - 1 which is valid since domain.label_count().get() > 0. /// idx is 255 when the iterator is exhausted. /// Since idx is decremented each time and it starts at a value less than 254, this is a valid value to use @@ -1146,11 +823,11 @@ pub struct LabelIter<'a, 'b: 'a, const ALLOWED_COUNT: usize, T> { /// After a label is read, 1 must be added to account for '.'. start_back: u8, } -impl<'a, 'b: 'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> LabelIter<'a, 'b, ALLOWED_COUNT, T> { +impl<'a, T: AsRef<[u8]>> LabelIter<'a, T> { /// Helper function to construct an instance. #[allow(clippy::arithmetic_side_effects)] #[inline] - fn new(domain: &'a Domain<'b, ALLOWED_COUNT, T>) -> Self { + fn new(domain: &'a Domain<T>) -> Self { Self { // This won't underflow since `label_count` is at least 1. idx: domain.label_count().get() - 1, @@ -1161,9 +838,7 @@ impl<'a, 'b: 'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> LabelIter<'a, 'b, A } } } -impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Iterator - for LabelIter<'a, '_, ALLOWED_COUNT, T> -{ +impl<'a, T: AsRef<[u8]>> Iterator for LabelIter<'a, T> { type Item = Label<'a>; #[allow( unsafe_code, @@ -1216,13 +891,8 @@ impl<'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> Iterator opt } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> FusedIterator - for LabelIter<'_, '_, ALLOWED_COUNT, T> -{ -} -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> ExactSizeIterator - for LabelIter<'_, '_, ALLOWED_COUNT, T> -{ +impl<T: AsRef<[u8]>> FusedIterator for LabelIter<'_, T> {} +impl<T: AsRef<[u8]>> ExactSizeIterator for LabelIter<'_, T> { #[allow(clippy::arithmetic_side_effects, clippy::as_conversions)] #[inline] fn len(&self) -> usize { @@ -1237,9 +907,7 @@ impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> ExactSizeIterator } } } -impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> DoubleEndedIterator - for LabelIter<'_, '_, ALLOWED_COUNT, T> -{ +impl<T: AsRef<[u8]>> DoubleEndedIterator for LabelIter<'_, T> { #[allow( unsafe_code, clippy::arithmetic_side_effects, @@ -1284,11 +952,9 @@ impl<const ALLOWED_COUNT: usize, T: AsRef<[u8]>> DoubleEndedIterator }) } } -impl<'a, 'b: 'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> IntoIterator - for &'a Domain<'b, ALLOWED_COUNT, T> -{ +impl<'a, T: AsRef<[u8]>> IntoIterator for &'a Domain<T> { type Item = Label<'a>; - type IntoIter = LabelIter<'a, 'b, ALLOWED_COUNT, T>; + type IntoIter = LabelIter<'a, T>; #[inline] fn into_iter(self) -> Self::IntoIter { LabelIter::new(self) @@ -1298,10 +964,10 @@ impl<'a, 'b: 'a, const ALLOWED_COUNT: usize, T: AsRef<[u8]>> IntoIterator #[allow(clippy::exhaustive_enums)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Rfc1123Err { - /// [`Domain::allowed_ascii`] is not [`ASCII_HYPHEN_DIGITS_LETTERS`]. - InvalidAllowedAscii, /// The [`Domain`] has a trailing `b'.'`. ContainsTrailingDot, + /// The [`Domain`] contains ASCII not in [`crate::char_set::ASCII_HYPHEN_DIGITS_LETTERS`]. + InvalidAscii, /// A [`Label`] of [`Domain`] starts with an ASCII hyphen. LabelStartsWithAHyphen, /// A [`Label`] of [`Domain`] ends with an ASCII hyphen. @@ -1311,10 +977,10 @@ impl Display for Rfc1123Err { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - Self::InvalidAllowedAscii => { - f.write_str("the allowed ASCII is not letters, digits, and hyphen") - } Self::ContainsTrailingDot => f.write_str("the domain contained a trailing dot"), + Self::InvalidAscii => { + f.write_str("the domain contained ASCII besides hyphen, digits, and letters") + } Self::LabelStartsWithAHyphen => { f.write_str("a label in the domain starts with a hyphen") } @@ -1370,11 +1036,11 @@ impl Error for Rfc1123Err {} /// RFC 1123 as if it were written presciently does not make sense. For that reason the more relaxed interpretation /// is rejected. Consequently we use the most relaxed interpretation. #[derive(Clone, Debug)] -pub struct Rfc1123Domain<'a, T> { +pub struct Rfc1123Domain<T> { /// The domain. - dom: Domain<'a, 63, T>, + dom: Domain<T>, } -impl<T> Rfc1123Domain<'_, T> { +impl<T> Rfc1123Domain<T> { /// Returns the contained [`Domain`]. /// /// # Example @@ -1386,11 +1052,11 @@ impl<T> Rfc1123Domain<'_, T> { /// assert!(Rfc1123Domain::try_from(dom).unwrap().domain() == dom2); /// ``` #[inline] - pub const fn domain(&self) -> &Domain<'_, 63, T> { + pub const fn domain(&self) -> &Domain<T> { &self.dom } } -impl<T: AsRef<[u8]>> Rfc1123Domain<'_, T> { +impl<T: AsRef<[u8]>> Rfc1123Domain<T> { /// Returns `true` iff the domain adheres to the literal interpretation of RFC 1123. For more information /// read the description of [`Rfc1123Domain`]. /// @@ -1440,8 +1106,7 @@ impl<T: AsRef<[u8]>> Rfc1123Domain<'_, T> { #[inline] pub fn is_ipv4(&self) -> bool { // Faster to check metadata first to hopefully avoid re-parsing the domain as an IPv4 address. - self.dom.flag == CharFlag::None - && self.as_bytes().len() < 16 + self.as_bytes().len() < 16 && self.label_count().get() == 4 // We don't use `std::net::Ipv4Addr::from_str` since that does not consider octets with leading // 0s as valid. This means something like `0.0.0.01` is not considered an IPv4 address, but we @@ -1474,157 +1139,135 @@ impl<T: AsRef<[u8]>> Rfc1123Domain<'_, T> { .is_ok() } } -impl<'a, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<'a, T>> - for Rfc1123Domain<'b, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for Rfc1123Domain<T2> { #[inline] - fn eq(&self, other: &Rfc1123Domain<'a, T>) -> bool { + fn eq(&self, other: &Rfc1123Domain<T>) -> bool { self.dom == other.dom } } -impl<'a, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Rfc1123Domain<'a, T>> - for Rfc1123Domain<'b, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Rfc1123Domain<T>> for Rfc1123Domain<T2> { #[inline] - fn eq(&self, other: &&Rfc1123Domain<'a, T>) -> bool { + fn eq(&self, other: &&Rfc1123Domain<T>) -> bool { self.dom == other.dom } } -impl<'a, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<'a, T>> - for &Rfc1123Domain<'b, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for &Rfc1123Domain<T2> { #[inline] - fn eq(&self, other: &Rfc1123Domain<'a, T>) -> bool { + fn eq(&self, other: &Rfc1123Domain<T>) -> bool { self.dom == other.dom } } -impl<'a, 'b, const ALLOWED_COUNT: usize, T: AsRef<[u8]>, T2: AsRef<[u8]>> - PartialEq<Rfc1123Domain<'a, T>> for Domain<'b, ALLOWED_COUNT, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for Domain<T2> { #[inline] - fn eq(&self, other: &Rfc1123Domain<'a, T>) -> bool { + fn eq(&self, other: &Rfc1123Domain<T>) -> bool { *self == other.dom } } -impl<'a, 'b, const ALLOWED_COUNT: usize, T: AsRef<[u8]>, T2: AsRef<[u8]>> - PartialEq<Rfc1123Domain<'a, T>> for &Domain<'b, ALLOWED_COUNT, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Rfc1123Domain<T>> for &Domain<T2> { #[inline] - fn eq(&self, other: &Rfc1123Domain<'a, T>) -> bool { + fn eq(&self, other: &Rfc1123Domain<T>) -> bool { **self == other.dom } } -impl<'a, 'b, const ALLOWED_COUNT: usize, T: AsRef<[u8]>, T2: AsRef<[u8]>> - PartialEq<&Rfc1123Domain<'a, T>> for Domain<'b, ALLOWED_COUNT, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<&Rfc1123Domain<T>> for Domain<T2> { #[inline] - fn eq(&self, other: &&Rfc1123Domain<'a, T>) -> bool { + fn eq(&self, other: &&Rfc1123Domain<T>) -> bool { *self == other.dom } } -impl<'a, 'b, const ALLOWED_COUNT: usize, T: AsRef<[u8]>, T2: AsRef<[u8]>> - PartialEq<Domain<'a, ALLOWED_COUNT, T>> for Rfc1123Domain<'b, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialEq<Domain<T>> for Rfc1123Domain<T2> { #[inline] - fn eq(&self, other: &Domain<'a, ALLOWED_COUNT, T>) -> bool { + fn eq(&self, other: &Domain<T>) -> bool { self.dom == *other } } -impl<T: AsRef<[u8]>> Eq for Rfc1123Domain<'_, T> {} -impl<'a, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Rfc1123Domain<'a, T>> - for Rfc1123Domain<'b, T2> -{ +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<'a, T>) -> Option<Ordering> { + fn partial_cmp(&self, other: &Rfc1123Domain<T>) -> Option<Ordering> { self.dom.partial_cmp(&other.dom) } } -impl<'a, 'b, const ALLOWED_COUNT: usize, T: AsRef<[u8]>, T2: AsRef<[u8]>> - PartialOrd<Rfc1123Domain<'a, T>> for Domain<'b, ALLOWED_COUNT, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Rfc1123Domain<T>> for Domain<T2> { #[inline] - fn partial_cmp(&self, other: &Rfc1123Domain<'a, T>) -> Option<Ordering> { + fn partial_cmp(&self, other: &Rfc1123Domain<T>) -> Option<Ordering> { self.partial_cmp(&other.dom) } } -impl<'a, 'b, const ALLOWED_COUNT: usize, T: AsRef<[u8]>, T2: AsRef<[u8]>> - PartialOrd<Domain<'a, ALLOWED_COUNT, T>> for Rfc1123Domain<'b, T2> -{ +impl<T: AsRef<[u8]>, T2: AsRef<[u8]>> PartialOrd<Domain<T>> for Rfc1123Domain<T2> { #[inline] - fn partial_cmp(&self, other: &Domain<'a, ALLOWED_COUNT, T>) -> Option<Ordering> { + fn partial_cmp(&self, other: &Domain<T>) -> Option<Ordering> { self.dom.partial_cmp(other) } } -impl<T: AsRef<[u8]>> Ord for Rfc1123Domain<'_, T> { +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> { +impl<T: AsRef<[u8]>> Hash for Rfc1123Domain<T> { #[inline] fn hash<H: Hasher>(&self, state: &mut H) { self.dom.hash(state); } } -impl<'a, 'b: 'a, T> AsRef<Domain<'a, 63, T>> for Rfc1123Domain<'b, T> { +impl<T> AsRef<Domain<T>> for Rfc1123Domain<T> { #[inline] - fn as_ref(&self) -> &Domain<'a, 63, T> { + fn as_ref(&self) -> &Domain<T> { &self.dom } } -impl<'a, 'b: 'a, T> Borrow<Domain<'a, 63, T>> for Rfc1123Domain<'b, T> { +impl<T> Borrow<Domain<T>> for Rfc1123Domain<T> { #[inline] - fn borrow(&self) -> &Domain<'a, 63, T> { + fn borrow(&self) -> &Domain<T> { &self.dom } } -impl<'a, T> Deref for Rfc1123Domain<'a, T> { - type Target = Domain<'a, 63, T>; +impl<T> Deref for Rfc1123Domain<T> { + type Target = Domain<T>; #[inline] fn deref(&self) -> &Self::Target { &self.dom } } -impl<'a, 'b: 'a, T> From<Rfc1123Domain<'b, T>> for Domain<'a, 63, T> { +impl<T> From<Rfc1123Domain<T>> for Domain<T> { #[inline] - fn from(value: Rfc1123Domain<'b, T>) -> Self { + fn from(value: Rfc1123Domain<T>) -> Self { value.dom } } -impl<'a, 'b: 'a, T: AsRef<[u8]>> TryFrom<Domain<'b, 63, T>> for Rfc1123Domain<'a, T> { +impl<T: AsRef<[u8]>> TryFrom<Domain<T>> for Rfc1123Domain<T> { type Error = Rfc1123Err; - #[allow( - clippy::arithmetic_side_effects, - clippy::indexing_slicing, - clippy::unreachable - )] + #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] #[inline] - fn try_from(value: Domain<'b, 63, T>) -> Result<Self, Self::Error> { - if *value.allowed_ascii != ASCII_HYPHEN_DIGITS_LETTERS { - Err(Rfc1123Err::InvalidAllowedAscii) - } else if value.contains_trailing_dot() { + fn try_from(value: Domain<T>) -> Result<Self, Self::Error> { + if value.contains_trailing_dot() { Err(Rfc1123Err::ContainsTrailingDot) } else { value .into_iter() .try_fold((), |(), label| { - let bytes = label.as_bytes(); - // `Label`s are never empty, so the below indexing is fine. - // Underflow won't occur for the same reason. - if bytes[0] == b'-' { - Err(Rfc1123Err::LabelStartsWithAHyphen) - } else if bytes[bytes.len() - 1] == b'-' { - Err(Rfc1123Err::LabelEndsWithAHyphen) + if label.is_hyphen_or_alphanumeric() { + let bytes = label.value.as_bytes(); + // `Label`s are never empty, so the below indexing is fine. + // Underflow won't occur for the same reason. + if bytes[0] == b'-' { + Err(Rfc1123Err::LabelStartsWithAHyphen) + } else if bytes[bytes.len() - 1] == b'-' { + Err(Rfc1123Err::LabelEndsWithAHyphen) + } else { + Ok(()) + } } else { - Ok(()) + Err(Rfc1123Err::InvalidAscii) } }) .map(|()| Self { dom: value }) } } } -impl<T: AsRef<[u8]>> Display for Rfc1123Domain<'_, T> { +impl<T: AsRef<[u8]>> Display for Rfc1123Domain<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.dom.fmt(f) @@ -1633,7 +1276,7 @@ impl<T: AsRef<[u8]>> Display for Rfc1123Domain<'_, T> { #[cfg(test)] mod tests { use super::{Domain, DomainErr, Rfc1123Domain, Rfc1123Err}; - use crate::char_set::{self, ASCII_FIREFOX, ASCII_HYPHEN_DIGITS_LETTERS}; + use crate::char_set::{AllowedAscii, ASCII_FIREFOX, ASCII_HYPHEN_DIGITS_LETTERS}; use core::cmp::Ordering; #[test] fn test_dom_parse() { @@ -1954,15 +1597,14 @@ mod tests { .map_or_else(|e| e == Rfc1123Err::ContainsTrailingDot, |_| false) ) ); - assert!(char_set::AllowedAscii::<63>::try_from( - b"!@#$%^&*()1234567890asdfghjklqwertyuiopzxcvbnmASDFGHJKLZXCVBNMQ".to_owned() - ) - .map_or(false, |ascii| { - Domain::try_from_bytes("example.com", &ascii).map_or(false, |dom| { - Rfc1123Domain::try_from(dom) - .map_or_else(|e| e == Rfc1123Err::InvalidAllowedAscii, |_| false) + 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::InvalidAscii, |_| false) + }) }) - })); + ); assert!( Domain::try_from_bytes("example-.com", &ASCII_HYPHEN_DIGITS_LETTERS).map_or( false,