use super::Id;
use crate::email::parser::BytesExt;
use core::marker::PhantomData;
use serde::de::DeserializeOwned;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde_json::{value::RawValue, Value};
mod filters;
pub use filters::*;
mod comparator;
pub use comparator::*;
mod argument;
pub use argument::*;
use super::protocol::Method;
use std::collections::HashMap;
pub trait Object {
const NAME: &'static str;
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct JmapSession {
pub capabilities: HashMap<String, CapabilitiesObject>,
pub accounts: HashMap<Id, Account>,
pub primary_accounts: HashMap<String, Id>,
pub username: String,
pub api_url: String,
pub download_url: String,
pub upload_url: String,
pub event_source_url: String,
pub state: String,
#[serde(flatten)]
pub extra_properties: HashMap<String, Value>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CapabilitiesObject {
#[serde(default)]
max_size_upload: u64,
#[serde(default)]
max_concurrent_upload: u64,
#[serde(default)]
max_size_request: u64,
#[serde(default)]
max_concurrent_requests: u64,
#[serde(default)]
max_calls_in_request: u64,
#[serde(default)]
max_objects_in_get: u64,
#[serde(default)]
max_objects_in_set: u64,
#[serde(default)]
collation_algorithms: Vec<String>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Account {
name: String,
is_personal: bool,
is_read_only: bool,
account_capabilities: HashMap<String, Value>,
#[serde(flatten)]
extra_properties: HashMap<String, Value>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Get<OBJ: Object>
where
OBJ: std::fmt::Debug + Serialize,
{
#[serde(skip_serializing_if = "String::is_empty")]
pub account_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
pub ids: Option<JmapArgument<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Vec<String>>,
#[serde(skip)]
_ph: PhantomData<*const OBJ>,
}
impl<OBJ: Object> Get<OBJ>
where
OBJ: std::fmt::Debug + Serialize,
{
pub fn new() -> Self {
Self {
account_id: String::new(),
ids: None,
properties: None,
_ph: PhantomData,
}
}
_impl!(
account_id: String
);
_impl!(
ids: Option<JmapArgument<Vec<String>>>
);
_impl!(
properties: Option<Vec<String>>
);
}
impl<OBJ: Object + Serialize + std::fmt::Debug> Serialize for Get<OBJ> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut fields_no = 0;
if !self.account_id.is_empty() {
fields_no += 1;
}
if !self.ids.is_none() {
fields_no += 1;
}
if !self.properties.is_none() {
fields_no += 1;
}
let mut state = serializer.serialize_struct("Get", fields_no)?;
if !self.account_id.is_empty() {
state.serialize_field("accountId", &self.account_id)?;
}
match self.ids.as_ref() {
None => {}
Some(JmapArgument::Value(ref v)) => state.serialize_field("ids", v)?,
Some(JmapArgument::ResultReference {
ref result_of,
ref name,
ref path,
}) => {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct A<'a> {
result_of: &'a str,
name: &'a str,
path: &'a str,
}
state.serialize_field(
"#ids",
&A {
result_of,
name,
path,
},
)?;
}
}
if !self.properties.is_none() {
state.serialize_field("properties", self.properties.as_ref().unwrap())?;
}
state.end()
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MethodResponse<'a> {
#[serde(borrow)]
pub method_responses: Vec<&'a RawValue>,
#[serde(default)]
pub created_ids: HashMap<Id, Id>,
#[serde(default)]
pub session_state: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GetResponse<OBJ: Object> {
#[serde(skip_serializing_if = "String::is_empty")]
pub account_id: String,
#[serde(default)]
pub state: String,
pub list: Vec<OBJ>,
pub not_found: Vec<String>,
}
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetResponse<OBJ> {
type Error = crate::error::MeliError;
fn try_from(t: &RawValue) -> Result<GetResponse<OBJ>, crate::error::MeliError> {
let res: (String, GetResponse<OBJ>, String) = serde_json::from_str(t.get())?;
assert_eq!(&res.0, &format!("{}/get", OBJ::NAME));
Ok(res.1)
}
}
impl<OBJ: Object> GetResponse<OBJ> {
_impl!(get_mut account_id_mut, account_id: String);
_impl!(get_mut state_mut, state: String);
_impl!(get_mut list_mut, list: Vec<OBJ>);
_impl!(get_mut not_found_mut, not_found: Vec<String>);
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
enum JmapError {
RequestTooLarge,
InvalidArguments,
InvalidResultReference,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Query<F: FilterTrait<OBJ>, OBJ: Object>
where
OBJ: std::fmt::Debug + Serialize,
{
account_id: String,
filter: Option<F>,
sort: Option<Comparator<OBJ>>,
#[serde(default)]
position: u64,
#[serde(skip_serializing_if = "Option::is_none")]
anchor: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "u64_zero")]
anchor_offset: u64,
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<u64>,
#[serde(default = "bool_false")]
calculate_total: bool,
#[serde(skip)]
_ph: PhantomData<*const OBJ>,
}
impl<F: FilterTrait<OBJ>, OBJ: Object> Query<F, OBJ>
where
OBJ: std::fmt::Debug + Serialize,
{
pub fn new() -> Self {
Self {
account_id: String::new(),
filter: None,
sort: None,
position: 0,
anchor: None,
anchor_offset: 0,
limit: None,
calculate_total: false,
_ph: PhantomData,
}
}
_impl!(account_id: String);
_impl!(filter: Option<F>);
_impl!(sort: Option<Comparator<OBJ>>);
_impl!(position: u64);
_impl!(anchor: Option<String>);
_impl!(anchor_offset: u64);
_impl!(limit: Option<u64>);
_impl!(calculate_total: bool);
}
pub fn u64_zero(num: &u64) -> bool {
*num == 0
}
pub fn bool_false() -> bool {
false
}
pub fn bool_true() -> bool {
true
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct QueryResponse<OBJ: Object> {
#[serde(skip_serializing_if = "String::is_empty", default)]
pub account_id: String,
pub query_state: String,
pub can_calculate_changes: bool,
pub position: u64,
pub ids: Vec<Id>,
#[serde(default)]
pub total: u64,
#[serde(default)]
pub limit: u64,
#[serde(skip)]
_ph: PhantomData<*const OBJ>,
}
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for QueryResponse<OBJ> {
type Error = crate::error::MeliError;
fn try_from(t: &RawValue) -> Result<QueryResponse<OBJ>, crate::error::MeliError> {
let res: (String, QueryResponse<OBJ>, String) = serde_json::from_str(t.get())?;
assert_eq!(&res.0, &format!("{}/query", OBJ::NAME));
Ok(res.1)
}
}
impl<OBJ: Object> QueryResponse<OBJ> {
_impl!(get_mut ids_mut, ids: Vec<Id>);
}
pub struct ResultField<M: Method<OBJ>, OBJ: Object> {
pub field: &'static str,
pub _ph: PhantomData<*const (OBJ, M)>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Changes<OBJ: Object>
where
OBJ: std::fmt::Debug + Serialize,
{
#[serde(skip_serializing_if = "String::is_empty")]
pub account_id: String,
pub since_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_changes: Option<u64>,
#[serde(skip)]
_ph: PhantomData<*const OBJ>,
}
impl<OBJ: Object> Changes<OBJ>
where
OBJ: std::fmt::Debug + Serialize,
{
pub fn new() -> Self {
Self {
account_id: String::new(),
since_state: String::new(),
max_changes: None,
_ph: PhantomData,
}
}
_impl!(
account_id: String
);
_impl!(
since_state: String
);
_impl!(
max_changes: Option<u64>
);
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ChangesResponse<OBJ: Object> {
#[serde(skip_serializing_if = "String::is_empty")]
pub account_id: String,
pub old_state: String,
pub new_state: String,
pub has_more_changes: bool,
pub created: Vec<Id>,
pub updated: Vec<Id>,
pub destroyed: Vec<Id>,
#[serde(skip)]
_ph: PhantomData<*const OBJ>,
}
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for ChangesResponse<OBJ> {
type Error = crate::error::MeliError;
fn try_from(t: &RawValue) -> Result<ChangesResponse<OBJ>, crate::error::MeliError> {
let res: (String, ChangesResponse<OBJ>, String) = serde_json::from_str(t.get())?;
assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME));
Ok(res.1)
}
}
impl<OBJ: Object> ChangesResponse<OBJ> {
_impl!(get_mut account_id_mut, account_id: String);
_impl!(get_mut old_state_mut, old_state: String);
_impl!(get_mut new_state_mut, new_state: String);
_impl!(get has_more_changes, has_more_changes: bool);
_impl!(get_mut created_mut, created: Vec<String>);
_impl!(get_mut updated_mut, updated: Vec<String>);
_impl!(get_mut destroyed_mut, destroyed: Vec<String>);
}
pub fn download_request_format(
session: &JmapSession,
account_id: &Id,
blob_id: &Id,
name: Option<String>,
) -> String {
let mut ret = String::with_capacity(
session.download_url.len()
+ blob_id.len()
+ name.as_ref().map(|n| n.len()).unwrap_or(0)
+ account_id.len(),
);
let mut prev_pos = 0;
while let Some(pos) = session.download_url.as_bytes()[prev_pos..].find(b"{") {
ret.push_str(&session.download_url[prev_pos..prev_pos + pos]);
prev_pos += pos;
if session.download_url[prev_pos..].starts_with("{accountId}") {
ret.push_str(account_id);
prev_pos += "{accountId}".len();
} else if session.download_url[prev_pos..].starts_with("{blobId}") {
ret.push_str(blob_id);
prev_pos += "{blobId}".len();
} else if session.download_url[prev_pos..].starts_with("{name}") {
ret.push_str(name.as_ref().map(String::as_str).unwrap_or(""));
prev_pos += "{name}".len();
}
}
if prev_pos != session.download_url.len() {
ret.push_str(&session.download_url[prev_pos..]);
}
ret
}
pub fn upload_request_format(session: &JmapSession, account_id: &Id) -> String {
let mut ret = String::with_capacity(session.upload_url.len() + account_id.len());
let mut prev_pos = 0;
while let Some(pos) = session.upload_url.as_bytes()[prev_pos..].find(b"{") {
ret.push_str(&session.upload_url[prev_pos..prev_pos + pos]);
prev_pos += pos;
if session.upload_url[prev_pos..].starts_with("{accountId}") {
ret.push_str(account_id);
prev_pos += "{accountId}".len();
break;
} else {
ret.push('{');
prev_pos += 1;
}
}
if prev_pos != session.upload_url.len() {
ret.push_str(&session.upload_url[prev_pos..]);
}
ret
}