/* * Copyright Stalwart Labs Ltd. See the COPYING * file at the top-level directory of this distribution. * * Licensed under the Apache License, Version 2.0 or the MIT license * , 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) -> Self; fn create_id(&self) -> Option; } #[derive(Debug, Clone, Serialize)] pub struct SetRequest { #[serde(rename = "accountId")] #[serde(skip_serializing_if = "Option::is_none")] account_id: Option, #[serde(rename = "ifInState")] #[serde(skip_serializing_if = "Option::is_none")] if_in_state: Option, #[serde(skip_serializing_if = "Option::is_none")] create: Option>, #[serde(skip_serializing_if = "Option::is_none")] update: Option>, #[serde(skip_serializing_if = "Option::is_none")] destroy: Option>, #[serde(rename = "#destroy")] #[serde(skip_deserializing)] #[serde(skip_serializing_if = "Option::is_none")] destroy_ref: Option, #[serde(flatten)] arguments: O::SetArguments, } #[derive(Debug, Clone, Deserialize)] pub struct SetResponse { #[serde(rename = "accountId")] account_id: Option, #[serde(rename = "oldState")] old_state: Option, #[serde(rename = "newState")] new_state: Option, #[serde(rename = "created")] created: Option>, #[serde(rename = "updated")] updated: Option>>, #[serde(rename = "destroyed")] destroyed: Option>, #[serde(rename = "notCreated")] not_created: Option>>, #[serde(rename = "notUpdated")] not_updated: Option>>, #[serde(rename = "notDestroyed")] not_destroyed: Option>>, } #[derive(Debug, Clone, Deserialize)] pub struct SetError where U: Display, { #[serde(rename = "type")] pub type_: SetErrorType, description: Option, properties: Option>, } #[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 SetRequest { 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) -> &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) -> &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_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) -> &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, item: O) { self.update .get_or_insert_with(AHashMap::new) .insert(id.into(), item); } pub fn destroy(&mut self, ids: U) -> &mut Self where U: IntoIterator, V: Into, { 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 SetResponse { 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 { 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> { 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> { self.created.as_ref().map(|map| map.keys()) } pub fn updated_ids(&self) -> Option> { self.updated.as_ref().map(|map| map.keys()) } pub fn take_updated_ids(&mut self) -> Option> { self.updated .take() .map(|map| map.into_iter().map(|(k, _)| k).collect()) } pub fn destroyed_ids(&self) -> Option> { self.destroyed.as_ref().map(|list| list.iter()) } pub fn take_destroyed_ids(&mut self) -> Option> { self.destroyed.take() } pub fn not_created_ids(&self) -> Option> { self.not_created.as_ref().map(|map| map.keys()) } pub fn not_updated_ids(&self) -> Option> { self.not_updated.as_ref().map(|map| map.keys()) } pub fn not_destroyed_ids(&self) -> Option> { 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 SetError { 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 { 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 Display for SetError { 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::>() .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 { DateTime::::from_utc( NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap_or_default(), Utc, ) } pub fn string_not_set(string: &Option) -> bool { matches!(string, Some(string) if string.is_empty()) } pub fn date_not_set(date: &Option>) -> bool { matches!(date, Some(date) if date.timestamp() == 0) } pub fn list_not_set(list: &Option>) -> bool { matches!(list, Some(list) if list.is_empty() ) } pub fn map_not_set(list: &Option>) -> bool { matches!(list, Some(list) if list.is_empty() ) }