1use crate::{
13 email::{MailCapabilities, SubmissionCapabilities},
14 URI,
15};
16use ahash::AHashMap;
17use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Session {
21 #[serde(rename = "capabilities")]
22 capabilities: AHashMap<String, Capabilities>,
23
24 #[serde(rename = "accounts")]
25 accounts: AHashMap<String, Account>,
26
27 #[serde(rename = "primaryAccounts")]
28 primary_accounts: AHashMap<String, String>,
29
30 #[serde(rename = "username")]
31 username: String,
32
33 #[serde(rename = "apiUrl")]
34 api_url: String,
35
36 #[serde(rename = "downloadUrl")]
37 download_url: String,
38
39 #[serde(rename = "uploadUrl")]
40 upload_url: String,
41
42 #[serde(rename = "eventSourceUrl")]
43 event_source_url: String,
44
45 #[serde(rename = "state")]
46 state: String,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Account {
51 #[serde(rename = "name")]
52 name: String,
53
54 #[serde(rename = "isPersonal")]
55 is_personal: bool,
56
57 #[serde(rename = "isReadOnly")]
58 is_read_only: bool,
59
60 #[serde(rename = "accountCapabilities")]
61 account_capabilities: AHashMap<String, Capabilities>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(untagged)]
66pub enum Capabilities {
67 Core(CoreCapabilities),
68 Mail(MailCapabilities),
69 Submission(SubmissionCapabilities),
70 WebSocket(WebSocketCapabilities),
71 Sieve(SieveCapabilities),
72 Empty(EmptyCapabilities),
73 Other(serde_json::Value),
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct CoreCapabilities {
78 #[serde(rename = "maxSizeUpload")]
79 max_size_upload: usize,
80
81 #[serde(rename = "maxConcurrentUpload")]
82 max_concurrent_upload: usize,
83
84 #[serde(rename = "maxSizeRequest")]
85 max_size_request: usize,
86
87 #[serde(rename = "maxConcurrentRequests")]
88 max_concurrent_requests: usize,
89
90 #[serde(rename = "maxCallsInRequest")]
91 max_calls_in_request: usize,
92
93 #[serde(rename = "maxObjectsInGet")]
94 max_objects_in_get: usize,
95
96 #[serde(rename = "maxObjectsInSet")]
97 max_objects_in_set: usize,
98
99 #[serde(rename = "collationAlgorithms")]
100 collation_algorithms: Vec<String>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct WebSocketCapabilities {
105 #[serde(rename = "url")]
106 url: String,
107 #[serde(rename = "supportsPush")]
108 supports_push: bool,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct SieveCapabilities {
113 #[serde(rename = "implementation")]
114 implementation: Option<String>,
115 #[serde(rename = "maxSizeScriptName")]
116 max_script_name: Option<usize>,
117 #[serde(rename = "maxSizeScript")]
118 max_script_size: Option<usize>,
119 #[serde(rename = "maxNumberScripts")]
120 max_scripts: Option<usize>,
121 #[serde(rename = "maxNumberRedirects")]
122 max_redirects: Option<usize>,
123 #[serde(rename = "sieveExtensions")]
124 extensions: Vec<String>,
125 #[serde(rename = "notificationMethods")]
126 notification_methods: Option<Vec<String>>,
127 #[serde(rename = "externalLists")]
128 ext_lists: Option<Vec<String>>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct EmptyCapabilities {}
133
134impl Session {
135 pub fn capabilities(&self) -> impl Iterator<Item = &String> {
136 self.capabilities.keys()
137 }
138
139 pub fn capability(&self, capability: impl AsRef<str>) -> Option<&Capabilities> {
140 self.capabilities.get(capability.as_ref())
141 }
142
143 pub fn has_capability(&self, capability: impl AsRef<str>) -> bool {
144 self.capabilities.contains_key(capability.as_ref())
145 }
146
147 pub fn websocket_capabilities(&self) -> Option<&WebSocketCapabilities> {
148 self.capabilities
149 .get(URI::WebSocket.as_ref())
150 .and_then(|v| match v {
151 Capabilities::WebSocket(capabilities) => Some(capabilities),
152 _ => None,
153 })
154 }
155
156 pub fn core_capabilities(&self) -> Option<&CoreCapabilities> {
157 self.capabilities
158 .get(URI::Core.as_ref())
159 .and_then(|v| match v {
160 Capabilities::Core(capabilities) => Some(capabilities),
161 _ => None,
162 })
163 }
164
165 pub fn mail_capabilities(&self) -> Option<&MailCapabilities> {
166 self.capabilities
167 .get(URI::Mail.as_ref())
168 .and_then(|v| match v {
169 Capabilities::Mail(capabilities) => Some(capabilities),
170 _ => None,
171 })
172 }
173
174 pub fn submission_capabilities(&self) -> Option<&SubmissionCapabilities> {
175 self.capabilities
176 .get(URI::Submission.as_ref())
177 .and_then(|v| match v {
178 Capabilities::Submission(capabilities) => Some(capabilities),
179 _ => None,
180 })
181 }
182
183 pub fn sieve_capabilities(&self) -> Option<&SieveCapabilities> {
184 self.capabilities
185 .get(URI::Sieve.as_ref())
186 .and_then(|v| match v {
187 Capabilities::Sieve(capabilities) => Some(capabilities),
188 _ => None,
189 })
190 }
191
192 pub fn accounts(&self) -> impl Iterator<Item = &String> {
193 self.accounts.keys()
194 }
195
196 pub fn account(&self, account: &str) -> Option<&Account> {
197 self.accounts.get(account)
198 }
199
200 pub fn primary_accounts(&self) -> impl Iterator<Item = (&String, &String)> {
201 self.primary_accounts.iter()
202 }
203
204 pub fn username(&self) -> &str {
205 &self.username
206 }
207
208 pub fn api_url(&self) -> &str {
209 &self.api_url
210 }
211
212 pub fn download_url(&self) -> &str {
213 &self.download_url
214 }
215
216 pub fn upload_url(&self) -> &str {
217 &self.upload_url
218 }
219
220 pub fn event_source_url(&self) -> &str {
221 &self.event_source_url
222 }
223
224 pub fn state(&self) -> &str {
225 &self.state
226 }
227}
228
229impl Account {
230 pub fn name(&self) -> &str {
231 &self.name
232 }
233
234 pub fn is_personal(&self) -> bool {
235 self.is_personal
236 }
237
238 pub fn is_read_only(&self) -> bool {
239 self.is_read_only
240 }
241
242 pub fn capabilities(&self) -> impl Iterator<Item = &String> {
243 self.account_capabilities.keys()
244 }
245
246 pub fn capability(&self, capability: &str) -> Option<&Capabilities> {
247 self.account_capabilities.get(capability)
248 }
249}
250
251impl CoreCapabilities {
252 pub fn max_size_upload(&self) -> usize {
253 self.max_size_upload
254 }
255
256 pub fn max_concurrent_upload(&self) -> usize {
257 self.max_concurrent_upload
258 }
259
260 pub fn max_size_request(&self) -> usize {
261 self.max_size_request
262 }
263
264 pub fn max_concurrent_requests(&self) -> usize {
265 self.max_concurrent_requests
266 }
267
268 pub fn max_calls_in_request(&self) -> usize {
269 self.max_calls_in_request
270 }
271
272 pub fn max_objects_in_get(&self) -> usize {
273 self.max_objects_in_get
274 }
275
276 pub fn max_objects_in_set(&self) -> usize {
277 self.max_objects_in_set
278 }
279
280 pub fn collation_algorithms(&self) -> &[String] {
281 &self.collation_algorithms
282 }
283}
284
285impl WebSocketCapabilities {
286 pub fn url(&self) -> &str {
287 &self.url
288 }
289
290 pub fn supports_push(&self) -> bool {
291 self.supports_push
292 }
293}
294
295impl SieveCapabilities {
296 pub fn max_script_name_size(&self) -> usize {
297 self.max_script_name.unwrap_or(512)
298 }
299
300 pub fn max_script_size(&self) -> Option<usize> {
301 self.max_script_size
302 }
303
304 pub fn max_number_scripts(&self) -> Option<usize> {
305 self.max_scripts
306 }
307
308 pub fn max_number_redirects(&self) -> Option<usize> {
309 self.max_redirects
310 }
311
312 pub fn sieve_extensions(&self) -> &[String] {
313 &self.extensions
314 }
315
316 pub fn notification_methods(&self) -> Option<&[String]> {
317 self.notification_methods.as_deref()
318 }
319
320 pub fn external_lists(&self) -> Option<&[String]> {
321 self.ext_lists.as_deref()
322 }
323}
324
325pub trait URLParser: Sized {
326 fn parse(value: &str) -> Option<Self>;
327}
328
329pub enum URLPart<T: URLParser> {
330 Value(String),
331 Parameter(T),
332}
333
334impl<T: URLParser> URLPart<T> {
335 pub fn parse(url: &str) -> crate::Result<Vec<URLPart<T>>> {
336 let mut parts = Vec::new();
337 let mut buf = String::with_capacity(url.len());
338 let mut in_parameter = false;
339
340 for ch in url.chars() {
341 match ch {
342 '{' => {
343 if !buf.is_empty() {
344 parts.push(URLPart::Value(buf.clone()));
345 buf.clear();
346 }
347 in_parameter = true;
348 }
349 '}' => {
350 if in_parameter && !buf.is_empty() {
351 parts.push(URLPart::Parameter(T::parse(&buf).ok_or_else(|| {
352 crate::Error::Internal(format!(
353 "Invalid parameter '{}' in URL: {}",
354 buf, url
355 ))
356 })?));
357 buf.clear();
358 } else {
359 return Err(crate::Error::Internal(format!("Invalid URL: {}", url)));
360 }
361 in_parameter = false;
362 }
363 _ => {
364 buf.push(ch);
365 }
366 }
367 }
368
369 if !buf.is_empty() {
370 if !in_parameter {
371 parts.push(URLPart::Value(buf.clone()));
372 } else {
373 return Err(crate::Error::Internal(format!("Invalid URL: {}", url)));
374 }
375 }
376
377 Ok(parts)
378 }
379}