tirith_core/
policy_client.rs1use std::fmt;
2use std::time::Duration;
3
4#[derive(Debug)]
6pub enum PolicyFetchError {
7 NetworkError(String),
9 AuthError(u16),
11 ServerError(String),
13 InvalidResponse(String),
15}
16
17impl fmt::Display for PolicyFetchError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 PolicyFetchError::NetworkError(msg) => write!(f, "network error: {msg}"),
21 PolicyFetchError::AuthError(code) => write!(f, "authentication failed (HTTP {code})"),
22 PolicyFetchError::ServerError(msg) => write!(f, "server error: {msg}"),
23 PolicyFetchError::InvalidResponse(msg) => write!(f, "invalid response: {msg}"),
24 }
25 }
26}
27
28#[cfg(unix)]
33pub fn fetch_remote_policy(url: &str, api_key: &str) -> Result<String, PolicyFetchError> {
34 if let Err(reason) = crate::url_validate::validate_server_url(url) {
36 return Err(PolicyFetchError::NetworkError(reason));
37 }
38
39 let client = reqwest::blocking::Client::builder()
40 .connect_timeout(Duration::from_secs(5))
41 .timeout(Duration::from_secs(10))
42 .build()
43 .map_err(|e| PolicyFetchError::NetworkError(e.to_string()))?;
44
45 let endpoint = format!("{}/api/policy/fetch", url.trim_end_matches('/'));
46 let resp = client
47 .get(&endpoint)
48 .header("Authorization", format!("Bearer {api_key}"))
49 .send()
50 .map_err(|e| PolicyFetchError::NetworkError(e.to_string()))?;
51
52 match resp.status().as_u16() {
53 200 => resp
54 .text()
55 .map_err(|e| PolicyFetchError::InvalidResponse(e.to_string())),
56 401 | 403 => Err(PolicyFetchError::AuthError(resp.status().as_u16())),
57 404 => Err(PolicyFetchError::ServerError(
58 "no active policy found".into(),
59 )),
60 s => Err(PolicyFetchError::ServerError(format!(
61 "server returned HTTP {s}"
62 ))),
63 }
64}
65
66#[cfg(not(unix))]
68pub fn fetch_remote_policy(_url: &str, _api_key: &str) -> Result<String, PolicyFetchError> {
69 Err(PolicyFetchError::NetworkError(
70 "remote policy fetch is not supported on this platform".into(),
71 ))
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_policy_fetch_error_display() {
80 let e = PolicyFetchError::NetworkError("timeout".into());
81 assert_eq!(format!("{e}"), "network error: timeout");
82
83 let e = PolicyFetchError::AuthError(401);
84 assert_eq!(format!("{e}"), "authentication failed (HTTP 401)");
85
86 let e = PolicyFetchError::ServerError("internal error".into());
87 assert_eq!(format!("{e}"), "server error: internal error");
88
89 let e = PolicyFetchError::InvalidResponse("bad body".into());
90 assert_eq!(format!("{e}"), "invalid response: bad body");
91 }
92
93 #[test]
94 fn test_fetch_invalid_url_returns_network_error() {
95 let result = fetch_remote_policy("http://192.0.2.1:1", "test-key");
97 assert!(result.is_err());
98 match result.unwrap_err() {
99 PolicyFetchError::NetworkError(_) => {} other => panic!("expected NetworkError, got: {other}"),
101 }
102 }
103}