use std::{borrow::Cow, collections::HashMap};
use jmap_proto::{extensions::sharing as proto_sharing, Value};
use serde::{
de::{value::CowStrDeserializer, DeserializeSeed, MapAccess, Visitor},
forward_to_deserialize_any, Deserialize, Deserializer, Serialize,
};
use uuid::Uuid;
pub mod contacts;
pub mod core;
pub mod sharing;
pub trait JmapExtension {
const EXTENSION: &'static str;
}
pub trait JmapDataExtension<D>: JmapExtension {
const ENDPOINT: &'static str;
}
pub trait JmapSessionCapabilityExtension: JmapExtension {
type Metadata: Serialize;
fn build(&self, user: Uuid) -> Self::Metadata;
}
pub trait JmapAccountCapabilityExtension: JmapExtension {
type Metadata: Serialize;
fn build(&self, user: Uuid, account: Uuid) -> Self::Metadata;
}
pub struct ExtensionRegistry {
pub core: core::Core,
pub contacts: contacts::Contacts,
pub sharing_principals: sharing::Principals,
pub sharing_principals_owner: sharing::PrincipalsOwner,
}
impl ExtensionRegistry {
pub fn build_session_capabilities(&self, user: Uuid) -> HashMap<Cow<'static, str>, Value> {
let mut out = HashMap::new();
out.insert(
Cow::Borrowed(core::Core::EXTENSION),
serde_json::to_value(JmapSessionCapabilityExtension::build(&self.core, user)).unwrap(),
);
out.insert(
Cow::Borrowed(sharing::Principals::EXTENSION),
serde_json::to_value(JmapSessionCapabilityExtension::build(
&self.sharing_principals,
user,
))
.unwrap(),
);
out
}
}
pub enum ConcreteData<'a> {
AddressBook(contacts::AddressBook),
Principal(proto_sharing::Principal<'a>),
ShareNotification(proto_sharing::ShareNotification<'a>),
}
impl<'a> ConcreteData<'a> {
pub fn parse(endpoint: &str, data: ResolvedArguments<'a>) -> Option<Self> {
match endpoint {
<contacts::Contacts as JmapDataExtension<contacts::AddressBook>>::ENDPOINT => {
Some(Self::AddressBook(Deserialize::deserialize(data).unwrap()))
}
<sharing::Principals as JmapDataExtension<proto_sharing::Principal>>::ENDPOINT => {
Some(Self::Principal(Deserialize::deserialize(data).unwrap()))
},
<sharing::Principals as JmapDataExtension<proto_sharing::ShareNotification>>::ENDPOINT => {
Some(Self::ShareNotification(Deserialize::deserialize(data).unwrap()))
},
_ => None,
}
}
}
pub struct ResolvedArguments<'a>(pub HashMap<Cow<'a, str>, Cow<'a, Value>>);
impl<'de> Deserializer<'de> for ResolvedArguments<'de> {
type Error = serde_json::Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_map(ResolvedArgumentsVisitor {
iter: self.0.into_iter(),
value: None,
})
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
}
struct ResolvedArgumentsVisitor<'de> {
iter: <HashMap<Cow<'de, str>, Cow<'de, Value>> as IntoIterator>::IntoIter,
value: Option<Cow<'de, Value>>,
}
impl<'de> MapAccess<'de> for ResolvedArgumentsVisitor<'de> {
type Error = serde_json::Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: DeserializeSeed<'de>,
{
let Some((key, value)) = self.iter.next() else {
return Ok(None);
};
self.value = Some(value);
seed.deserialize(CowStrDeserializer::new(key)).map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: DeserializeSeed<'de>,
{
let value = self
.value
.take()
.ok_or(serde::de::Error::custom("value is missing"))?;
match value {
Cow::Owned(v) => seed.deserialize(v),
Cow::Borrowed(v) => seed.deserialize(v),
}
}
}