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 }