avalanche_types/codec/serde/
ip_port.rs

1use std::net::{IpAddr, SocketAddr};
2
3use serde::{self, Deserialize, Deserializer, Serializer};
4use serde_with::{DeserializeAs, SerializeAs};
5use url::Url;
6
7/// ref. <https://serde.rs/custom-date-format.html>
8pub fn serialize<S>(x: &SocketAddr, serializer: S) -> Result<S::Ok, S::Error>
9where
10    S: Serializer,
11{
12    // ref. <https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html#method.to_rfc3339_opts>
13    serializer.serialize_str(&x.to_string())
14}
15
16/// ref. <https://serde.rs/custom-date-format.html>
17pub fn deserialize<'de, D>(deserializer: D) -> Result<SocketAddr, D::Error>
18where
19    D: Deserializer<'de>,
20{
21    let s = String::deserialize(deserializer)?;
22
23    match s.parse() {
24        Ok(addr) => Ok(addr),
25        Err(e) => {
26            log::warn!("fallback to URL parsing {:?}", e);
27            let url = Url::parse(&s).map_err(serde::de::Error::custom)?;
28
29            let host = if let Some(hs) = url.host_str() {
30                hs.to_string()
31            } else {
32                return Err(serde::de::Error::custom("no host found"));
33            };
34            let ip: IpAddr = host.parse().map_err(serde::de::Error::custom)?;
35            let port = if let Some(port) = url.port() {
36                port
37            } else {
38                0 // e.g., DNS
39            };
40            Ok(SocketAddr::new(ip, port))
41        }
42    }
43}
44
45pub struct IpPort(SocketAddr);
46
47impl SerializeAs<SocketAddr> for IpPort {
48    fn serialize_as<S>(x: &SocketAddr, serializer: S) -> Result<S::Ok, S::Error>
49    where
50        S: Serializer,
51    {
52        serializer.serialize_str(&x.to_string())
53    }
54}
55
56impl<'de> DeserializeAs<'de, SocketAddr> for IpPort {
57    fn deserialize_as<D>(deserializer: D) -> Result<SocketAddr, D::Error>
58    where
59        D: Deserializer<'de>,
60    {
61        let s = String::deserialize(deserializer)?;
62
63        match s.parse() {
64            Ok(addr) => Ok(addr),
65            Err(e) => {
66                log::warn!("fallback to URL parsing {:?}", e);
67                let url = Url::parse(&s).map_err(serde::de::Error::custom)?;
68
69                let host = if let Some(hs) = url.host_str() {
70                    hs.to_string()
71                } else {
72                    return Err(serde::de::Error::custom("no host found"));
73                };
74                let ip: IpAddr = host.parse().map_err(serde::de::Error::custom)?;
75                let port = if let Some(port) = url.port() {
76                    port
77                } else {
78                    0 // e.g., DNS
79                };
80                Ok(SocketAddr::new(ip, port))
81            }
82        }
83    }
84}
85
86/// RUST_LOG=debug cargo test --package avalanche-types --lib -- codec::serde::ip_port::test_custom_de_serializer --exact --show-output
87#[test]
88fn test_custom_de_serializer() {
89    use std::net::Ipv4Addr;
90
91    use serde::Serialize;
92    use serde_with::serde_as;
93
94    #[serde_as]
95    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
96    struct Data {
97        #[serde_as(as = "Vec<IpPort>")]
98        data: Vec<SocketAddr>,
99    }
100
101    let d = Data {
102        data: vec![
103            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(206, 189, 137, 87)), 9651),
104            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(158, 255, 67, 151)), 9651),
105            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(34, 216, 139, 126)), 9650),
106        ],
107    };
108
109    let yaml_encoded = serde_yaml::to_string(&d).unwrap();
110    println!("yaml_encoded:\n{}", yaml_encoded);
111    let yaml_decoded = serde_yaml::from_str(&yaml_encoded).unwrap();
112    assert_eq!(d, yaml_decoded);
113
114    let json_encoded = serde_json::to_string(&d).unwrap();
115    println!("json_encoded:\n{}", json_encoded);
116    let json_decoded = serde_json::from_str(&json_encoded).unwrap();
117    assert_eq!(d, json_decoded);
118
119    let json_decoded_2: Data = serde_json::from_str(
120        "
121
122{
123\"data\":[\"206.189.137.87:9651\", \"158.255.67.151:9651\", \"http://34.216.139.126:9650\"]
124}
125
126",
127    )
128    .unwrap();
129    assert_eq!(d, json_decoded_2);
130}