ascii_domain

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

serde.rs (8346B)


      1 extern crate alloc;
      2 #[cfg(test)]
      3 /// Unit tests.
      4 mod tests;
      5 use crate::{
      6     char_set::{AllowedAscii, PRINTABLE_ASCII},
      7     dom::{Domain, DomainErr, Rfc1123Domain, Rfc1123Err},
      8 };
      9 use alloc::{borrow::ToOwned as _, string::String};
     10 use core::{
     11     fmt::{self, Formatter},
     12     marker::PhantomData,
     13 };
     14 use serde::{
     15     de::{self, Deserialize, Deserializer, Unexpected, Visitor},
     16     ser::{Serialize, Serializer},
     17 };
     18 /// The "default" `AllowedAscii` that is used for `Domain`.
     19 static DOMAIN_CHARS: &AllowedAscii<[u8; 92]> = &PRINTABLE_ASCII;
     20 impl<T: AsRef<[u8]>> Serialize for Domain<T> {
     21     /// Serializes `Domain` as a string.
     22     #[inline]
     23     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     24     where
     25         S: Serializer,
     26     {
     27         serializer.serialize_str(self.as_str())
     28     }
     29 }
     30 impl<T: AsRef<[u8]>> Serialize for Rfc1123Domain<T> {
     31     /// Serializes `Rfc1123Domain` as a string.
     32     #[inline]
     33     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     34     where
     35         S: Serializer,
     36     {
     37         serializer.serialize_str(self.as_str())
     38     }
     39 }
     40 /// Serde [`Visitor`] that deserializes a string into a [`Domain`] based on [`Self::allowed_ascii`].
     41 ///
     42 /// Since `Domain`s rely on an [`AllowedAscii`], there cannot be a single deserializer. This visitor
     43 /// makes it slightly easier to implement [`Deserialize`] for `Domain` wrappers based on whatever `AllowedAscii`
     44 /// is desired.
     45 ///
     46 /// # Example
     47 ///
     48 /// ```
     49 /// use ascii_domain::{dom::Domain, char_set::ASCII_HYPHEN_DIGITS_LETTERS, serde::DomainVisitor};
     50 /// use serde::de::{Deserialize, Deserializer};
     51 /// struct DomainWrapper(Domain<String>);
     52 /// impl<'de> Deserialize<'de> for DomainWrapper {
     53 ///     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     54 ///     where
     55 ///         D: Deserializer<'de>,
     56 ///     {
     57 ///         deserializer.deserialize_string(DomainVisitor::<'_, _, String>::new(&ASCII_HYPHEN_DIGITS_LETTERS)).map(|dom| DomainWrapper(dom))
     58 ///     }
     59 /// }
     60 /// ```
     61 #[expect(
     62     clippy::partial_pub_fields,
     63     reason = "we don't expost PhantomData for obvious reasons, so this is fine"
     64 )]
     65 #[derive(Clone, Copy, Debug)]
     66 pub struct DomainVisitor<'a, T, T2> {
     67     /// Phantom.
     68     _x: PhantomData<fn() -> T2>,
     69     /// The character set the visitor will use when deserializing a string into a `Domain`.
     70     pub allowed_ascii: &'a AllowedAscii<T>,
     71 }
     72 /// Converts `DomainErr` to a Serde `de::Error`.
     73 fn dom_err_to_serde<E: de::Error>(value: DomainErr) -> E {
     74     match value {
     75         DomainErr::Empty => E::invalid_length(
     76             0,
     77             &"a valid domain with length inclusively between 1 and 253",
     78         ),
     79         DomainErr::RootDomain => {
     80             E::invalid_length(0, &"a valid domain with at least one non-root label")
     81         }
     82         DomainErr::LenExceeds253(len) => E::invalid_length(
     83             len,
     84             &"a valid domain with length inclusively between 1 and 253",
     85         ),
     86         DomainErr::LabelLenExceeds63 => E::invalid_length(
     87             64,
     88             &"a valid domain containing labels of length inclusively between 1 and 63",
     89         ),
     90         DomainErr::EmptyLabel => E::invalid_length(
     91             0,
     92             &"a valid domain containing labels of length inclusively between 1 and 63",
     93         ),
     94         DomainErr::InvalidByte(byt) => E::invalid_value(
     95             Unexpected::Unsigned(u64::from(byt)),
     96             &"a valid domain containing only the supplied ASCII subset",
     97         ),
     98     }
     99 }
    100 impl<'a, T, T2> DomainVisitor<'a, T, T2> {
    101     /// Returns `DomainVisitor` with [`Self::allowed_ascii`] set to `allowed_ascii`.
    102     ///
    103     /// # Example
    104     ///
    105     /// ```
    106     /// use ascii_domain::{char_set::ASCII_HYPHEN_DIGITS_LETTERS, serde::DomainVisitor};
    107     /// assert!(DomainVisitor::<'_, _, String>::new(&ASCII_HYPHEN_DIGITS_LETTERS).allowed_ascii.len() == 63);
    108     /// ```
    109     #[expect(single_use_lifetimes, reason = "false positive")]
    110     #[inline]
    111     pub const fn new<'b: 'a>(allowed_ascii: &'b AllowedAscii<T>) -> Self {
    112         Self {
    113             _x: PhantomData,
    114             allowed_ascii,
    115         }
    116     }
    117 }
    118 impl<'de: 'a, 'a, T: AsRef<[u8]>> Visitor<'de> for DomainVisitor<'_, T, &'a str> {
    119     type Value = Domain<&'a str>;
    120     #[inline]
    121     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    122         formatter.write_str("Domain")
    123     }
    124     #[inline]
    125     fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    126     where
    127         E: de::Error,
    128     {
    129         Self::Value::try_from_bytes(v, self.allowed_ascii).map_err(|err| dom_err_to_serde::<E>(err))
    130     }
    131 }
    132 impl<T: AsRef<[u8]>> Visitor<'_> for DomainVisitor<'_, T, String> {
    133     type Value = Domain<String>;
    134     #[inline]
    135     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    136         formatter.write_str("Domain")
    137     }
    138     #[inline]
    139     fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
    140     where
    141         E: de::Error,
    142     {
    143         Self::Value::try_from_bytes(v, self.allowed_ascii).map_err(|err| dom_err_to_serde::<E>(err))
    144     }
    145     #[inline]
    146     fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    147     where
    148         E: de::Error,
    149     {
    150         self.visit_string(v.to_owned())
    151     }
    152 }
    153 /// Deserializes `String`s into a `Domain` based on [`PRINTABLE_ASCII`].
    154 impl<'de> Deserialize<'de> for Domain<String> {
    155     #[inline]
    156     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    157     where
    158         D: Deserializer<'de>,
    159     {
    160         deserializer.deserialize_string(DomainVisitor::<'_, _, String>::new(DOMAIN_CHARS))
    161     }
    162 }
    163 /// Deserializes `str`s into a `Domain` based on [`PRINTABLE_ASCII`].
    164 impl<'de: 'a, 'a> Deserialize<'de> for Domain<&'a str> {
    165     #[inline]
    166     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    167     where
    168         D: Deserializer<'de>,
    169     {
    170         deserializer.deserialize_str(DomainVisitor::<'_, _, &str>::new(DOMAIN_CHARS))
    171     }
    172 }
    173 /// Converts `Rfc1123Err` to a Serde `de::Error`.
    174 fn rfc_err_to_serde<E: de::Error>(value: Rfc1123Err) -> E {
    175     match value {
    176         Rfc1123Err::DomainErr(err) => dom_err_to_serde(err),
    177         Rfc1123Err::LabelStartsWithAHyphen | Rfc1123Err::LabelEndsWithAHyphen => E::invalid_value(
    178             Unexpected::Str("-"),
    179             &"a valid domain conforming to RFC 1123 which requires all labels to not begin or end with a '-'",
    180         ),
    181         Rfc1123Err::InvalidTld => E::invalid_value(
    182             Unexpected::Str(
    183                 "tld that is not all letters nor begins with 'xn--' and has length of at least five",
    184             ),
    185             &"a valid domain conforming to RFC 1123 which requires the last label (i.e., TLD) to either be all letters or have length of at least five and begins with 'xn--'",
    186         ),
    187     }
    188 }
    189 /// Serde [`Visitor`] that deserializes a string into an [`Rfc1123Domain`].
    190 struct Rfc1123Visitor<T>(PhantomData<fn() -> T>);
    191 impl<'de: 'a, 'a> Visitor<'de> for Rfc1123Visitor<&'a str> {
    192     type Value = Rfc1123Domain<&'a str>;
    193     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    194         formatter.write_str("Rfc1123Domain")
    195     }
    196     fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    197     where
    198         E: de::Error,
    199     {
    200         Self::Value::try_from_bytes(v).map_err(|err| rfc_err_to_serde(err))
    201     }
    202 }
    203 impl Visitor<'_> for Rfc1123Visitor<String> {
    204     type Value = Rfc1123Domain<String>;
    205     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    206         formatter.write_str("Rfc1123Domain")
    207     }
    208     fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
    209     where
    210         E: de::Error,
    211     {
    212         Self::Value::try_from_bytes(v).map_err(|err| rfc_err_to_serde(err))
    213     }
    214     fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    215     where
    216         E: de::Error,
    217     {
    218         self.visit_string(v.to_owned())
    219     }
    220 }
    221 impl<'de> Deserialize<'de> for Rfc1123Domain<String> {
    222     #[inline]
    223     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    224     where
    225         D: Deserializer<'de>,
    226     {
    227         deserializer.deserialize_string(Rfc1123Visitor::<String>(PhantomData))
    228     }
    229 }
    230 impl<'de: 'a, 'a> Deserialize<'de> for Rfc1123Domain<&'a str> {
    231     #[inline]
    232     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    233     where
    234         D: Deserializer<'de>,
    235     {
    236         deserializer.deserialize_str(Rfc1123Visitor::<&'a str>(PhantomData))
    237     }
    238 }