jmap-client/src/core/set.rs

462 lines
14 KiB
Rust

/*
* Copyright Stalwart Labs Ltd. See the COPYING
* file at the top-level directory of this distribution.
*
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
* option. This file may not be copied, modified, or distributed
* except according to those terms.
*/
use crate::Error;
use ahash::AHashMap;
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use super::{request::ResultReference, Object, RequestParams};
pub trait SetObject: Object {
type SetArguments: Default;
fn new(create_id: Option<usize>) -> Self;
fn create_id(&self) -> Option<String>;
}
#[derive(Debug, Clone, Serialize)]
pub struct SetRequest<O: SetObject> {
#[serde(rename = "accountId")]
#[serde(skip_serializing_if = "Option::is_none")]
account_id: Option<String>,
#[serde(rename = "ifInState")]
#[serde(skip_serializing_if = "Option::is_none")]
if_in_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
create: Option<AHashMap<String, O>>,
#[serde(skip_serializing_if = "Option::is_none")]
update: Option<AHashMap<String, O>>,
#[serde(skip_serializing_if = "Option::is_none")]
destroy: Option<Vec<String>>,
#[serde(rename = "#destroy")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "Option::is_none")]
destroy_ref: Option<ResultReference>,
#[serde(flatten)]
arguments: O::SetArguments,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SetResponse<O: SetObject> {
#[serde(rename = "accountId")]
account_id: Option<String>,
#[serde(rename = "oldState")]
old_state: Option<String>,
#[serde(rename = "newState")]
new_state: Option<String>,
#[serde(rename = "created")]
created: Option<AHashMap<String, O>>,
#[serde(rename = "updated")]
updated: Option<AHashMap<String, Option<O>>>,
#[serde(rename = "destroyed")]
destroyed: Option<Vec<String>>,
#[serde(rename = "notCreated")]
not_created: Option<AHashMap<String, SetError<O::Property>>>,
#[serde(rename = "notUpdated")]
not_updated: Option<AHashMap<String, SetError<O::Property>>>,
#[serde(rename = "notDestroyed")]
not_destroyed: Option<AHashMap<String, SetError<O::Property>>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SetError<U>
where
U: Display,
{
#[serde(rename = "type")]
pub type_: SetErrorType,
description: Option<String>,
properties: Option<Vec<U>>,
}
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
pub enum SetErrorType {
#[serde(rename = "forbidden")]
Forbidden,
#[serde(rename = "overQuota")]
OverQuota,
#[serde(rename = "tooLarge")]
TooLarge,
#[serde(rename = "rateLimit")]
RateLimit,
#[serde(rename = "notFound")]
NotFound,
#[serde(rename = "invalidPatch")]
InvalidPatch,
#[serde(rename = "willDestroy")]
WillDestroy,
#[serde(rename = "invalidProperties")]
InvalidProperties,
#[serde(rename = "singleton")]
Singleton,
#[serde(rename = "mailboxHasChild")]
MailboxHasChild,
#[serde(rename = "mailboxHasEmail")]
MailboxHasEmail,
#[serde(rename = "blobNotFound")]
BlobNotFound,
#[serde(rename = "tooManyKeywords")]
TooManyKeywords,
#[serde(rename = "tooManyMailboxes")]
TooManyMailboxes,
#[serde(rename = "forbiddenFrom")]
ForbiddenFrom,
#[serde(rename = "invalidEmail")]
InvalidEmail,
#[serde(rename = "tooManyRecipients")]
TooManyRecipients,
#[serde(rename = "noRecipients")]
NoRecipients,
#[serde(rename = "invalidRecipients")]
InvalidRecipients,
#[serde(rename = "forbiddenMailFrom")]
ForbiddenMailFrom,
#[serde(rename = "forbiddenToSend")]
ForbiddenToSend,
#[serde(rename = "cannotUnsend")]
CannotUnsend,
#[serde(rename = "alreadyExists")]
AlreadyExists,
#[serde(rename = "invalidScript")]
InvalidScript,
#[serde(rename = "scriptIsActive")]
ScriptIsActive,
}
impl<O: SetObject> SetRequest<O> {
pub fn new(params: RequestParams) -> Self {
Self {
account_id: if O::requires_account_id() {
params.account_id.into()
} else {
None
},
if_in_state: None,
create: None,
update: None,
destroy: None,
destroy_ref: None,
arguments: Default::default(),
}
}
pub fn account_id(&mut self, account_id: impl Into<String>) -> &mut Self {
if O::requires_account_id() {
self.account_id = Some(account_id.into());
}
self
}
pub fn if_in_state(&mut self, if_in_state: impl Into<String>) -> &mut Self {
self.if_in_state = Some(if_in_state.into());
self
}
pub fn create(&mut self) -> &mut O {
let create_id = self.create.as_ref().map_or(0, |c| c.len());
let create_id_str = format!("c{}", create_id);
self.create
.get_or_insert_with(AHashMap::new)
.insert(create_id_str.clone(), O::new(create_id.into()));
self.create
.as_mut()
.unwrap()
.get_mut(&create_id_str)
.unwrap()
}
pub fn create_with_id(&mut self, create_id: impl Into<String>) -> &mut O {
let create_id = create_id.into();
self.create
.get_or_insert_with(AHashMap::new)
.insert(create_id.clone(), O::new(0.into()));
self.create.as_mut().unwrap().get_mut(&create_id).unwrap()
}
pub fn create_item(&mut self, item: O) -> String {
let create_id = self.create.as_ref().map_or(0, |c| c.len());
let create_id_str = format!("c{}", create_id);
self.create
.get_or_insert_with(AHashMap::new)
.insert(create_id_str.clone(), item);
create_id_str
}
pub fn update(&mut self, id: impl Into<String>) -> &mut O {
let id: String = id.into();
self.update
.get_or_insert_with(AHashMap::new)
.insert(id.clone(), O::new(None));
self.update.as_mut().unwrap().get_mut(&id).unwrap()
}
pub fn update_item(&mut self, id: impl Into<String>, item: O) {
self.update
.get_or_insert_with(AHashMap::new)
.insert(id.into(), item);
}
pub fn destroy<U, V>(&mut self, ids: U) -> &mut Self
where
U: IntoIterator<Item = V>,
V: Into<String>,
{
self.destroy
.get_or_insert_with(Vec::new)
.extend(ids.into_iter().map(|id| id.into()));
self.destroy_ref = None;
self
}
pub fn destroy_ref(&mut self, reference: ResultReference) -> &mut Self {
self.destroy_ref = reference.into();
self.destroy = None;
self
}
pub fn arguments(&mut self) -> &mut O::SetArguments {
&mut self.arguments
}
}
impl<O: SetObject> SetResponse<O> {
pub fn account_id(&self) -> Option<&str> {
self.account_id.as_deref()
}
pub fn old_state(&self) -> Option<&str> {
self.old_state.as_deref()
}
pub fn new_state(&self) -> &str {
self.new_state.as_deref().unwrap_or("")
}
pub fn take_new_state(&mut self) -> String {
self.new_state.take().unwrap_or_default()
}
pub fn created(&mut self, id: &str) -> crate::Result<O> {
if let Some(result) = self.created.as_mut().and_then(|r| r.remove(id)) {
Ok(result)
} else if let Some(error) = self.not_created.as_mut().and_then(|r| r.remove(id)) {
Err(error.to_string_error().into())
} else {
Err(Error::Internal(format!("Id {} not found.", id)))
}
}
pub fn updated(&mut self, id: &str) -> crate::Result<Option<O>> {
if let Some(result) = self.updated.as_mut().and_then(|r| r.remove(id)) {
Ok(result)
} else if let Some(error) = self.not_updated.as_mut().and_then(|r| r.remove(id)) {
Err(error.to_string_error().into())
} else {
Err(Error::Internal(format!("Id {} not found.", id)))
}
}
pub fn destroyed(&mut self, id: &str) -> crate::Result<()> {
if self
.destroyed
.as_ref()
.map_or(false, |r| r.iter().any(|i| i == id))
{
Ok(())
} else if let Some(error) = self.not_destroyed.as_mut().and_then(|r| r.remove(id)) {
Err(error.to_string_error().into())
} else {
Err(Error::Internal(format!("Id {} not found.", id)))
}
}
pub fn created_ids(&self) -> Option<impl Iterator<Item = &String>> {
self.created.as_ref().map(|map| map.keys())
}
pub fn updated_ids(&self) -> Option<impl Iterator<Item = &String>> {
self.updated.as_ref().map(|map| map.keys())
}
pub fn take_updated_ids(&mut self) -> Option<Vec<String>> {
self.updated
.take()
.map(|map| map.into_iter().map(|(k, _)| k).collect())
}
pub fn destroyed_ids(&self) -> Option<impl Iterator<Item = &String>> {
self.destroyed.as_ref().map(|list| list.iter())
}
pub fn take_destroyed_ids(&mut self) -> Option<Vec<String>> {
self.destroyed.take()
}
pub fn not_created_ids(&self) -> Option<impl Iterator<Item = &String>> {
self.not_created.as_ref().map(|map| map.keys())
}
pub fn not_updated_ids(&self) -> Option<impl Iterator<Item = &String>> {
self.not_updated.as_ref().map(|map| map.keys())
}
pub fn not_destroyed_ids(&self) -> Option<impl Iterator<Item = &String>> {
self.not_destroyed.as_ref().map(|map| map.keys())
}
pub fn has_updated(&self) -> bool {
self.updated.as_ref().map_or(false, |m| !m.is_empty())
}
pub fn has_created(&self) -> bool {
self.created.as_ref().map_or(false, |m| !m.is_empty())
}
pub fn has_destroyed(&self) -> bool {
self.destroyed.as_ref().map_or(false, |m| !m.is_empty())
}
pub fn unwrap_update_errors(&self) -> crate::Result<()> {
if let Some(errors) = &self.not_updated {
if let Some(err) = errors.values().next() {
return Err(err.to_string_error().into());
}
}
Ok(())
}
pub fn unwrap_create_errors(&self) -> crate::Result<()> {
if let Some(errors) = &self.not_created {
if let Some(err) = errors.values().next() {
return Err(err.to_string_error().into());
}
}
Ok(())
}
}
impl<U: Display> SetError<U> {
pub fn error(&self) -> &SetErrorType {
&self.type_
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn properties(&self) -> Option<&[U]> {
self.properties.as_deref()
}
pub fn to_string_error(&self) -> SetError<String> {
SetError {
type_: self.type_.clone(),
description: self.description.as_ref().map(|s| s.to_string()),
properties: self
.properties
.as_ref()
.map(|s| s.iter().map(|s| s.to_string()).collect()),
}
}
}
impl<U: Display> Display for SetError<U> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.type_.fmt(f)?;
if let Some(description) = &self.description {
write!(f, ": {}", description)?;
}
if let Some(properties) = &self.properties {
write!(
f,
" (properties: {})",
properties
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(", ")
)?;
}
Ok(())
}
}
impl Display for SetErrorType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SetErrorType::Forbidden => write!(f, "forbidden"),
SetErrorType::OverQuota => write!(f, "overQuota"),
SetErrorType::TooLarge => write!(f, "tooLarge"),
SetErrorType::RateLimit => write!(f, "rateLimit"),
SetErrorType::NotFound => write!(f, "notFound"),
SetErrorType::InvalidPatch => write!(f, "invalidPatch"),
SetErrorType::WillDestroy => write!(f, "willDestroy"),
SetErrorType::InvalidProperties => write!(f, "invalidProperties"),
SetErrorType::Singleton => write!(f, "singleton"),
SetErrorType::MailboxHasChild => write!(f, "mailboxHasChild"),
SetErrorType::MailboxHasEmail => write!(f, "mailboxHasEmail"),
SetErrorType::BlobNotFound => write!(f, "blobNotFound"),
SetErrorType::TooManyKeywords => write!(f, "tooManyKeywords"),
SetErrorType::TooManyMailboxes => write!(f, "tooManyMailboxes"),
SetErrorType::ForbiddenFrom => write!(f, "forbiddenFrom"),
SetErrorType::InvalidEmail => write!(f, "invalidEmail"),
SetErrorType::TooManyRecipients => write!(f, "tooManyRecipients"),
SetErrorType::NoRecipients => write!(f, "noRecipients"),
SetErrorType::InvalidRecipients => write!(f, "invalidRecipients"),
SetErrorType::ForbiddenMailFrom => write!(f, "forbiddenMailFrom"),
SetErrorType::ForbiddenToSend => write!(f, "forbiddenToSend"),
SetErrorType::CannotUnsend => write!(f, "cannotUnsend"),
SetErrorType::AlreadyExists => write!(f, "alreadyExists"),
SetErrorType::InvalidScript => write!(f, "invalidScript"),
SetErrorType::ScriptIsActive => write!(f, "scriptIsActive"),
}
}
}
pub fn from_timestamp(timestamp: i64) -> DateTime<Utc> {
DateTime::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap_or_default(),
Utc,
)
}
pub fn string_not_set(string: &Option<String>) -> bool {
matches!(string, Some(string) if string.is_empty())
}
pub fn date_not_set(date: &Option<DateTime<Utc>>) -> bool {
matches!(date, Some(date) if date.timestamp() == 0)
}
pub fn list_not_set<O>(list: &Option<Vec<O>>) -> bool {
matches!(list, Some(list) if list.is_empty() )
}
pub fn map_not_set<K, V>(list: &Option<AHashMap<K, V>>) -> bool {
matches!(list, Some(list) if list.is_empty() )
}