Skip to content

Commit

Permalink
JMAP for Sieve Scripts support.
Browse files Browse the repository at this point in the history
  • Loading branch information
mdecimus committed Oct 27, 2022
1 parent 9f5e950 commit 6cd9019
Show file tree
Hide file tree
Showing 17 changed files with 1,012 additions and 25 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
jmap-client 0.2.0
================================
- JMAP for Sieve Scripts support.

jmap-client 0.1.0
================================
- First release.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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. <[email protected]>"]
license = "Apache-2.0 OR MIT"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
49 changes: 49 additions & 0 deletions src/core/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>>),
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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,
Expand Down
65 changes: 65 additions & 0 deletions src/core/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
mailbox::Mailbox,
principal::Principal,
push_subscription::PushSubscription,
sieve::{validate::SieveScriptValidateResponse, SieveScript},
thread::Thread,
vacation_response::VacationResponse,
Get, Method,
Expand Down Expand Up @@ -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>>;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(_),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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"))?,
Expand Down
58 changes: 58 additions & 0 deletions src/core/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub enum Capabilities {
Mail(MailCapabilities),
Submission(SubmissionCapabilities),
WebSocket(WebSocketCapabilities),
Sieve(SieveCapabilities),
Empty(EmptyCapabilities),
Other(serde_json::Value),
}
Expand Down Expand Up @@ -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 {}

Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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>;
}
Expand Down
71 changes: 49 additions & 22 deletions src/core/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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"),
}
}
}
Expand Down
Loading

0 comments on commit 6cd9019

Please sign in to comment.