Skip to main content

caldir_core/remote/
config.rs

1mod params;
2
3use crate::ProviderSlug;
4use serde::{Deserialize, Serialize};
5
6pub use params::RemoteConfigParams;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct RemoteConfig {
10    #[serde(rename = "provider")]
11    provider_slug: ProviderSlug,
12
13    #[serde(flatten)]
14    params: RemoteConfigParams,
15}
16
17/// A remote config is always part of a calendar config
18/// so it doesn't need its own ::load and ::write methods
19impl RemoteConfig {
20    pub fn new(provider_slug: ProviderSlug, params: RemoteConfigParams) -> Self {
21        Self {
22            provider_slug,
23            params,
24        }
25    }
26
27    pub fn provider_slug(&self) -> &ProviderSlug {
28        &self.provider_slug
29    }
30
31    pub fn get(&self, key: &str) -> Option<&toml::Value> {
32        self.params().get(key)
33    }
34
35    pub(crate) fn params(&self) -> &RemoteConfigParams {
36        &self.params
37    }
38
39    /// Returns the account identifier for this remote, if present.
40    ///
41    /// Looks for a `{provider}_account` field in the config
42    /// (e.g., `google_account`, `icloud_account`)
43    /// Providers that have a concept of an account should include this field.
44    /// Providers without accounts (e.g., plain CalDAV servers) can omit it.
45    pub fn account_identifier(&self) -> Option<&str> {
46        let key = format!("{}_account", self.provider_slug());
47        self.params().get(&key).and_then(|v| v.as_str())
48    }
49
50    /// The account's email: `{provider}_email` if set, else an email-shaped account identifier.
51    pub fn account_email(&self) -> Option<&str> {
52        let key = format!("{}_email", self.provider_slug());
53        self.params()
54            .get(&key)
55            .and_then(|v| v.as_str())
56            .or_else(|| self.account_identifier())
57            .filter(|email| email.contains('@'))
58    }
59
60    #[cfg(test)]
61    fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
62        toml::from_str(s)
63    }
64
65    #[cfg(test)]
66    fn to_toml(&self) -> Result<String, toml::ser::Error> {
67        toml::to_string(self)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn parses_provider_and_flattened_params() {
77        let toml_str = r#"
78provider = "hooli"
79hooli_calendar_id = "abc@group.calendar.hooli.com"
80hooli_account = "user@hmail.com"
81"#;
82
83        let remote_config = RemoteConfig::from_toml(toml_str).unwrap();
84
85        assert_eq!(remote_config.provider_slug.to_string(), "hooli");
86        assert_eq!(
87            remote_config.get("hooli_account"),
88            Some(&toml::Value::String("user@hmail.com".to_string()))
89        );
90        assert_eq!(
91            remote_config.get("hooli_calendar_id"),
92            Some(&toml::Value::String(
93                "abc@group.calendar.hooli.com".to_string()
94            ))
95        );
96    }
97
98    #[test]
99    fn parses_provider_with_no_params() {
100        let remote_config = RemoteConfig::from_toml(r#"provider = "caldav""#).unwrap();
101
102        assert_eq!(remote_config.provider_slug.to_string(), "caldav");
103        assert!(remote_config.params().is_empty());
104    }
105
106    #[test]
107    fn round_trip_preserves_provider_and_params() {
108        let mut params = RemoteConfigParams::new();
109        params.insert(
110            "hooli_account".to_string(),
111            toml::Value::String("user@hmail.com".to_string()),
112        );
113        params.insert(
114            "hooli_calendar_id".to_string(),
115            toml::Value::String("abc@group.calendar.hooli.com".to_string()),
116        );
117
118        let remote = RemoteConfig::new(ProviderSlug::from("hooli"), params);
119
120        let serialized = remote.to_toml().unwrap();
121        let parsed = RemoteConfig::from_toml(&serialized).unwrap();
122
123        assert_eq!(parsed, remote);
124    }
125
126    #[test]
127    fn account_identifier_returns_provider_account_field() {
128        let toml_str = r#"
129provider = "hooli"
130hooli_account = "user@hmail.com"
131"#;
132
133        let remote_config = RemoteConfig::from_toml(toml_str).unwrap();
134
135        assert_eq!(remote_config.account_identifier(), Some("user@hmail.com"));
136    }
137
138    #[test]
139    fn account_identifier_returns_none_when_missing() {
140        let remote_config = RemoteConfig::from_toml(r#"provider = "hooli""#).unwrap();
141
142        assert_eq!(remote_config.account_identifier(), None);
143    }
144
145    #[test]
146    fn account_email_prefers_provider_email_field() {
147        let toml_str = r#"
148provider = "caldav"
149caldav_account = "AB12345@sync.infomaniak.com"
150caldav_email = "alice@example.com"
151"#;
152
153        let remote_config = RemoteConfig::from_toml(toml_str).unwrap();
154
155        assert_eq!(remote_config.account_email(), Some("alice@example.com"));
156    }
157
158    #[test]
159    fn account_email_falls_back_to_account_identifier() {
160        let toml_str = r#"
161provider = "hooli"
162hooli_account = "user@hmail.com"
163"#;
164
165        let remote_config = RemoteConfig::from_toml(toml_str).unwrap();
166
167        assert_eq!(remote_config.account_email(), Some("user@hmail.com"));
168    }
169
170    #[test]
171    fn account_email_ignores_non_email_identifier() {
172        let toml_str = r#"
173provider = "hooli"
174hooli_account = "12345"
175"#;
176
177        let remote_config = RemoteConfig::from_toml(toml_str).unwrap();
178
179        assert_eq!(remote_config.account_email(), None);
180    }
181
182    #[test]
183    fn missing_provider_errors() {
184        let result = RemoteConfig::from_toml(r#"hooli_account = "user@hmail.com""#);
185
186        assert!(result.is_err());
187    }
188}