diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eeb984..e62964d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +jmap-client 0.2.0 +================================ +- JMAP for Sieve Scripts support. + jmap-client 0.1.0 ================================ - First release. diff --git a/Cargo.toml b/Cargo.toml index 19c2ec0..811cf2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "jmap-client" description = "JMAP client library for Rust" -version = "0.1.0" +version = "0.2.0" edition = "2018" authors = [ "Stalwart Labs Ltd. "] license = "Apache-2.0 OR MIT" diff --git a/README.md b/README.md index 815b2f9..b1b6b24 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ _jmap-client_ is a **JSON Meta Application Protocol (JMAP) library** written in - JMAP Core ([RFC 8620](https://datatracker.ietf.org/doc/html/rfc8620)) - JMAP for Mail ([RFC 8621](https://datatracker.ietf.org/doc/html/rfc8621)) - JMAP over WebSocket ([RFC 8887](https://datatracker.ietf.org/doc/html/rfc8887)). +- JMAP for Sieve Scripts ([DRAFT-SIEVE-12](https://www.ietf.org/archive/id/draft-ietf-jmap-sieve-12.html)). Features: diff --git a/src/core/request.rs b/src/core/request.rs index e8e011d..70fc796 100644 --- a/src/core/request.rs +++ b/src/core/request.rs @@ -21,6 +21,7 @@ use crate::{ mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, + sieve::{validate::SieveScriptValidateRequest, SieveScript}, thread::Thread, vacation_response::VacationResponse, Error, Method, Set, URI, @@ -92,6 +93,10 @@ pub enum Arguments { EmailSubmissionSet(SetRequest>), VacationResponseGet(GetRequest>), VacationResponseSet(SetRequest>), + SieveScriptGet(GetRequest>), + SieveScriptQuery(QueryRequest>), + SieveScriptValidate(SieveScriptValidateRequest), + SieveScriptSet(SetRequest>), PrincipalGet(GetRequest>), PrincipalQuery(QueryRequest>), PrincipalQueryChanges(QueryChangesRequest>), @@ -202,6 +207,22 @@ impl Arguments { Arguments::VacationResponseSet(SetRequest::new(params)) } + pub fn sieve_script_get(params: RequestParams) -> Self { + Arguments::SieveScriptGet(GetRequest::new(params)) + } + + pub fn sieve_script_query(params: RequestParams) -> Self { + Arguments::SieveScriptQuery(QueryRequest::new(params)) + } + + pub fn sieve_script_validate(params: RequestParams, blob_id: impl Into) -> Self { + Arguments::SieveScriptValidate(SieveScriptValidateRequest::new(params, blob_id)) + } + + pub fn sieve_script_set(params: RequestParams) -> Self { + Arguments::SieveScriptSet(SetRequest::new(params)) + } + pub fn principal_get(params: RequestParams) -> Self { Arguments::PrincipalGet(GetRequest::new(params)) } @@ -395,6 +416,34 @@ impl Arguments { } } + pub fn sieve_script_get_mut(&mut self) -> &mut GetRequest> { + match self { + Arguments::SieveScriptGet(ref mut r) => r, + _ => unreachable!(), + } + } + + pub fn sieve_script_query_mut(&mut self) -> &mut QueryRequest> { + match self { + Arguments::SieveScriptQuery(ref mut r) => r, + _ => unreachable!(), + } + } + + pub fn sieve_script_validate_mut(&mut self) -> &mut SieveScriptValidateRequest { + match self { + Arguments::SieveScriptValidate(ref mut r) => r, + _ => unreachable!(), + } + } + + pub fn sieve_script_set_mut(&mut self) -> &mut SetRequest> { + match self { + Arguments::SieveScriptSet(ref mut r) => r, + _ => unreachable!(), + } + } + pub fn principal_get_mut(&mut self) -> &mut GetRequest> { match self { Arguments::PrincipalGet(ref mut r) => r, diff --git a/src/core/response.rs b/src/core/response.rs index fc9f3e8..05183c5 100644 --- a/src/core/response.rs +++ b/src/core/response.rs @@ -24,6 +24,7 @@ use crate::{ mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, + sieve::{validate::SieveScriptValidateResponse, SieveScript}, thread::Thread, vacation_response::VacationResponse, Get, Method, @@ -132,6 +133,8 @@ pub type EmailSubmissionGetResponse = GetResponse>; pub type EmailSubmissionChangesResponse = ChangesResponse>; pub type VacationResponseGetResponse = GetResponse>; pub type VacationResponseSetResponse = SetResponse>; +pub type SieveScriptGetResponse = GetResponse>; +pub type SieveScriptSetResponse = SetResponse>; pub type PrincipalChangesResponse = ChangesResponse>; pub type PrincipalSetResponse = SetResponse>; pub type PrincipalGetResponse = GetResponse>; @@ -173,6 +176,10 @@ pub enum MethodResponse { SetEmailSubmission(EmailSubmissionSetResponse), GetVacationResponse(VacationResponseGetResponse), SetVacationResponse(VacationResponseSetResponse), + GetSieveScript(SieveScriptGetResponse), + QuerySieveScript(QueryResponse), + SetSieveScript(SieveScriptSetResponse), + ValidateSieveScript(SieveScriptValidateResponse), GetPrincipal(PrincipalGetResponse), ChangesPrincipal(PrincipalChangesResponse), @@ -257,6 +264,16 @@ impl TaggedMethodResponse { MethodResponse::SetVacationResponse(_), Method::SetVacationResponse ) + | (MethodResponse::GetSieveScript(_), Method::GetSieveScript) + | ( + MethodResponse::ValidateSieveScript(_), + Method::ValidateSieveScript + ) + | ( + MethodResponse::QuerySieveScript(_), + Method::QuerySieveScript + ) + | (MethodResponse::SetSieveScript(_), Method::SetSieveScript) | (MethodResponse::GetPrincipal(_), Method::GetPrincipal) | ( MethodResponse::ChangesPrincipal(_), @@ -509,6 +526,38 @@ impl TaggedMethodResponse { } } + pub fn unwrap_get_sieve_script(self) -> crate::Result { + match self.response { + MethodResponse::GetSieveScript(response) => Ok(response), + MethodResponse::Error(err) => Err(err.into()), + _ => Err("Response type mismatch".into()), + } + } + + pub fn unwrap_validate_sieve_script(self) -> crate::Result { + match self.response { + MethodResponse::ValidateSieveScript(response) => Ok(response), + MethodResponse::Error(err) => Err(err.into()), + _ => Err("Response type mismatch".into()), + } + } + + pub fn unwrap_set_sieve_script(self) -> crate::Result { + match self.response { + MethodResponse::SetSieveScript(response) => Ok(response), + MethodResponse::Error(err) => Err(err.into()), + _ => Err("Response type mismatch".into()), + } + } + + pub fn unwrap_query_sieve_script(self) -> crate::Result { + match self.response { + MethodResponse::QuerySieveScript(response) => Ok(response), + MethodResponse::Error(err) => Err(err.into()), + _ => Err("Response type mismatch".into()), + } + } + pub fn unwrap_get_principal(self) -> crate::Result { match self.response { MethodResponse::GetPrincipal(response) => Ok(response), @@ -708,6 +757,22 @@ impl<'de> Visitor<'de> for TaggedMethodResponseVisitor { seq.next_element()? .ok_or_else(|| serde::de::Error::custom("Expected a method response"))?, ), + Method::GetSieveScript => MethodResponse::GetSieveScript( + seq.next_element()? + .ok_or_else(|| serde::de::Error::custom("Expected a method response"))?, + ), + Method::SetSieveScript => MethodResponse::SetSieveScript( + seq.next_element()? + .ok_or_else(|| serde::de::Error::custom("Expected a method response"))?, + ), + Method::QuerySieveScript => MethodResponse::QuerySieveScript( + seq.next_element()? + .ok_or_else(|| serde::de::Error::custom("Expected a method response"))?, + ), + Method::ValidateSieveScript => MethodResponse::ValidateSieveScript( + seq.next_element()? + .ok_or_else(|| serde::de::Error::custom("Expected a method response"))?, + ), Method::GetPrincipal => MethodResponse::GetPrincipal( seq.next_element()? .ok_or_else(|| serde::de::Error::custom("Expected a method response"))?, diff --git a/src/core/session.rs b/src/core/session.rs index 0444528..51de182 100644 --- a/src/core/session.rs +++ b/src/core/session.rs @@ -68,6 +68,7 @@ pub enum Capabilities { Mail(MailCapabilities), Submission(SubmissionCapabilities), WebSocket(WebSocketCapabilities), + Sieve(SieveCapabilities), Empty(EmptyCapabilities), Other(serde_json::Value), } @@ -107,6 +108,24 @@ pub struct WebSocketCapabilities { supports_push: bool, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SieveCapabilities { + #[serde(rename(serialize = "maxSizeScriptName"))] + max_script_name: Option, + #[serde(rename(serialize = "maxSizeScript"))] + max_script_size: Option, + #[serde(rename(serialize = "maxNumberScripts"))] + max_scripts: Option, + #[serde(rename(serialize = "maxNumberRedirects"))] + max_redirects: Option, + #[serde(rename(serialize = "sieveExtensions"))] + extensions: Vec, + #[serde(rename(serialize = "notificationMethods"))] + notification_methods: Option>, + #[serde(rename(serialize = "externalLists"))] + ext_lists: Option>, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmptyCapabilities {} @@ -159,6 +178,15 @@ impl Session { }) } + pub fn sieve_capabilities(&self) -> Option<&SieveCapabilities> { + self.capabilities + .get(URI::Sieve.as_ref()) + .and_then(|v| match v { + Capabilities::Sieve(capabilities) => Some(capabilities), + _ => None, + }) + } + pub fn accounts(&self) -> impl Iterator { self.accounts.keys() } @@ -262,6 +290,36 @@ impl WebSocketCapabilities { } } +impl SieveCapabilities { + pub fn max_script_name_size(&self) -> usize { + self.max_script_name.unwrap_or(512) + } + + pub fn max_script_size(&self) -> Option { + self.max_script_size + } + + pub fn max_number_scripts(&self) -> Option { + self.max_scripts + } + + pub fn max_number_redirects(&self) -> Option { + self.max_redirects + } + + pub fn sieve_extensions(&self) -> &[String] { + &self.extensions + } + + pub fn notification_methods(&self) -> Option<&[String]> { + self.notification_methods.as_deref() + } + + pub fn external_lists(&self) -> Option<&[String]> { + self.ext_lists.as_deref() + } +} + pub trait URLParser: Sized { fn parse(value: &str) -> Option; } diff --git a/src/core/set.rs b/src/core/set.rs index 99a7c09..4b032b3 100644 --- a/src/core/set.rs +++ b/src/core/set.rs @@ -139,6 +139,12 @@ pub enum SetErrorType { ForbiddenToSend, #[serde(rename = "cannotUnsend")] CannotUnsend, + #[serde(rename = "alreadyExists")] + AlreadyExists, + #[serde(rename = "invalidScript")] + InvalidScript, + #[serde(rename = "scriptIsActive")] + ScriptIsActive, } impl SetRequest { @@ -325,6 +331,24 @@ impl SetResponse { 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 { @@ -376,28 +400,31 @@ impl Display for SetError { 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::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"), } } } diff --git a/src/lib.rs b/src/lib.rs index 03fdb0e..976f59f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ //! - JMAP Core ([RFC 8620](https://datatracker.ietf.org/doc/html/rfc8620)) //! - JMAP for Mail ([RFC 8621](https://datatracker.ietf.org/doc/html/rfc8621)) //! - JMAP over WebSocket ([RFC 8887](https://datatracker.ietf.org/doc/html/rfc8887)). +//! - JMAP for Sieve Scripts ([DRAFT-SIEVE-12](https://www.ietf.org/archive/id/draft-ietf-jmap-sieve-12.html)). //! //! Features: //! @@ -191,6 +192,7 @@ pub mod identity; pub mod mailbox; pub mod principal; pub mod push_subscription; +pub mod sieve; pub mod thread; pub mod vacation_response; @@ -220,6 +222,8 @@ pub enum URI { Calendars, #[serde(rename = "urn:ietf:params:jmap:websocket")] WebSocket, + #[serde(rename = "urn:ietf:params:jmap:sieve")] + Sieve, #[serde(rename = "urn:ietf:params:jmap:principals")] Principals, #[serde(rename = "urn:ietf:params:jmap:principals:owner")] @@ -236,6 +240,7 @@ impl AsRef for URI { URI::Contacts => "urn:ietf:params:jmap:contacts", URI::Calendars => "urn:ietf:params:jmap:calendars", URI::WebSocket => "urn:ietf:params:jmap:websocket", + URI::Sieve => "urn:ietf:params:jmap:sieve", URI::Principals => "urn:ietf:params:jmap:principals", URI::PrincipalsOwner => "urn:ietf:params:jmap:principals:owner", } @@ -304,6 +309,14 @@ pub enum Method { GetVacationResponse, #[serde(rename = "VacationResponse/set")] SetVacationResponse, + #[serde(rename = "SieveScript/get")] + GetSieveScript, + #[serde(rename = "SieveScript/set")] + SetSieveScript, + #[serde(rename = "SieveScript/query")] + QuerySieveScript, + #[serde(rename = "SieveScript/validate")] + ValidateSieveScript, #[serde(rename = "Principal/get")] GetPrincipal, #[serde(rename = "Principal/changes")] diff --git a/src/sieve/get.rs b/src/sieve/get.rs new file mode 100644 index 0000000..52f4b81 --- /dev/null +++ b/src/sieve/get.rs @@ -0,0 +1,44 @@ +/* + * 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::{core::get::GetObject, Get, Set}; + +use super::SieveScript; + +impl SieveScript { + pub fn id(&self) -> Option<&str> { + self.id.as_deref() + } + + pub fn take_id(&mut self) -> String { + self.id.take().unwrap_or_default() + } + + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn blob_id(&self) -> Option<&str> { + self.blob_id.as_deref() + } + + pub fn is_active(&self) -> bool { + self.is_active.unwrap_or(false) + } +} + +impl GetObject for SieveScript { + type GetArguments = (); +} + +impl GetObject for SieveScript { + type GetArguments = (); +} diff --git a/src/sieve/helpers.rs b/src/sieve/helpers.rs new file mode 100644 index 0000000..fc21392 --- /dev/null +++ b/src/sieve/helpers.rs @@ -0,0 +1,224 @@ +/* + * 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::{ + client::Client, + core::{ + get::GetRequest, + query::{Comparator, Filter, QueryRequest, QueryResponse}, + request::{Arguments, Request}, + response::{SieveScriptGetResponse, SieveScriptSetResponse}, + set::{SetObject, SetRequest}, + }, + Method, Set, URI, +}; + +use super::{ + validate::{SieveScriptValidateRequest, SieveScriptValidateResponse}, + Property, SieveScript, +}; + +impl Client { + pub async fn sieve_script_create( + &self, + name: impl Into, + script: impl Into>, + activate: bool, + ) -> crate::Result { + let blob_id = self.upload(None, script.into(), None).await?.take_blob_id(); + let mut request = self.build(); + let set_request = request.set_sieve_script(); + let id = set_request + .create() + .name(name) + .blob_id(blob_id) + .create_id() + .unwrap(); + if activate { + set_request + .arguments() + .on_success_activate_script(id.clone()); + } + request + .send_single::() + .await? + .created(&id) + } + + pub async fn sieve_script_replace( + &self, + id: &str, + script: impl Into>, + activate: bool, + ) -> crate::Result> { + let blob_id = self.upload(None, script.into(), None).await?.take_blob_id(); + let mut request = self.build(); + let set_request = request.set_sieve_script(); + set_request.update(id).blob_id(blob_id); + if activate { + set_request.arguments().on_success_activate_script_id(id); + } + request + .send_single::() + .await? + .updated(id) + } + + pub async fn sieve_script_rename( + &self, + id: &str, + name: impl Into, + activate: bool, + ) -> crate::Result> { + let mut request = self.build(); + let set_request = request.set_sieve_script(); + set_request.update(id).name(name); + if activate { + set_request.arguments().on_success_activate_script_id(id); + } + request + .send_single::() + .await? + .updated(id) + } + + pub async fn sieve_script_activate(&self, id: &str) -> crate::Result<()> { + let mut request = self.build(); + request + .set_sieve_script() + .arguments() + .on_success_activate_script_id(id); + request + .send_single::() + .await? + .unwrap_update_errors() + } + + pub async fn sieve_script_deactivate(&self) -> crate::Result<()> { + let mut request = self.build(); + request + .set_sieve_script() + .arguments() + .on_success_deactivate_scripts(); + request + .send_single::() + .await? + .unwrap_update_errors() + } + + pub async fn sieve_script_destroy(&self, id: &str) -> crate::Result<()> { + let mut request = self.build(); + request.set_sieve_script().destroy([id]); + request + .send_single::() + .await? + .destroyed(id) + } + + pub async fn sieve_script_get( + &self, + id: &str, + properties: Option>, + ) -> crate::Result> { + let mut request = self.build(); + let get_request = request.get_sieve_script().ids([id]); + if let Some(properties) = properties { + get_request.properties(properties.into_iter()); + } + request + .send_single::() + .await + .map(|mut r| r.take_list().pop()) + } + + pub async fn sieve_script_query( + &self, + filter: Option>>, + sort: Option>>, + ) -> crate::Result { + let mut request = self.build(); + let query_request = request.query_sieve_script(); + if let Some(filter) = filter { + query_request.filter(filter); + } + if let Some(sort) = sort { + query_request.sort(sort.into_iter()); + } + request.send_single::().await + } + + pub async fn sieve_script_validate(&self, script: impl Into>) -> crate::Result<()> { + let blob_id = self.upload(None, script.into(), None).await?.take_blob_id(); + let mut request = self.build(); + request.validate_sieve_script(blob_id); + request + .send_single::() + .await? + .unwrap_error() + } +} + +impl Request<'_> { + pub fn get_sieve_script(&mut self) -> &mut GetRequest> { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::GetSieveScript, + Arguments::sieve_script_get(self.params(Method::GetSieveScript)), + ) + .sieve_script_get_mut() + } + + pub async fn send_get_sieve_script(self) -> crate::Result { + self.send_single().await + } + + pub fn set_sieve_script(&mut self) -> &mut SetRequest> { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::SetSieveScript, + Arguments::sieve_script_set(self.params(Method::SetSieveScript)), + ) + .sieve_script_set_mut() + } + + pub async fn send_set_sieve_script(self) -> crate::Result { + self.send_single().await + } + + pub fn validate_sieve_script( + &mut self, + blob_id: impl Into, + ) -> &mut SieveScriptValidateRequest { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::ValidateSieveScript, + Arguments::sieve_script_validate(self.params(Method::ValidateSieveScript), blob_id), + ) + .sieve_script_validate_mut() + } + + pub async fn send_validate_sieve_script(self) -> crate::Result { + self.send_single().await + } + + pub fn query_sieve_script(&mut self) -> &mut QueryRequest> { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::QuerySieveScript, + Arguments::sieve_script_query(self.params(Method::QuerySieveScript)), + ) + .sieve_script_query_mut() + } + + pub async fn send_query_sieve_script(self) -> crate::Result { + self.send_single().await + } +} diff --git a/src/sieve/helpers_blocking.rs b/src/sieve/helpers_blocking.rs new file mode 100644 index 0000000..52bac36 --- /dev/null +++ b/src/sieve/helpers_blocking.rs @@ -0,0 +1,212 @@ +/* + * 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::{ + client::Client, + core::{ + get::GetRequest, + query::{Comparator, Filter, QueryRequest, QueryResponse}, + request::{Arguments, Request}, + response::{SieveScriptGetResponse, SieveScriptSetResponse}, + set::{SetObject, SetRequest}, + }, + Method, Set, URI, +}; + +use super::{ + validate::{SieveScriptValidateRequest, SieveScriptValidateResponse}, + Property, SieveScript, +}; + +impl Client { + pub fn sieve_script_create( + &self, + name: impl Into, + script: impl Into>, + activate: bool, + ) -> crate::Result { + let blob_id = self.upload(None, script.into(), None)?.take_blob_id(); + let mut request = self.build(); + let set_request = request.set_sieve_script(); + let id = set_request + .create() + .name(name) + .blob_id(blob_id) + .create_id() + .unwrap(); + if activate { + set_request + .arguments() + .on_success_activate_script(id.clone()); + } + request + .send_single::()? + .created(&id) + } + + pub fn sieve_script_replace( + &self, + id: &str, + script: impl Into>, + activate: bool, + ) -> crate::Result> { + let blob_id = self.upload(None, script.into(), None)?.take_blob_id(); + let mut request = self.build(); + let set_request = request.set_sieve_script(); + set_request.update(id).blob_id(blob_id); + if activate { + set_request.arguments().on_success_activate_script_id(id); + } + request.send_single::()?.updated(id) + } + + pub fn sieve_script_rename( + &self, + id: &str, + name: impl Into, + activate: bool, + ) -> crate::Result> { + let mut request = self.build(); + let set_request = request.set_sieve_script(); + set_request.update(id).name(name); + if activate { + set_request.arguments().on_success_activate_script_id(id); + } + request.send_single::()?.updated(id) + } + + pub fn sieve_script_activate(&self, id: &str) -> crate::Result<()> { + let mut request = self.build(); + request + .set_sieve_script() + .arguments() + .on_success_activate_script_id(id); + request + .send_single::()? + .unwrap_update_errors() + } + + pub fn sieve_script_deactivate(&self) -> crate::Result<()> { + let mut request = self.build(); + request + .set_sieve_script() + .arguments() + .on_success_deactivate_scripts(); + request + .send_single::()? + .unwrap_update_errors() + } + + pub fn sieve_script_destroy(&self, id: &str) -> crate::Result<()> { + let mut request = self.build(); + request.set_sieve_script().destroy([id]); + request + .send_single::()? + .destroyed(id) + } + + pub fn sieve_script_get( + &self, + id: &str, + properties: Option>, + ) -> crate::Result> { + let mut request = self.build(); + let get_request = request.get_sieve_script().ids([id]); + if let Some(properties) = properties { + get_request.properties(properties.into_iter()); + } + request + .send_single::() + .map(|mut r| r.take_list().pop()) + } + + pub fn sieve_script_query( + &self, + filter: Option>>, + sort: Option>>, + ) -> crate::Result { + let mut request = self.build(); + let query_request = request.query_sieve_script(); + if let Some(filter) = filter { + query_request.filter(filter); + } + if let Some(sort) = sort { + query_request.sort(sort.into_iter()); + } + request.send_single::() + } + + pub fn sieve_script_validate(&self, script: impl Into>) -> crate::Result<()> { + let blob_id = self.upload(None, script.into(), None)?.take_blob_id(); + let mut request = self.build(); + request.validate_sieve_script(blob_id); + request + .send_single::()? + .unwrap_error() + } +} + +impl Request<'_> { + pub fn get_sieve_script(&mut self) -> &mut GetRequest> { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::GetSieveScript, + Arguments::sieve_script_get(self.params(Method::GetSieveScript)), + ) + .sieve_script_get_mut() + } + + pub fn send_get_sieve_script(self) -> crate::Result { + self.send_single() + } + + pub fn set_sieve_script(&mut self) -> &mut SetRequest> { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::SetSieveScript, + Arguments::sieve_script_set(self.params(Method::SetSieveScript)), + ) + .sieve_script_set_mut() + } + + pub fn send_set_sieve_script(self) -> crate::Result { + self.send_single() + } + + pub fn validate_sieve_script( + &mut self, + blob_id: impl Into, + ) -> &mut SieveScriptValidateRequest { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::ValidateSieveScript, + Arguments::sieve_script_validate(self.params(Method::ValidateSieveScript), blob_id), + ) + .sieve_script_validate_mut() + } + + pub fn send_validate_sieve_script(self) -> crate::Result { + self.send_single() + } + + pub fn query_sieve_script(&mut self) -> &mut QueryRequest> { + self.add_capability(URI::Sieve); + self.add_method_call( + Method::QuerySieveScript, + Arguments::sieve_script_query(self.params(Method::QuerySieveScript)), + ) + .sieve_script_query_mut() + } + + pub fn send_query_sieve_script(self) -> crate::Result { + self.send_single() + } +} diff --git a/src/sieve/mod.rs b/src/sieve/mod.rs new file mode 100644 index 0000000..d83856b --- /dev/null +++ b/src/sieve/mod.rs @@ -0,0 +1,106 @@ +/* + * 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. + */ + +pub mod get; +#[cfg(feature = "async")] +pub mod helpers; +#[cfg(feature = "blocking")] +pub mod helpers_blocking; +pub mod query; +pub mod set; +pub mod validate; + +use std::fmt::Display; + +use crate::core::changes::ChangesObject; +use crate::core::Object; +use crate::Get; +use crate::Set; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SieveScript { + #[serde(skip)] + _create_id: Option, + + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "id")] + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(rename = "blobId")] + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_id: Option, + + #[serde(rename = "isActive")] + #[serde(skip_serializing_if = "Option::is_none")] + pub is_active: Option, +} + +#[derive(Debug, Clone, Serialize, Default)] +pub struct SetArguments { + #[serde(rename = "onSuccessActivateScript")] + #[serde(skip_serializing_if = "Option::is_none")] + on_success_activate_script: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Copy)] +pub enum Property { + #[serde(rename = "id")] + Id, + #[serde(rename = "name")] + Name, + #[serde(rename = "blobId")] + BlobId, + #[serde(rename = "isActive")] + IsActive, +} + +impl Display for Property { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Property::Id => write!(f, "id"), + Property::Name => write!(f, "name"), + Property::BlobId => write!(f, "blobId"), + Property::IsActive => write!(f, "isActive"), + } + } +} + +impl Object for SieveScript { + type Property = Property; + + fn requires_account_id() -> bool { + true + } +} + +impl Object for SieveScript { + type Property = Property; + + fn requires_account_id() -> bool { + true + } +} + +impl ChangesObject for SieveScript { + type ChangesResponse = (); +} + +impl ChangesObject for SieveScript { + type ChangesResponse = (); +} diff --git a/src/sieve/query.rs b/src/sieve/query.rs new file mode 100644 index 0000000..7fb779c --- /dev/null +++ b/src/sieve/query.rs @@ -0,0 +1,71 @@ +/* + * 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 serde::Serialize; + +use crate::{ + core::query::{self, QueryObject}, + Set, +}; + +use super::SieveScript; + +#[derive(Serialize, Clone, Debug)] +#[serde(untagged)] +pub enum Filter { + Name { + #[serde(rename = "name")] + value: String, + }, + IsActive { + #[serde(rename = "isActive")] + value: bool, + }, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(tag = "property")] +pub enum Comparator { + #[serde(rename = "name")] + Name, + #[serde(rename = "isActive")] + IsActive, +} + +impl Filter { + pub fn name(value: impl Into) -> Self { + Filter::Name { + value: value.into(), + } + } + + pub fn is_active(value: bool) -> Self { + Filter::IsActive { value } + } +} + +impl Comparator { + pub fn name() -> query::Comparator { + query::Comparator::new(Comparator::Name) + } + + pub fn is_active() -> query::Comparator { + query::Comparator::new(Comparator::IsActive) + } +} + +impl QueryObject for SieveScript { + type QueryArguments = (); + + type Filter = Filter; + + type Sort = Comparator; +} diff --git a/src/sieve/set.rs b/src/sieve/set.rs new file mode 100644 index 0000000..4dacb6e --- /dev/null +++ b/src/sieve/set.rs @@ -0,0 +1,74 @@ +/* + * 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::{core::set::SetObject, Get, Set}; + +use super::{SetArguments, SieveScript}; + +impl SieveScript { + pub fn name(&mut self, name: impl Into) -> &mut Self { + self.name = Some(name.into()); + self + } + + pub fn blob_id(&mut self, blob_id: impl Into) -> &mut Self { + self.blob_id = Some(blob_id.into()); + self + } +} + +impl SetObject for SieveScript { + type SetArguments = SetArguments; + + fn new(_create_id: Option) -> Self { + SieveScript { + _create_id, + _state: Default::default(), + id: None, + name: None, + blob_id: None, + is_active: None, + } + } + + fn create_id(&self) -> Option { + self._create_id.map(|id| format!("c{}", id)) + } +} + +impl SetArguments { + pub fn on_success_activate_script(&mut self, id: impl Into) -> &mut Self { + self.on_success_activate_script = Some(Some(format!("#{}", id.into()))); + self + } + + pub fn on_success_activate_script_id(&mut self, id: impl Into) -> &mut Self { + self.on_success_activate_script = Some(Some(id.into())); + self + } + + pub fn on_success_deactivate_scripts(&mut self) -> &mut Self { + self.on_success_activate_script = Some(None); + self + } +} + +impl SetObject for SieveScript { + type SetArguments = (); + + fn new(_create_id: Option) -> Self { + unimplemented!() + } + + fn create_id(&self) -> Option { + None + } +} diff --git a/src/sieve/validate.rs b/src/sieve/validate.rs new file mode 100644 index 0000000..f9096cf --- /dev/null +++ b/src/sieve/validate.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +use crate::core::{set::SetError, RequestParams}; + +#[derive(Debug, Clone, Serialize)] +pub struct SieveScriptValidateRequest { + #[serde(rename = "accountId")] + account_id: String, + + #[serde(rename = "blobId")] + blob_id: String, +} + +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub struct SieveScriptValidateResponse { + #[serde(rename = "accountId")] + account_id: String, + + error: Option>, +} + +impl SieveScriptValidateRequest { + pub fn new(params: RequestParams, blob_id: impl Into) -> Self { + SieveScriptValidateRequest { + account_id: params.account_id, + blob_id: blob_id.into(), + } + } +} + +impl SieveScriptValidateResponse { + pub fn unwrap_error(self) -> crate::Result<()> { + match self.error { + Some(err) => Err(err.into()), + None => Ok(()), + } + } +} diff --git a/src/vacation_response/helpers.rs b/src/vacation_response/helpers.rs index a2cc60c..a296a29 100644 --- a/src/vacation_response/helpers.rs +++ b/src/vacation_response/helpers.rs @@ -101,7 +101,7 @@ impl Client { pub async fn vacation_response_get( &self, - properties: Option>, + properties: Option>, ) -> crate::Result> { let mut request = self.build(); let get_request = request.get_vacation_response().ids(["singleton"]); diff --git a/src/vacation_response/helpers_blocking.rs b/src/vacation_response/helpers_blocking.rs index 44a8f23..83c0344 100644 --- a/src/vacation_response/helpers_blocking.rs +++ b/src/vacation_response/helpers_blocking.rs @@ -97,7 +97,7 @@ impl Client { pub fn vacation_response_get( &self, - properties: Option>, + properties: Option>, ) -> crate::Result> { let mut request = self.build(); let get_request = request.get_vacation_response().ids(["singleton"]);