ribbit_client/
types.rs

1//! Type definitions for the Ribbit client
2
3use std::fmt;
4
5/// Supported regions for Ribbit endpoints
6///
7/// # Regional Availability
8///
9/// Some regions may have network restrictions:
10/// - `CN` (China): Typically only accessible from within China
11/// - Other regions (`US`, `EU`, `KR`, `TW`, `SG`): Generally accessible globally
12///
13/// The client will timeout after 10 seconds if a region is unreachable.
14///
15/// # Example
16///
17/// ```
18/// use ribbit_client::Region;
19///
20/// let region = Region::US;
21/// assert_eq!(region.hostname(), "us.version.battle.net");
22/// assert_eq!(region.as_str(), "us");
23///
24/// // Parse from string
25/// let parsed: Region = "eu".parse().unwrap();
26/// assert_eq!(parsed, Region::EU);
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum Region {
30    /// United States
31    US,
32    /// Europe
33    EU,
34    /// China
35    CN,
36    /// Korea
37    KR,
38    /// Taiwan
39    TW,
40    /// Singapore
41    SG,
42}
43
44impl Region {
45    /// Get the hostname for this region
46    #[must_use]
47    pub fn hostname(&self) -> &'static str {
48        match self {
49            Region::US => "us.version.battle.net",
50            Region::EU => "eu.version.battle.net",
51            Region::CN => "cn.version.battle.net",
52            Region::KR => "kr.version.battle.net",
53            Region::TW => "tw.version.battle.net",
54            Region::SG => "sg.version.battle.net",
55        }
56    }
57
58    /// Get the region code as a string
59    #[must_use]
60    pub fn as_str(&self) -> &'static str {
61        match self {
62            Region::US => "us",
63            Region::EU => "eu",
64            Region::CN => "cn",
65            Region::KR => "kr",
66            Region::TW => "tw",
67            Region::SG => "sg",
68        }
69    }
70}
71
72impl fmt::Display for Region {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", self.as_str())
75    }
76}
77
78impl std::str::FromStr for Region {
79    type Err = crate::error::Error;
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        match s.to_lowercase().as_str() {
83            "us" => Ok(Region::US),
84            "eu" => Ok(Region::EU),
85            "cn" => Ok(Region::CN),
86            "kr" => Ok(Region::KR),
87            "tw" => Ok(Region::TW),
88            "sg" => Ok(Region::SG),
89            _ => Err(crate::error::Error::InvalidRegion(s.to_string())),
90        }
91    }
92}
93
94/// Ribbit protocol version
95///
96/// # Example
97///
98/// ```
99/// use ribbit_client::ProtocolVersion;
100///
101/// let v1 = ProtocolVersion::V1;
102/// assert_eq!(v1.prefix(), "v1");
103/// assert_eq!(v1.to_string(), "v1");
104/// ```
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum ProtocolVersion {
107    /// Version 1 - MIME format with signatures and checksums
108    V1,
109    /// Version 2 - Raw PSV data without MIME wrapper
110    V2,
111}
112
113impl ProtocolVersion {
114    /// Get the version prefix for commands
115    #[must_use]
116    pub fn prefix(&self) -> &'static str {
117        match self {
118            ProtocolVersion::V1 => "v1",
119            ProtocolVersion::V2 => "v2",
120        }
121    }
122}
123
124impl fmt::Display for ProtocolVersion {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "{}", self.prefix())
127    }
128}
129
130/// Common Ribbit endpoints
131///
132/// # Example
133///
134/// ```
135/// use ribbit_client::Endpoint;
136///
137/// // Product versions endpoint
138/// let versions = Endpoint::ProductVersions("wow".to_string());
139/// assert_eq!(versions.as_path(), "products/wow/versions");
140///
141/// // Certificate endpoint
142/// let cert = Endpoint::Cert("abc123".to_string());
143/// assert_eq!(cert.as_path(), "certs/abc123");
144///
145/// // Custom endpoint
146/// let custom = Endpoint::Custom("custom/path".to_string());
147/// assert_eq!(custom.as_path(), "custom/path");
148/// ```
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum Endpoint {
151    /// Summary of all products
152    Summary,
153    /// Product versions
154    ProductVersions(String),
155    /// Product CDNs
156    ProductCdns(String),
157    /// Product background download info
158    ProductBgdl(String),
159    /// Certificate by hash
160    Cert(String),
161    /// OCSP response by hash
162    Ocsp(String),
163    /// Custom endpoint
164    Custom(String),
165}
166
167impl Endpoint {
168    /// Convert the endpoint to its path representation
169    #[must_use]
170    pub fn as_path(&self) -> String {
171        match self {
172            Endpoint::Summary => "summary".to_string(),
173            Endpoint::ProductVersions(product) => format!("products/{product}/versions"),
174            Endpoint::ProductCdns(product) => format!("products/{product}/cdns"),
175            Endpoint::ProductBgdl(product) => format!("products/{product}/bgdl"),
176            Endpoint::Cert(hash) => format!("certs/{hash}"),
177            Endpoint::Ocsp(hash) => format!("ocsp/{hash}"),
178            Endpoint::Custom(path) => path.clone(),
179        }
180    }
181}
182
183/// Ribbit TCP port
184pub const RIBBIT_PORT: u16 = 1119;
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_region_hostname() {
192        assert_eq!(Region::US.hostname(), "us.version.battle.net");
193        assert_eq!(Region::EU.hostname(), "eu.version.battle.net");
194        assert_eq!(Region::CN.hostname(), "cn.version.battle.net");
195        assert_eq!(Region::KR.hostname(), "kr.version.battle.net");
196        assert_eq!(Region::TW.hostname(), "tw.version.battle.net");
197        assert_eq!(Region::SG.hostname(), "sg.version.battle.net");
198    }
199
200    #[test]
201    fn test_region_as_str() {
202        assert_eq!(Region::US.as_str(), "us");
203        assert_eq!(Region::EU.as_str(), "eu");
204        assert_eq!(Region::CN.as_str(), "cn");
205        assert_eq!(Region::KR.as_str(), "kr");
206        assert_eq!(Region::TW.as_str(), "tw");
207        assert_eq!(Region::SG.as_str(), "sg");
208    }
209
210    #[test]
211    fn test_region_display() {
212        assert_eq!(Region::US.to_string(), "us");
213        assert_eq!(Region::EU.to_string(), "eu");
214    }
215
216    #[test]
217    fn test_region_from_str() {
218        assert_eq!("us".parse::<Region>().unwrap(), Region::US);
219        assert_eq!("US".parse::<Region>().unwrap(), Region::US);
220        assert_eq!("eu".parse::<Region>().unwrap(), Region::EU);
221        assert_eq!("EU".parse::<Region>().unwrap(), Region::EU);
222
223        assert!("invalid".parse::<Region>().is_err());
224        assert!("".parse::<Region>().is_err());
225    }
226
227    #[test]
228    fn test_protocol_version_prefix() {
229        assert_eq!(ProtocolVersion::V1.prefix(), "v1");
230        assert_eq!(ProtocolVersion::V2.prefix(), "v2");
231    }
232
233    #[test]
234    fn test_protocol_version_display() {
235        assert_eq!(ProtocolVersion::V1.to_string(), "v1");
236        assert_eq!(ProtocolVersion::V2.to_string(), "v2");
237    }
238
239    #[test]
240    fn test_endpoint_as_path() {
241        assert_eq!(Endpoint::Summary.as_path(), "summary");
242        assert_eq!(
243            Endpoint::ProductVersions("wow".to_string()).as_path(),
244            "products/wow/versions"
245        );
246        assert_eq!(
247            Endpoint::ProductCdns("wow_classic".to_string()).as_path(),
248            "products/wow_classic/cdns"
249        );
250        assert_eq!(
251            Endpoint::ProductBgdl("wow_beta".to_string()).as_path(),
252            "products/wow_beta/bgdl"
253        );
254        assert_eq!(
255            Endpoint::Cert("abc123".to_string()).as_path(),
256            "certs/abc123"
257        );
258        assert_eq!(
259            Endpoint::Ocsp("def456".to_string()).as_path(),
260            "ocsp/def456"
261        );
262        assert_eq!(
263            Endpoint::Custom("custom/path".to_string()).as_path(),
264            "custom/path"
265        );
266    }
267
268    #[test]
269    fn test_ribbit_port() {
270        assert_eq!(RIBBIT_PORT, 1119);
271    }
272}