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