1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
pub use v1::{GameServer, GameServerList};

pub mod v1 {
    use country_parser::Country;
    use serde::{
        de::{Deserializer, Error, Unexpected},
        ser::Serializer,
        Deserialize, Serialize,
    };

    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
    pub struct GameServerList {
        /// List of all servers registered to this serverbrowser
        pub servers: Vec<GameServer>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
    pub struct GameServer {
        /// The name of the server.
        pub name: String,
        /// The address through which the server might be accessed on the open
        /// internet. This field may be an IPv4 address, IPv6 address,
        /// URL, must not contain a port
        pub address: String,
        /// Port of the gameserver address (usually `14004`)
        pub port: u16,
        /// The server description.
        pub description: String,
        /// The ISO 3166‑1 Alpha-2 code that the server is physically based in
        /// (note: this field is intended as an indication of factors
        /// like ping, not the language of the server). (e.g. "US")
        #[serde(deserialize_with = "deserialize_country")]
        #[serde(serialize_with = "serialize_country")]
        #[serde(default)]
        pub location: Option<Country>,
        /// The auth server that must be used to connect to this server.
        /// If you want to use the official auth server use `Some("https://auth.veloren.net")`
        pub auth_server: String,
        /// The version channel used by the server. `None` means not running a
        /// channel distributed by Airshipper. If in doubt, `"weekly"`
        /// is probably correct.
        pub channel: Option<String>,
        /// Whether the server is officially affiliated with the Veloren
        /// project.
        pub official: bool,
    }

    fn deserialize_country<'de, D: Deserializer<'de>>(de: D) -> Result<Option<Country>, D::Error> {
        country_parser::parse(String::deserialize(de)?)
            .map(Some)
            .ok_or_else(|| {
                D::Error::invalid_value(
                    Unexpected::Other("invalid country"),
                    &"valid ISO-3166 country",
                )
            })
    }

    fn serialize_country<S: Serializer>(
        country: &Option<Country>,
        ser: S,
    ) -> Result<S::Ok, S::Error> {
        match country {
            Some(country) => ser.serialize_str(&country.iso2),
            None => ser.serialize_none(),
        }
    }

    impl GameServer {
        pub fn new(
            name: &str,
            address: &str,
            port: u16,
            desc: &str,
            location: Option<Country>,
            auth: &str,
            channel: Option<&str>,
            official: bool,
        ) -> Self {
            Self {
                name: name.to_string(),
                address: address.to_string(),
                port,
                description: desc.to_string(),
                location,
                auth_server: auth.to_string(),
                channel: channel.map(|c| c.to_string()),
                official,
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_server_list_ron_deserialize() {
        ron::de::from_reader::<_, GameServerList>(
            &include_bytes!("../examples/v1/example_server_list.ron")[..],
        )
        .unwrap();
    }

    #[test]
    fn check_server_list_json_deserialize() {
        serde_json::de::from_reader::<_, GameServerList>(
            &include_bytes!("../examples/v1/example_server_list.json")[..],
        )
        .unwrap();
    }

    #[test]
    fn check_server_list_json_roundtrip() {
        let data = serde_json::de::from_reader::<_, GameServerList>(
            &include_bytes!("../examples/v1/example_server_list.json")[..],
        )
        .unwrap();
        serde_json::to_string_pretty(&data).unwrap();
    }
}