use std::any::Any; use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use crate::session::ISession; use crate::*; // JMAP Core capability {{{ /// JMAP Core URN pub const URN_JMAP_CORE: &str = "urn:ietf:params:jmap:core"; // Capabilities {{{ pub trait Capability: 'static + Sized { const URN: &str; fn from_object(obj: &JObject) -> Self; fn account_cap(&self, account_id: JMAPId, obj: &JObject) -> Self; fn account(&self) -> Option; fn from_object_dyn(obj: &JObject) -> Box { Box::new(Self::from_object(obj)) } fn is_account_capability(&self) -> bool { self.account().is_some() } } pub trait CapabilityDyn { // Get urn of the capability fn get_urn(&self) -> &'static str; // Convert to any for downcasting, immutable fn as_any(&self) -> &dyn Any; /// Convert to any for downcasting, mutable fn as_any_mut(&mut self) -> &mut dyn Any; /// Convert to any for downcasting, consumes object fn to_any(self) -> Box; /// Given a capability and accountCapabilities Object get a capability fn account_cap_dyn(&self, account_id: JMAPId, obj: &JObject) -> Box; /// Return the account if this capabilities has been given info for account fn account_dyn(&self) -> Option; } impl CapabilityDyn for T { fn get_urn(&self) -> &'static str { T::URN } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn to_any(self) -> Box { Box::new(self) } fn account_cap_dyn(&self, account_id: JMAPId, obj: &JObject) -> Box { Box::new(self.account_cap(account_id, obj)) } fn account_dyn(&self) -> Option { self.account() } } /// A function that parses a JObject into a Capability Trait object. /// This capability will be able to implement functionality to the JMAPSession object. pub type CapabilityParser = Box Box>; /// Collection of CapabilityParser pub struct CapabilityRegistry(pub BTreeMap); impl Default for CapabilityRegistry { fn default() -> Self { let mut c = Self::new(); c.add_capability::(); c } } impl CapabilityRegistry { /// Create a collection of parsers pub fn new() -> Self { Self(BTreeMap::new()) } /// Insert a parser pub fn add_capability(&mut self) { self.0 .insert(T::URN.to_string(), Box::new(T::from_object_dyn)); } /// Use the parsers to create a list of capabilities supported by the server in the session. pub fn server_capabilities(&self, session: &ISession) -> ServerCapabilities { let mut out = BTreeMap::new(); for (urn, cap) in session.capabilities.iter() { if let Some(parser) = self.0.get(urn) { out.insert(urn.clone(), parser(cap)); }; } ServerCapabilities(out) } } /// The backing datatype in the CapabilityCollection trait. pub type CapabilityMap = BTreeMap>; /// Trait for list of capabilities, or any URN -> Capabilty collection pub trait CapabilityCollection { fn inner(&self) -> &CapabilityMap; fn inner_mut(&mut self) -> &mut CapabilityMap; fn get_capability(&self) -> Option<&T> { self.inner().get(T::URN)?.as_any().downcast_ref::() } fn get_capability_mut(&mut self) -> Option<&mut T> { self.inner_mut() .get_mut(T::URN)? .as_any_mut() .downcast_mut::() } } /// Collection pub struct ServerCapabilities(pub CapabilityMap); impl CapabilityCollection for ServerCapabilities { fn inner(&self) -> &CapabilityMap { &self.0 } fn inner_mut(&mut self) -> &mut CapabilityMap { &mut self.0 } } impl std::fmt::Debug for ServerCapabilities { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("ServerCapabilities") .field(&self.0.keys().collect::>()) .finish() } } impl ServerCapabilities { /// Evaluate AccountCapabilities using this list of capabilities and the intermediate session pub fn account_capabilities( &self, client: &JMAPId, session: &ISession, ) -> Option { session.accounts.get(client).map(|acc| { let mut out = BTreeMap::new(); let empty = JObj::new(); for (urn, scap) in self.0.iter() { let ccap = acc.accountCapabilities.get(urn).unwrap_or(&empty); out.insert(urn.clone(), scap.account_cap_dyn(client.clone(), ccap)); } AccountCapabilities(out) }) } } /// List of capabilities, specific to an account. pub struct AccountCapabilities(pub CapabilityMap); impl std::fmt::Debug for AccountCapabilities { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("AccountCapabilities") .field(&self.0.keys().collect::>()) .finish() } } impl CapabilityCollection for AccountCapabilities { fn inner(&self) -> &CapabilityMap { &self.0 } fn inner_mut(&mut self) -> &mut CapabilityMap { &mut self.0 } } // }}} /// JMAP Core capability as outlined in RFC 8620 #[allow(non_snake_case)] #[derive(Serialize, Deserialize, Clone)] pub struct JMAPCoreCapability { pub maxSizeUpload: JUint, pub maxConcurrentUpload: JUint, pub maxSizeRequest: JUint, pub maxConcurrentRequests: JUint, pub maxCallsInRequest: JUint, pub maxObjectsInGet: JUint, pub maxObjectsInSet: JUint, pub collationAlgorithms: JArr, #[serde(skip)] accountId: Option, } impl Capability for JMAPCoreCapability { const URN: &str = URN_JMAP_CORE; fn from_object(obj: &JObject) -> Self { // TODO: Unwrap let out: Self = serde_json::from_value(JUnknown::Object(obj.clone()).into()).unwrap(); out } fn account_cap(&self, account_id: JMAPId, _: &JObject) -> Self { let mut out = self.clone(); out.accountId = Some(account_id); out } fn account(&self) -> Option { self.accountId.clone() } } // }}}