JMAP for Sieve Scripts support.

main
Mauro D 2022-10-27 17:13:03 +00:00
parent 9f5e950f20
commit 6cd9019699
17 changed files with 1012 additions and 25 deletions

View File

@ -1,3 +1,7 @@
jmap-client 0.2.0
================================
- JMAP for Sieve Scripts support.
jmap-client 0.1.0
================================
- First release.

View File

@ -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. <hello@stalw.art>"]
license = "Apache-2.0 OR MIT"

View File

@ -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:

View File

@ -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<EmailSubmission<Set>>),
VacationResponseGet(GetRequest<VacationResponse<Set>>),
VacationResponseSet(SetRequest<VacationResponse<Set>>),
SieveScriptGet(GetRequest<SieveScript<Set>>),
SieveScriptQuery(QueryRequest<SieveScript<Set>>),
SieveScriptValidate(SieveScriptValidateRequest),
SieveScriptSet(SetRequest<SieveScript<Set>>),
PrincipalGet(GetRequest<Principal<Set>>),
PrincipalQuery(QueryRequest<Principal<Set>>),
PrincipalQueryChanges(QueryChangesRequest<Principal<Set>>),
@ -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<String>) -> 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<SieveScript<Set>> {
match self {
Arguments::SieveScriptGet(ref mut r) => r,
_ => unreachable!(),
}
}
pub fn sieve_script_query_mut(&mut self) -> &mut QueryRequest<SieveScript<Set>> {
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<SieveScript<Set>> {
match self {
Arguments::SieveScriptSet(ref mut r) => r,
_ => unreachable!(),
}
}
pub fn principal_get_mut(&mut self) -> &mut GetRequest<Principal<Set>> {
match self {
Arguments::PrincipalGet(ref mut r) => r,

View File

@ -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<EmailSubmission<Get>>;
pub type EmailSubmissionChangesResponse = ChangesResponse<EmailSubmission<Get>>;
pub type VacationResponseGetResponse = GetResponse<VacationResponse<Get>>;
pub type VacationResponseSetResponse = SetResponse<VacationResponse<Get>>;
pub type SieveScriptGetResponse = GetResponse<SieveScript<Get>>;
pub type SieveScriptSetResponse = SetResponse<SieveScript<Get>>;
pub type PrincipalChangesResponse = ChangesResponse<Principal<Get>>;
pub type PrincipalSetResponse = SetResponse<Principal<Get>>;
pub type PrincipalGetResponse = GetResponse<Principal<Get>>;
@ -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<SieveScriptGetResponse> {
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<SieveScriptValidateResponse> {
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<SieveScriptSetResponse> {
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<QueryResponse> {
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<PrincipalGetResponse> {
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"))?,

View File

@ -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<usize>,
#[serde(rename(serialize = "maxSizeScript"))]
max_script_size: Option<usize>,
#[serde(rename(serialize = "maxNumberScripts"))]
max_scripts: Option<usize>,
#[serde(rename(serialize = "maxNumberRedirects"))]
max_redirects: Option<usize>,
#[serde(rename(serialize = "sieveExtensions"))]
extensions: Vec<String>,
#[serde(rename(serialize = "notificationMethods"))]
notification_methods: Option<Vec<String>>,
#[serde(rename(serialize = "externalLists"))]
ext_lists: Option<Vec<String>>,
}
#[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<Item = &String> {
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<usize> {
self.max_script_size
}
pub fn max_number_scripts(&self) -> Option<usize> {
self.max_scripts
}
pub fn max_number_redirects(&self) -> Option<usize> {
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<Self>;
}

View File

@ -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<O: SetObject> SetRequest<O> {
@ -325,6 +331,24 @@ impl<O: SetObject> SetResponse<O> {
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> {
@ -376,28 +400,31 @@ impl<U: Display> Display for SetError<U> {
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"),
}
}
}

View File

@ -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<str> 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")]

44
src/sieve/get.rs Normal file
View File

@ -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 <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::{core::get::GetObject, Get, Set};
use super::SieveScript;
impl SieveScript<Get> {
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<Set> {
type GetArguments = ();
}
impl GetObject for SieveScript<Get> {
type GetArguments = ();
}

224
src/sieve/helpers.rs Normal file
View File

@ -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 <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::{
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<String>,
script: impl Into<Vec<u8>>,
activate: bool,
) -> crate::Result<SieveScript> {
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::<SieveScriptSetResponse>()
.await?
.created(&id)
}
pub async fn sieve_script_replace(
&self,
id: &str,
script: impl Into<Vec<u8>>,
activate: bool,
) -> crate::Result<Option<SieveScript>> {
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::<SieveScriptSetResponse>()
.await?
.updated(id)
}
pub async fn sieve_script_rename(
&self,
id: &str,
name: impl Into<String>,
activate: bool,
) -> crate::Result<Option<SieveScript>> {
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::<SieveScriptSetResponse>()
.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::<SieveScriptSetResponse>()
.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::<SieveScriptSetResponse>()
.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::<SieveScriptSetResponse>()
.await?
.destroyed(id)
}
pub async fn sieve_script_get(
&self,
id: &str,
properties: Option<impl IntoIterator<Item = Property>>,
) -> crate::Result<Option<SieveScript>> {
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::<SieveScriptGetResponse>()
.await
.map(|mut r| r.take_list().pop())
}
pub async fn sieve_script_query(
&self,
filter: Option<impl Into<Filter<super::query::Filter>>>,
sort: Option<impl IntoIterator<Item = Comparator<super::query::Comparator>>>,
) -> crate::Result<QueryResponse> {
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::<QueryResponse>().await
}
pub async fn sieve_script_validate(&self, script: impl Into<Vec<u8>>) -> 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::<SieveScriptValidateResponse>()
.await?
.unwrap_error()
}
}
impl Request<'_> {
pub fn get_sieve_script(&mut self) -> &mut GetRequest<SieveScript<Set>> {
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<SieveScriptGetResponse> {
self.send_single().await
}
pub fn set_sieve_script(&mut self) -> &mut SetRequest<SieveScript<Set>> {
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<SieveScriptSetResponse> {
self.send_single().await
}
pub fn validate_sieve_script(
&mut self,
blob_id: impl Into<String>,
) -> &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<SieveScriptValidateResponse> {
self.send_single().await
}
pub fn query_sieve_script(&mut self) -> &mut QueryRequest<SieveScript<Set>> {
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<QueryResponse> {
self.send_single().await
}
}

View File

@ -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 <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::{
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<String>,
script: impl Into<Vec<u8>>,
activate: bool,
) -> crate::Result<SieveScript> {
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::<SieveScriptSetResponse>()?
.created(&id)
}
pub fn sieve_script_replace(
&self,
id: &str,
script: impl Into<Vec<u8>>,
activate: bool,
) -> crate::Result<Option<SieveScript>> {
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::<SieveScriptSetResponse>()?.updated(id)
}
pub fn sieve_script_rename(
&self,
id: &str,
name: impl Into<String>,
activate: bool,
) -> crate::Result<Option<SieveScript>> {
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::<SieveScriptSetResponse>()?.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::<SieveScriptSetResponse>()?
.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::<SieveScriptSetResponse>()?
.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::<SieveScriptSetResponse>()?
.destroyed(id)
}
pub fn sieve_script_get(
&self,
id: &str,
properties: Option<impl IntoIterator<Item = Property>>,
) -> crate::Result<Option<SieveScript>> {
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::<SieveScriptGetResponse>()
.map(|mut r| r.take_list().pop())
}
pub fn sieve_script_query(
&self,
filter: Option<impl Into<Filter<super::query::Filter>>>,
sort: Option<impl IntoIterator<Item = Comparator<super::query::Comparator>>>,
) -> crate::Result<QueryResponse> {
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::<QueryResponse>()
}
pub fn sieve_script_validate(&self, script: impl Into<Vec<u8>>) -> 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::<SieveScriptValidateResponse>()?
.unwrap_error()
}
}
impl Request<'_> {
pub fn get_sieve_script(&mut self) -> &mut GetRequest<SieveScript<Set>> {
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<SieveScriptGetResponse> {
self.send_single()
}
pub fn set_sieve_script(&mut self) -> &mut SetRequest<SieveScript<Set>> {
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<SieveScriptSetResponse> {
self.send_single()
}
pub fn validate_sieve_script(
&mut self,
blob_id: impl Into<String>,
) -> &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<SieveScriptValidateResponse> {
self.send_single()
}
pub fn query_sieve_script(&mut self) -> &mut QueryRequest<SieveScript<Set>> {
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<QueryResponse> {
self.send_single()
}
}

106
src/sieve/mod.rs Normal file
View File

@ -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 <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.
*/
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<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,
#[serde(rename = "id")]
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "name")]
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "blobId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_id: Option<String>,
#[serde(rename = "isActive")]
#[serde(skip_serializing_if = "Option::is_none")]
pub is_active: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct SetArguments {
#[serde(rename = "onSuccessActivateScript")]
#[serde(skip_serializing_if = "Option::is_none")]
on_success_activate_script: Option<Option<String>>,
}
#[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<Set> {
type Property = Property;
fn requires_account_id() -> bool {
true
}
}
impl Object for SieveScript<Get> {
type Property = Property;
fn requires_account_id() -> bool {
true
}
}
impl ChangesObject for SieveScript<Set> {
type ChangesResponse = ();
}
impl ChangesObject for SieveScript<Get> {
type ChangesResponse = ();
}

71
src/sieve/query.rs Normal file
View File

@ -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 <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 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<String>) -> Self {
Filter::Name {
value: value.into(),
}
}
pub fn is_active(value: bool) -> Self {
Filter::IsActive { value }
}
}
impl Comparator {
pub fn name() -> query::Comparator<Comparator> {
query::Comparator::new(Comparator::Name)
}
pub fn is_active() -> query::Comparator<Comparator> {
query::Comparator::new(Comparator::IsActive)
}
}
impl QueryObject for SieveScript<Set> {
type QueryArguments = ();
type Filter = Filter;
type Sort = Comparator;
}

74
src/sieve/set.rs Normal file
View File

@ -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 <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::{core::set::SetObject, Get, Set};
use super::{SetArguments, SieveScript};
impl SieveScript<Set> {
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
self.name = Some(name.into());
self
}
pub fn blob_id(&mut self, blob_id: impl Into<String>) -> &mut Self {
self.blob_id = Some(blob_id.into());
self
}
}
impl SetObject for SieveScript<Set> {
type SetArguments = SetArguments;
fn new(_create_id: Option<usize>) -> Self {
SieveScript {
_create_id,
_state: Default::default(),
id: None,
name: None,
blob_id: None,
is_active: None,
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}
impl SetArguments {
pub fn on_success_activate_script(&mut self, id: impl Into<String>) -> &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<String>) -> &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<Get> {
type SetArguments = ();
fn new(_create_id: Option<usize>) -> Self {
unimplemented!()
}
fn create_id(&self) -> Option<String> {
None
}
}

39
src/sieve/validate.rs Normal file
View File

@ -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<SetError<String>>,
}
impl SieveScriptValidateRequest {
pub fn new(params: RequestParams, blob_id: impl Into<String>) -> 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(()),
}
}
}

View File

@ -101,7 +101,7 @@ impl Client {
pub async fn vacation_response_get(
&self,
properties: Option<Vec<Property>>,
properties: Option<impl IntoIterator<Item = Property>>,
) -> crate::Result<Option<VacationResponse>> {
let mut request = self.build();
let get_request = request.get_vacation_response().ids(["singleton"]);

View File

@ -97,7 +97,7 @@ impl Client {
pub fn vacation_response_get(
&self,
properties: Option<Vec<Property>>,
properties: Option<impl IntoIterator<Item = Property>>,
) -> crate::Result<Option<VacationResponse>> {
let mut request = self.build();
let get_request = request.get_vacation_response().ids(["singleton"]);