diff options
Diffstat (limited to 'src/session.rs')
| -rw-r--r-- | src/session.rs | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..82a3563 --- /dev/null +++ b/src/session.rs @@ -0,0 +1,326 @@ +use serde::{Deserialize, Serialize}; +use std::any::Any; +use std::collections::BTreeMap; +use std::collections::btree_map::{Iter, Keys}; + +use crate::capabilities::{AccountCapabilities, CapabilityParsers, ServerCapabilities}; +use crate::*; + +// 2: JMAP Session Resource {{{ + +/// Intermediate Serde-Powered Account Struct +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ISessionAccount { + pub name: String, + pub isPersonal: bool, + pub isReadOnly: bool, + pub accountCapabilities: JObj<String, JObject>, +} + +/// Intermediate Serde-Powered Session Struct +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize, Debug)] +pub struct ISession { + pub capabilities: JObj<String, JObject>, + pub accounts: JObj<JMAPId, ISessionAccount>, + pub primaryAccounts: JObj<String, JMAPId>, + pub username: String, + pub apiUrl: String, + pub downloadUrl: String, + pub eventSourceUrl: String, + pub state: String, +} +//}}} +// 2.a Library specifics for the Session Resource {{{ +/// Fully evaluated jmap account +#[derive(Debug)] +pub struct JMAPAccount { + pub base: ISessionAccount, + pub capabilities: AccountCapabilities, +} + +/// Evaluated session (the accounts are evaluated lazily) +#[derive(Debug)] +pub struct JMAPSession { + base: ISession, + capabilities: ServerCapabilities, + accounts: BTreeMap<JMAPId, Option<JMAPAccount>>, +} + +impl JMAPSession { + /// Get session from the json + pub fn from_json(json: &str, parsers: CapabilityParsers) -> Result<Self, serde_json::Error> { + let session: ISession = serde_json::from_str(json)?; + Ok(Self::from_intermediate(session, parsers)) + } + + /// Get session from the ISession object + pub fn from_intermediate(session: ISession, parsers: CapabilityParsers) -> Self { + let mut accounts = BTreeMap::new(); + for acc in session.accounts.keys() { + accounts.insert(acc.clone(), None); + } + Self { + capabilities: parsers.server_capabilities(&session), + base: session, + accounts, + } + } + + /// Show all the available accounts + pub fn list_accounts(&self) -> Keys<'_, JMAPId, Option<JMAPAccount>> { + self.accounts.keys() + } + + /// Get all the json-based account intermediate structs in an iterator + pub fn intermediate_accounts(&self) -> Iter<'_, JMAPId, ISessionAccount> { + self.base.accounts.iter() + } + + /// Get a singular json-base account intermediate + pub fn get_intermediate_account(&self, account_id: &JMAPId) -> Option<&ISessionAccount> { + self.base.accounts.get(account_id) + } + + /// Get the fully evaluated account if it was initialized + pub fn get_account(&self, account_id: &JMAPId) -> Option<&JMAPAccount> { + if let Some(acc) = self.accounts.get(account_id) { + acc.as_ref() + } else { + None + } + } + + /// Get the fully evaluated account if it was initialized, mutable + pub fn get_account_mut(&mut self, account_id: &JMAPId) -> Option<&mut JMAPAccount> { + if let Some(acc) = self.accounts.get_mut(account_id) { + acc.as_mut() + } else { + None + } + } + + /// Evaluate account + fn eval_account(&mut self, account_id: &JMAPId) -> Option<bool> { + let evaluated_account = self.accounts.get(account_id).is_some_and(|x| x.is_some()); + if evaluated_account { + return Some(false); + } + + if let Some(capabilities) = self + .capabilities + .account_capabilities(account_id, &self.base) + { + let base = self + .get_intermediate_account(account_id) + .expect("Already checked") + .clone(); + let acc = self + .accounts + .get_mut(account_id) + .expect("Accounts and intermediateAccounts have the same keys"); + *acc = Some(JMAPAccount { base, capabilities }); + Some(true) + } else { + None + } + } + + /// get_account that evaluates the account if necessary + pub fn eval_get_account(&mut self, account_id: &JMAPId) -> Option<&JMAPAccount> { + if self.eval_account(account_id).is_some() { + Some( + self.get_account(account_id) + .expect("We should have made this account just now"), + ) + } else { + None + } + } + + /// get_account_mut that evaluates the account if necessary + pub fn eval_get_account_mut(&mut self, account_id: &JMAPId) -> Option<&mut JMAPAccount> { + if self.eval_account(account_id).is_some() { + Some( + self.get_account_mut(account_id) + .expect("We should have made this account just now"), + ) + } else { + None + } + } +} +// }}} +// Appendix A: tests {{{ +#[cfg(test)] +mod test { + use crate::JMAPId; + use crate::capabilities::{ + Capability, CapabilityCollection, JMAPCoreCapability, URN_JMAP_CORE, + }; + use std::assert_matches; + + #[test] + fn intermediate_parse_test() { + let in1 = r#" + { + "capabilities": { + "urn:ietf:params:jmap:core": { + "maxSizeUpload": 50000000, + "maxConcurrentUpload": 8, + "maxSizeRequest": 10000000, + "maxConcurrentRequests": 8, + "maxCallsInRequest": 32, + "maxObjectsInGet": 256, + "maxObjectsInSet": 128, + "collationAlgorithms": [ + "i;ascii-numeric", + "i;ascii-casemap", + "i;unicode-casemap" + ] + }, + "urn:ietf:params:jmap:mail": {}, + "urn:ietf:params:jmap:contacts": {}, + "https://example.com/apis/foobar": { + "maxFoosFinangled": 42 + } + }, + "accounts": { + "A13824": { + "name": "john@example.com", + "isPersonal": true, + "isReadOnly": false, + "accountCapabilities": { + "urn:ietf:params:jmap:mail": { + "maxMailboxesPerEmail": null, + "maxMailboxDepth": 10 + }, + "urn:ietf:params:jmap:contacts": { + } + } + }, + "A97813": { + "name": "jane@example.com", + "isPersonal": false, + "isReadOnly": true, + "accountCapabilities": { + "urn:ietf:params:jmap:mail": { + "maxMailboxesPerEmail": 1, + "maxMailboxDepth": 10 + } + } + } + }, + "primaryAccounts": { + "urn:ietf:params:jmap:mail": "A13824", + "urn:ietf:params:jmap:contacts": "A13824" + }, + "username": "john@example.com", + "apiUrl": "https://jmap.example.com/api/", + "downloadUrl": "https://jmap.example.com/download/{accountId}/{blobId}/{name}?accept={type}", + "uploadUrl": "https://jmap.example.com/upload/{accountId}/", + "eventSourceUrl": "https://jmap.example.com/eventsource/?types={types}&closeafter={closeafter}&ping={ping}", + "state": "75128aab4b1b" +} + "#; + + let intermediate: Result<crate::session::ISession, serde_json::Error> = + serde_json::from_str(in1); + assert_matches!(intermediate, Ok(_)); + println!("{:?}", intermediate); + } + + #[test] + fn basic_init() { + let in1 = r#" + { + "capabilities": { + "urn:ietf:params:jmap:core": { + "maxSizeUpload": 50000000, + "maxConcurrentUpload": 8, + "maxSizeRequest": 10000000, + "maxConcurrentRequests": 8, + "maxCallsInRequest": 32, + "maxObjectsInGet": 256, + "maxObjectsInSet": 128, + "collationAlgorithms": [ + "i;ascii-numeric", + "i;ascii-casemap", + "i;unicode-casemap" + ] + }, + "urn:ietf:params:jmap:mail": {}, + "urn:ietf:params:jmap:contacts": {}, + "https://example.com/apis/foobar": { + "maxFoosFinangled": 42 + } + }, + "accounts": { + "A13824": { + "name": "john@example.com", + "isPersonal": true, + "isReadOnly": false, + "accountCapabilities": { + "urn:ietf:params:jmap:mail": { + "maxMailboxesPerEmail": null, + "maxMailboxDepth": 10 + }, + "urn:ietf:params:jmap:contacts": { + } + } + } + }, + "primaryAccounts": { + "urn:ietf:params:jmap:mail": "A13824", + "urn:ietf:params:jmap:contacts": "A13824" + }, + "username": "john@example.com", + "apiUrl": "https://jmap.example.com/api/", + "downloadUrl": "https://jmap.example.com/download/{accountId}/{blobId}/{name}?accept={type}", + "uploadUrl": "https://jmap.example.com/upload/{accountId}/", + "eventSourceUrl": "https://jmap.example.com/eventsource/?types={types}&closeafter={closeafter}&ping={ping}", + "state": "75128aab4b1b" +} + "#; + + let parsers = crate::session::CapabilityParsers::default(); + let mut session = match crate::session::JMAPSession::from_json(in1, parsers) { + Ok(i) => i, + Err(e) => unreachable!("Error in parsing json {:?}", e), + }; + print!("{:?}", session); + + let list: Vec<JMAPId> = session.list_accounts().map(Clone::clone).collect(); + assert_eq!(list, vec![JMAPId("A13824".to_string())]); + assert!( + session + .capabilities + .get_capability::<JMAPCoreCapability>(URN_JMAP_CORE) + .is_some() + ); + let account = session.get_account(&crate::JMAPId("A13824".to_string())); + assert_matches!(account, None); + let account_opt = session.eval_get_account(&crate::JMAPId("A13824".to_string())); + assert_matches!(account_opt, Some(_)); + let account_opt = session.eval_get_account_mut(&crate::JMAPId("A13824".to_string())); + let account = account_opt.unwrap(); + assert_eq!( + account + .capabilities + .0 + .keys() + .map(Clone::clone) + .collect::<Vec<String>>(), + vec![URN_JMAP_CORE] + ); + let cap: &mut JMAPCoreCapability = account + .capabilities + .get_capability_mut(URN_JMAP_CORE) + .unwrap(); + assert_eq!(cap.account(), Some(JMAPId("A13824".to_string()))); + } +} +// }}} + +// vim: set foldmethod=marker |
