datto_api/
platforms.rs

1//! Datto RMM Platform configuration.
2//!
3//! The Datto RMM API is hosted on multiple regional platforms.
4//! All platforms share the same API schema.
5
6use std::fmt;
7use std::str::FromStr;
8
9/// Datto RMM platform identifiers.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Platform {
12    /// Pinotage platform
13    Pinotage,
14    /// Merlot platform
15    Merlot,
16    /// Concord platform
17    Concord,
18    /// Vidal platform
19    Vidal,
20    /// Zinfandel platform
21    Zinfandel,
22    /// Syrah platform
23    Syrah,
24}
25
26impl Platform {
27    /// Get the base API URL for this platform.
28    pub fn base_url(&self) -> &'static str {
29        match self {
30            Platform::Pinotage => "https://pinotage-api.centrastage.net/api",
31            Platform::Merlot => "https://merlot-api.centrastage.net/api",
32            Platform::Concord => "https://concord-api.centrastage.net/api",
33            Platform::Vidal => "https://vidal-api.centrastage.net/api",
34            Platform::Zinfandel => "https://zinfandel-api.centrastage.net/api",
35            Platform::Syrah => "https://syrah-api.centrastage.net/api",
36        }
37    }
38
39    /// Get the OAuth token endpoint for this platform.
40    pub fn token_endpoint(&self) -> String {
41        format!("{}/public/oauth/token", self.base_url())
42    }
43
44    /// Get all available platforms.
45    pub fn all() -> &'static [Platform] {
46        &[
47            Platform::Pinotage,
48            Platform::Merlot,
49            Platform::Concord,
50            Platform::Vidal,
51            Platform::Zinfandel,
52            Platform::Syrah,
53        ]
54    }
55}
56
57impl fmt::Display for Platform {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Platform::Pinotage => write!(f, "pinotage"),
61            Platform::Merlot => write!(f, "merlot"),
62            Platform::Concord => write!(f, "concord"),
63            Platform::Vidal => write!(f, "vidal"),
64            Platform::Zinfandel => write!(f, "zinfandel"),
65            Platform::Syrah => write!(f, "syrah"),
66        }
67    }
68}
69
70impl FromStr for Platform {
71    type Err = PlatformParseError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        match s.to_lowercase().as_str() {
75            "pinotage" => Ok(Platform::Pinotage),
76            "merlot" => Ok(Platform::Merlot),
77            "concord" => Ok(Platform::Concord),
78            "vidal" => Ok(Platform::Vidal),
79            "zinfandel" => Ok(Platform::Zinfandel),
80            "syrah" => Ok(Platform::Syrah),
81            _ => Err(PlatformParseError(s.to_string())),
82        }
83    }
84}
85
86/// Error returned when parsing an invalid platform name.
87#[derive(Debug, Clone)]
88pub struct PlatformParseError(String);
89
90impl fmt::Display for PlatformParseError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(
93            f,
94            "unknown platform '{}'. Valid platforms: pinotage, merlot, concord, vidal, zinfandel, syrah",
95            self.0
96        )
97    }
98}
99
100impl std::error::Error for PlatformParseError {}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_all_platform_urls() {
108        assert_eq!(
109            Platform::Pinotage.base_url(),
110            "https://pinotage-api.centrastage.net/api"
111        );
112        assert_eq!(
113            Platform::Merlot.base_url(),
114            "https://merlot-api.centrastage.net/api"
115        );
116        assert_eq!(
117            Platform::Concord.base_url(),
118            "https://concord-api.centrastage.net/api"
119        );
120        assert_eq!(
121            Platform::Vidal.base_url(),
122            "https://vidal-api.centrastage.net/api"
123        );
124        assert_eq!(
125            Platform::Zinfandel.base_url(),
126            "https://zinfandel-api.centrastage.net/api"
127        );
128        assert_eq!(
129            Platform::Syrah.base_url(),
130            "https://syrah-api.centrastage.net/api"
131        );
132    }
133
134    #[test]
135    fn test_token_endpoints() {
136        assert_eq!(
137            Platform::Merlot.token_endpoint(),
138            "https://merlot-api.centrastage.net/api/public/oauth/token"
139        );
140        assert_eq!(
141            Platform::Pinotage.token_endpoint(),
142            "https://pinotage-api.centrastage.net/api/public/oauth/token"
143        );
144    }
145
146    #[test]
147    fn test_platform_from_str() {
148        assert_eq!(Platform::from_str("merlot").unwrap(), Platform::Merlot);
149        assert_eq!(Platform::from_str("MERLOT").unwrap(), Platform::Merlot);
150        assert_eq!(Platform::from_str("Merlot").unwrap(), Platform::Merlot);
151        assert_eq!(Platform::from_str("pinotage").unwrap(), Platform::Pinotage);
152        assert_eq!(Platform::from_str("concord").unwrap(), Platform::Concord);
153        assert_eq!(Platform::from_str("vidal").unwrap(), Platform::Vidal);
154        assert_eq!(Platform::from_str("zinfandel").unwrap(), Platform::Zinfandel);
155        assert_eq!(Platform::from_str("syrah").unwrap(), Platform::Syrah);
156    }
157
158    #[test]
159    fn test_platform_from_str_invalid() {
160        let err = Platform::from_str("invalid").unwrap_err();
161        assert!(err.to_string().contains("unknown platform 'invalid'"));
162        assert!(err.to_string().contains("Valid platforms:"));
163    }
164
165    #[test]
166    fn test_platform_display() {
167        assert_eq!(Platform::Pinotage.to_string(), "pinotage");
168        assert_eq!(Platform::Merlot.to_string(), "merlot");
169        assert_eq!(Platform::Concord.to_string(), "concord");
170        assert_eq!(Platform::Vidal.to_string(), "vidal");
171        assert_eq!(Platform::Zinfandel.to_string(), "zinfandel");
172        assert_eq!(Platform::Syrah.to_string(), "syrah");
173    }
174
175    #[test]
176    fn test_platform_all() {
177        let all = Platform::all();
178        assert_eq!(all.len(), 6);
179        assert!(all.contains(&Platform::Pinotage));
180        assert!(all.contains(&Platform::Merlot));
181        assert!(all.contains(&Platform::Concord));
182        assert!(all.contains(&Platform::Vidal));
183        assert!(all.contains(&Platform::Zinfandel));
184        assert!(all.contains(&Platform::Syrah));
185    }
186
187    #[test]
188    fn test_platform_equality() {
189        assert_eq!(Platform::Merlot, Platform::Merlot);
190        assert_ne!(Platform::Merlot, Platform::Pinotage);
191    }
192
193    #[test]
194    fn test_platform_clone() {
195        let p1 = Platform::Merlot;
196        let p2 = p1;
197        assert_eq!(p1, p2);
198    }
199
200    #[test]
201    fn test_platform_hash() {
202        use std::collections::HashSet;
203        let mut set = HashSet::new();
204        set.insert(Platform::Merlot);
205        set.insert(Platform::Merlot); // duplicate
206        set.insert(Platform::Pinotage);
207        assert_eq!(set.len(), 2);
208    }
209}