summaryrefslogtreecommitdiff
path: root/src/session.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/session.rs326
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