From a23d97e3a44ab8ec965adb1e0198bc1ab42cddeb Mon Sep 17 00:00:00 2001 From: Mauro D Date: Thu, 12 May 2022 14:48:26 +0000 Subject: [PATCH] Initial tests. --- Cargo.toml | 1 - src/blob/upload.rs | 4 + src/client.rs | 61 ++++-- src/core/copy.rs | 6 +- src/core/error.rs | 31 +++ src/core/request.rs | 120 ++++++------ src/core/response.rs | 365 ++++++++++++++++++----------------- src/core/session.rs | 20 +- src/core/set.rs | 145 +++++++++++--- src/email/helpers.rs | 138 +++++++++++++ src/email/import.rs | 64 ++++-- src/email/mod.rs | 42 +++- src/email_submission/mod.rs | 19 +- src/identity/mod.rs | 17 ++ src/lib.rs | 29 ++- src/mailbox/helpers.rs | 130 +++++++++++++ src/mailbox/mod.rs | 24 ++- src/mailbox/set.rs | 2 +- src/push_subscription/mod.rs | 16 ++ src/push_subscription/set.rs | 5 - src/vacation_response/mod.rs | 16 ++ 21 files changed, 929 insertions(+), 326 deletions(-) create mode 100644 src/email/helpers.rs create mode 100644 src/mailbox/helpers.rs diff --git a/Cargo.toml b/Cargo.toml index dea0b89..dd7d52f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" chrono = { version = "0.4", features = ["serde"]} reqwest = "0.11" -ece = "2.2" base64 = "0.13" #[dev-dependencies] diff --git a/src/blob/upload.rs b/src/blob/upload.rs index 54d2f96..e8bd1fb 100644 --- a/src/blob/upload.rs +++ b/src/blob/upload.rs @@ -82,4 +82,8 @@ impl UploadResponse { pub fn size(&self) -> usize { self.size } + + pub fn unwrap_blob_id(self) -> String { + self.blob_id + } } diff --git a/src/client.rs b/src/client.rs index 7987f71..9b1ac17 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,11 +4,13 @@ use reqwest::{ header::{self}, Response, }; +use serde::de::DeserializeOwned; use crate::{ blob, core::{ - request::Request, + request::{self, Request}, + response, session::{Session, URLPart}, }, event_source, Error, @@ -58,7 +60,7 @@ impl Client { let default_account_id = session .primary_accounts() .next() - .map(|a| a.0.to_string()) + .map(|a| a.1.to_string()) .unwrap_or_default(); headers.insert( @@ -94,8 +96,30 @@ impl Client { &self.headers } - pub async fn update_session(&mut self, new_state: &str) -> crate::Result<()> { - if new_state != self.session.state() { + pub async fn send( + &mut self, + request: &request::Request<'_>, + ) -> crate::Result> + where + R: DeserializeOwned, + { + let response: response::Response = serde_json::from_slice( + &Client::handle_error( + reqwest::Client::builder() + .timeout(Duration::from_millis(self.timeout)) + .default_headers(self.headers.clone()) + .build()? + .post(self.session.api_url()) + .body(serde_json::to_string(&request)?) + .send() + .await?, + ) + .await? + .bytes() + .await?, + )?; + + if response.session_state() != self.session.state() { let session: Session = serde_json::from_slice( &Client::handle_error( reqwest::Client::builder() @@ -115,7 +139,8 @@ impl Client { self.event_source_url = URLPart::parse(session.event_source_url())?; self.session = session; } - Ok(()) + + Ok(response) } pub fn set_default_account_id(&mut self, defaul_account_id: impl Into) { @@ -126,7 +151,7 @@ impl Client { &self.default_account_id } - pub fn request(&mut self) -> Request<'_> { + pub fn build(&mut self) -> Request<'_> { Request::new(self) } @@ -154,18 +179,28 @@ impl Client { &response.bytes().await?, )?)) } else { - Err(Error::ServerError(format!("{}", response.status()))) + Err(Error::Server(format!("{}", response.status()))) } } } #[cfg(test)] mod tests { - use crate::core::response::Response; - #[test] - fn test_serialize() { - let r: Response = serde_json::from_slice( + fn _test_serialize() { + + /*let coco = request + .send() + .await + .unwrap() + .unwrap_method_responses() + .pop() + .unwrap() + .unwrap_get_email() + .unwrap();*/ + //coco.list().first().unwrap().subject().unwrap(); + + /*let r: Response = serde_json::from_slice( br#"{"sessionState": "123", "methodResponses": [[ "Email/query", { "accountId": "A1", "queryState": "abcdefg", @@ -202,9 +237,9 @@ mod tests { "notFound": [] }, "t2" ]]}"#, ) - .unwrap(); + .unwrap();*/ - println!("{:?}", r); + //println!("{:?}", r); /*let mut client = Client::connect("coco"); let mut request = client.request(); diff --git a/src/core/copy.rs b/src/core/copy.rs index 77f0d3f..e26c706 100644 --- a/src/core/copy.rs +++ b/src/core/copy.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; use serde::{Deserialize, Serialize}; @@ -32,7 +32,7 @@ pub struct CopyRequest { } #[derive(Debug, Clone, Deserialize)] -pub struct CopyResponse { +pub struct CopyResponse { #[serde(rename = "fromAccountId")] from_account_id: String, @@ -102,7 +102,7 @@ impl CopyRequest { } } -impl CopyResponse { +impl CopyResponse { pub fn from_account_id(&self) -> &str { &self.from_account_id } diff --git a/src/core/error.rs b/src/core/error.rs index 7f5b3f6..4f69f1f 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -102,6 +102,37 @@ impl MethodError { } } +impl Display for MethodError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.p_type { + MethodErrorType::ServerUnavailable => write!(f, "Server unavailable"), + MethodErrorType::ServerFail => write!(f, "Server fail"), + MethodErrorType::ServerPartialFail => write!(f, "Server partial fail"), + MethodErrorType::UnknownMethod => write!(f, "Unknown method"), + MethodErrorType::InvalidArguments => write!(f, "Invalid arguments"), + MethodErrorType::InvalidResultReference => write!(f, "Invalid result reference"), + MethodErrorType::Forbidden => write!(f, "Forbidden"), + MethodErrorType::AccountNotFound => write!(f, "Account not found"), + MethodErrorType::AccountNotSupportedByMethod => { + write!(f, "Account not supported by method") + } + MethodErrorType::AccountReadOnly => write!(f, "Account read only"), + MethodErrorType::RequestTooLarge => write!(f, "Request too large"), + MethodErrorType::CannotCalculateChanges => write!(f, "Cannot calculate changes"), + MethodErrorType::StateMismatch => write!(f, "State mismatch"), + MethodErrorType::AlreadyExists => write!(f, "Already exists"), + MethodErrorType::FromAccountNotFound => write!(f, "From account not found"), + MethodErrorType::FromAccountNotSupportedByMethod => { + write!(f, "From account not supported by method") + } + MethodErrorType::AnchorNotFound => write!(f, "Anchor not found"), + MethodErrorType::UnsupportedSort => write!(f, "Unsupported sort"), + MethodErrorType::UnsupportedFilter => write!(f, "Unsupported filter"), + MethodErrorType::TooManyChanges => write!(f, "Too many changes"), + } + } +} + impl Display for ProblemDetails { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.p_type { diff --git a/src/core/request.rs b/src/core/request.rs index add965f..f7666fd 100644 --- a/src/core/request.rs +++ b/src/core/request.rs @@ -1,6 +1,6 @@ -use std::{collections::HashMap, time::Duration}; +use std::collections::HashMap; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use crate::{ blob::copy::CopyBlobRequest, @@ -12,18 +12,25 @@ use crate::{ push_subscription::{self, PushSubscription}, thread, vacation_response::{self, VacationResponse}, - Method, Set, URI, + Error, Method, Set, URI, }; use super::{ - changes::ChangesRequest, copy::CopyRequest, get::GetRequest, query::QueryRequest, - query_changes::QueryChangesRequest, response::Response, set::SetRequest, + changes::ChangesRequest, + copy::CopyRequest, + get::GetRequest, + query::QueryRequest, + query_changes::QueryChangesRequest, + response::{MethodResponse, Response, SingleMethodResponse}, + set::SetRequest, }; #[derive(Serialize)] pub struct Request<'x> { #[serde(skip)] - client: &'x mut Client, + client: Option<&'x mut Client>, + #[serde(skip)] + default_account_id: String, using: Vec, @@ -399,28 +406,29 @@ impl<'x> Request<'x> { using: vec![URI::Core, URI::Mail], method_calls: vec![], created_ids: None, - client, + default_account_id: client.default_account_id().to_string(), + client: client.into(), } } - pub async fn send(&mut self) -> crate::Result { - let response: Response = serde_json::from_slice( - &Client::handle_error( - reqwest::Client::builder() - .timeout(Duration::from_millis(self.client.timeout())) - .default_headers(self.client.headers().clone()) - .build()? - .post(self.client.session().api_url()) - .body(serde_json::to_string(&self)?) - .send() - .await?, - ) - .await? - .bytes() - .await?, - )?; - self.client.update_session(response.session_state()).await?; - Ok(response) + pub async fn send(mut self) -> crate::Result> { + Option::take(&mut self.client).unwrap().send(&self).await + } + + pub async fn send_single(mut self) -> crate::Result + where + T: DeserializeOwned, + { + let response: Response> = + Option::take(&mut self.client).unwrap().send(&self).await?; + match response + .unwrap_method_responses() + .pop() + .ok_or_else(|| Error::Internal("Server returned no results".to_string()))? + { + SingleMethodResponse::Ok((_, response, _)) => Ok(response), + SingleMethodResponse::Error((_, err, _)) => Err(err.into()), + } } fn add_method_call(&mut self, method: Method, arguments: Arguments) -> &mut Arguments { @@ -432,7 +440,7 @@ impl<'x> Request<'x> { pub fn get_push(&mut self) -> &mut GetRequest { self.add_method_call( Method::GetPushSubscription, - Arguments::push_get(self.client.default_account_id().to_string()), + Arguments::push_get(self.default_account_id.clone()), ) .push_get_mut() } @@ -440,7 +448,7 @@ impl<'x> Request<'x> { pub fn set_push(&mut self) -> &mut SetRequest, ()> { self.add_method_call( Method::SetPushSubscription, - Arguments::push_set(self.client.default_account_id().to_string()), + Arguments::push_set(self.default_account_id.clone()), ) .push_set_mut() } @@ -460,7 +468,7 @@ impl<'x> Request<'x> { pub fn get_mailbox(&mut self) -> &mut GetRequest { self.add_method_call( Method::GetMailbox, - Arguments::mailbox_get(self.client.default_account_id().to_string()), + Arguments::mailbox_get(self.default_account_id.clone()), ) .mailbox_get_mut() } @@ -468,10 +476,7 @@ impl<'x> Request<'x> { pub fn changes_mailbox(&mut self, since_state: impl Into) -> &mut ChangesRequest { self.add_method_call( Method::ChangesMailbox, - Arguments::changes( - self.client.default_account_id().to_string(), - since_state.into(), - ), + Arguments::changes(self.default_account_id.clone(), since_state.into()), ) .changes_mut() } @@ -485,7 +490,7 @@ impl<'x> Request<'x> { > { self.add_method_call( Method::QueryMailbox, - Arguments::mailbox_query(self.client.default_account_id().to_string()), + Arguments::mailbox_query(self.default_account_id.clone()), ) .mailbox_query_mut() } @@ -501,7 +506,7 @@ impl<'x> Request<'x> { self.add_method_call( Method::QueryChangesMailbox, Arguments::mailbox_query_changes( - self.client.default_account_id().to_string(), + self.default_account_id.clone(), since_query_state.into(), ), ) @@ -511,7 +516,7 @@ impl<'x> Request<'x> { pub fn set_mailbox(&mut self) -> &mut SetRequest, mailbox::SetArguments> { self.add_method_call( Method::SetMailbox, - Arguments::mailbox_set(self.client.default_account_id().to_string()), + Arguments::mailbox_set(self.default_account_id.clone()), ) .mailbox_set_mut() } @@ -519,7 +524,7 @@ impl<'x> Request<'x> { pub fn get_thread(&mut self) -> &mut GetRequest { self.add_method_call( Method::GetThread, - Arguments::thread_get(self.client.default_account_id().to_string()), + Arguments::thread_get(self.default_account_id.clone()), ) .thread_get_mut() } @@ -527,17 +532,14 @@ impl<'x> Request<'x> { pub fn changes_thread(&mut self, since_state: impl Into) -> &mut ChangesRequest { self.add_method_call( Method::ChangesThread, - Arguments::changes( - self.client.default_account_id().to_string(), - since_state.into(), - ), + Arguments::changes(self.default_account_id.clone(), since_state.into()), ) .changes_mut() } pub fn get_email(&mut self) -> &mut GetRequest { self.add_method_call( Method::GetEmail, - Arguments::email_get(self.client.default_account_id().to_string()), + Arguments::email_get(self.default_account_id.clone()), ) .email_get_mut() } @@ -545,10 +547,7 @@ impl<'x> Request<'x> { pub fn changes_email(&mut self, since_state: impl Into) -> &mut ChangesRequest { self.add_method_call( Method::ChangesEmail, - Arguments::changes( - self.client.default_account_id().to_string(), - since_state.into(), - ), + Arguments::changes(self.default_account_id.clone(), since_state.into()), ) .changes_mut() } @@ -559,7 +558,7 @@ impl<'x> Request<'x> { { self.add_method_call( Method::QueryEmail, - Arguments::email_query(self.client.default_account_id().to_string()), + Arguments::email_query(self.default_account_id.clone()), ) .email_query_mut() } @@ -575,7 +574,7 @@ impl<'x> Request<'x> { self.add_method_call( Method::QueryChangesEmail, Arguments::email_query_changes( - self.client.default_account_id().to_string(), + self.default_account_id.clone(), since_query_state.into(), ), ) @@ -585,7 +584,7 @@ impl<'x> Request<'x> { pub fn set_email(&mut self) -> &mut SetRequest, ()> { self.add_method_call( Method::SetEmail, - Arguments::email_set(self.client.default_account_id().to_string()), + Arguments::email_set(self.default_account_id.clone()), ) .email_set_mut() } @@ -605,7 +604,7 @@ impl<'x> Request<'x> { pub fn import_email(&mut self) -> &mut EmailImportRequest { self.add_method_call( Method::ImportEmail, - Arguments::email_import(self.client.default_account_id().to_string()), + Arguments::email_import(self.default_account_id.clone()), ) .email_import_mut() } @@ -613,7 +612,7 @@ impl<'x> Request<'x> { pub fn parse_email(&mut self) -> &mut EmailParseRequest { self.add_method_call( Method::ParseEmail, - Arguments::email_parse(self.client.default_account_id().to_string()), + Arguments::email_parse(self.default_account_id.clone()), ) .email_parse_mut() } @@ -621,7 +620,7 @@ impl<'x> Request<'x> { pub fn get_identity(&mut self) -> &mut GetRequest { self.add_method_call( Method::GetIdentity, - Arguments::identity_get(self.client.default_account_id().to_string()), + Arguments::identity_get(self.default_account_id.clone()), ) .identity_get_mut() } @@ -629,7 +628,7 @@ impl<'x> Request<'x> { pub fn set_identity(&mut self) -> &mut SetRequest, ()> { self.add_method_call( Method::SetIdentity, - Arguments::identity_set(self.client.default_account_id().to_string()), + Arguments::identity_set(self.default_account_id.clone()), ) .identity_set_mut() } @@ -640,7 +639,7 @@ impl<'x> Request<'x> { } self.add_method_call( Method::GetEmailSubmission, - Arguments::email_submission_get(self.client.default_account_id().to_string()), + Arguments::email_submission_get(self.default_account_id.clone()), ) .email_submission_get_mut() } @@ -654,10 +653,7 @@ impl<'x> Request<'x> { } self.add_method_call( Method::ChangesEmailSubmission, - Arguments::changes( - self.client.default_account_id().to_string(), - since_state.into(), - ), + Arguments::changes(self.default_account_id.clone(), since_state.into()), ) .changes_mut() } @@ -671,7 +667,7 @@ impl<'x> Request<'x> { } self.add_method_call( Method::QueryEmailSubmission, - Arguments::email_submission_query(self.client.default_account_id().to_string()), + Arguments::email_submission_query(self.default_account_id.clone()), ) .email_submission_query_mut() } @@ -690,7 +686,7 @@ impl<'x> Request<'x> { self.add_method_call( Method::QueryChangesEmailSubmission, Arguments::email_submission_query_changes( - self.client.default_account_id().to_string(), + self.default_account_id.clone(), since_query_state.into(), ), ) @@ -705,7 +701,7 @@ impl<'x> Request<'x> { } self.add_method_call( Method::SetEmailSubmission, - Arguments::email_submission_set(self.client.default_account_id().to_string()), + Arguments::email_submission_set(self.default_account_id.clone()), ) .email_submission_set_mut() } @@ -716,7 +712,7 @@ impl<'x> Request<'x> { } self.add_method_call( Method::GetVacationResponse, - Arguments::vacation_response_get(self.client.default_account_id().to_string()), + Arguments::vacation_response_get(self.default_account_id.clone()), ) .vacation_response_get_mut() } @@ -727,7 +723,7 @@ impl<'x> Request<'x> { } self.add_method_call( Method::SetVacationResponse, - Arguments::vacation_response_set(self.client.default_account_id().to_string()), + Arguments::vacation_response_set(self.default_account_id.clone()), ) .vacation_response_set_mut() } diff --git a/src/core/response.rs b/src/core/response.rs index 8bdeec3..91f5081 100644 --- a/src/core/response.rs +++ b/src/core/response.rs @@ -20,9 +20,9 @@ use super::{ }; #[derive(Debug, Deserialize)] -pub struct Response { +pub struct Response { #[serde(rename = "methodResponses")] - method_responses: Vec, + method_responses: Vec, #[serde(rename = "createdIds")] created_ids: Option>, @@ -31,15 +31,13 @@ pub struct Response { session_state: String, } -impl Response { - pub fn method_responses(&self) -> &[MethodResponse] { +impl Response { + pub fn method_responses(&self) -> &[T] { self.method_responses.as_ref() } - pub fn method_response(&self, id: &str) -> Option<&MethodResponse> { + pub fn unwrap_method_responses(self) -> Vec { self.method_responses - .iter() - .find(|response| response.call_id() == id) } pub fn created_ids(&self) -> Option> { @@ -51,92 +49,82 @@ impl Response { } } +impl Response { + pub fn method_response(&self, id: &str) -> Option<&MethodResponse> { + self.method_responses + .iter() + .find(|response| response.call_id() == id) + } +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum SingleMethodResponse { + Error((Error, MethodError, String)), + Ok((String, T, String)), +} + +pub type PushSubscriptionSetResponse = + SetResponse, push_subscription::Property>; +pub type PushSubscriptionGetResponse = GetResponse>; +pub type MaiboxChangesResponse = ChangesResponse; +pub type MailboxSetResponse = SetResponse, mailbox::Property>; +pub type MailboxGetResponse = GetResponse>; +pub type ThreadGetResponse = GetResponse; +pub type ThreadChangesResponse = ChangesResponse<()>; +pub type EmailGetResponse = GetResponse>; +pub type EmailSetResponse = SetResponse, email::Property>; +pub type EmailCopyResponse = CopyResponse, email::Property>; +pub type EmailChangesResponse = ChangesResponse<()>; +pub type SearchSnippetGetResponse = GetResponse; +pub type IdentitySetResponse = SetResponse, identity::Property>; +pub type IdentityGetResponse = GetResponse>; +pub type IdentityChangesResponse = ChangesResponse<()>; +pub type EmailSubmissionSetResponse = SetResponse, email_submission::Property>; +pub type EmailSubmissionGetResponse = GetResponse>; +pub type EmailSubmissionChangesResponse = ChangesResponse<()>; +pub type VacationResponseGetResponse = GetResponse>; +pub type VacationResponseSetResponse = + SetResponse, vacation_response::Property>; + #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum MethodResponse { CopyBlob((CopyBlob, CopyBlobResponse, String)), - GetPushSubscription( - ( - GetPushSubscription, - GetResponse>, - String, - ), - ), - SetPushSubscription( - ( - SetPushSubscription, - SetResponse, push_subscription::Property>, - String, - ), - ), - GetMailbox((GetMailbox, GetResponse>, String)), - ChangesMailbox( - ( - ChangesMailbox, - ChangesResponse, - String, - ), - ), + GetPushSubscription((GetPushSubscription, PushSubscriptionGetResponse, String)), + SetPushSubscription((SetPushSubscription, PushSubscriptionSetResponse, String)), + GetMailbox((GetMailbox, MailboxGetResponse, String)), + ChangesMailbox((ChangesMailbox, MaiboxChangesResponse, String)), QueryMailbox((QueryMailbox, QueryResponse, String)), QueryChangesMailbox((QueryChangesMailbox, QueryChangesResponse, String)), - SetMailbox( - ( - SetMailbox, - SetResponse, mailbox::Property>, - String, - ), - ), - GetThread((GetThread, GetResponse, String)), - ChangesThread((ChangesThread, ChangesResponse<()>, String)), - GetEmail((GetEmail, GetResponse>, String)), - ChangesEmail((ChangesEmail, ChangesResponse<()>, String)), + SetMailbox((SetMailbox, MailboxSetResponse, String)), + GetThread((GetThread, ThreadGetResponse, String)), + ChangesThread((ChangesThread, ThreadChangesResponse, String)), + GetEmail((GetEmail, EmailGetResponse, String)), + ChangesEmail((ChangesEmail, EmailChangesResponse, String)), QueryEmail((QueryEmail, QueryResponse, String)), QueryChangesEmail((QueryChangesEmail, QueryChangesResponse, String)), - SetEmail((SetEmail, SetResponse, email::Property>, String)), - CopyEmail((CopyEmail, CopyResponse, email::Property>, String)), + SetEmail((SetEmail, EmailSetResponse, String)), + CopyEmail((CopyEmail, EmailCopyResponse, String)), ImportEmail((ImportEmail, EmailImportResponse, String)), ParseEmail((ParseEmail, EmailParseResponse, String)), - GetSearchSnippet((GetSearchSnippet, GetResponse, String)), - GetIdentity((GetIdentity, GetResponse>, String)), - ChangesIdentity((ChangesIdentity, ChangesResponse<()>, String)), - SetIdentity( + GetSearchSnippet((GetSearchSnippet, SearchSnippetGetResponse, String)), + GetIdentity((GetIdentity, IdentityGetResponse, String)), + ChangesIdentity((ChangesIdentity, IdentityChangesResponse, String)), + SetIdentity((SetIdentity, IdentitySetResponse, String)), + GetEmailSubmission((GetEmailSubmission, EmailSubmissionGetResponse, String)), + ChangesEmailSubmission( ( - SetIdentity, - SetResponse, identity::Property>, + ChangesEmailSubmission, + EmailSubmissionChangesResponse, String, ), ), - GetEmailSubmission( - ( - GetEmailSubmission, - GetResponse>, - String, - ), - ), - ChangesEmailSubmission((ChangesEmailSubmission, ChangesResponse<()>, String)), QueryEmailSubmission((QueryEmailSubmission, QueryResponse, String)), QueryChangesEmailSubmission((QueryChangesEmailSubmission, QueryChangesResponse, String)), - SetEmailSubmission( - ( - SetEmailSubmission, - SetResponse, email_submission::Property>, - String, - ), - ), - GetVacationResponse( - ( - GetVacationResponse, - GetResponse>, - String, - ), - ), - SetVacationResponse( - ( - SetVacationResponse, - SetResponse, vacation_response::Property>, - String, - ), - ), + SetEmailSubmission((SetEmailSubmission, EmailSubmissionSetResponse, String)), + GetVacationResponse((GetVacationResponse, VacationResponseGetResponse, String)), + SetVacationResponse((SetVacationResponse, VacationResponseSetResponse, String)), Echo((Echo, serde_json::Value, String)), Error((Error, MethodError, String)), } @@ -221,226 +209,243 @@ impl MethodResponse { ) } - pub fn as_copy_copy(&self) -> Option<&CopyBlobResponse> { + pub fn unwrap_copy_blob(self) -> crate::Result { match self { - Self::CopyBlob((_, response, _)) => response.into(), - _ => None, + Self::CopyBlob((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_push_subscription(&self) -> Option<&GetResponse>> { + pub fn unwrap_get_push_subscription(self) -> crate::Result { match self { - Self::GetPushSubscription((_, response, _)) => response.into(), - _ => None, + Self::GetPushSubscription((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_set_push_subscription( - &self, - ) -> Option<&SetResponse, push_subscription::Property>> { + pub fn unwrap_set_push_subscription(self) -> crate::Result { match self { - Self::SetPushSubscription((_, response, _)) => response.into(), - _ => None, + Self::SetPushSubscription((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_mailbox(&self) -> Option<&GetResponse>> { + pub fn unwrap_get_mailbox(self) -> crate::Result { match self { - Self::GetMailbox((_, response, _)) => response.into(), - _ => None, + Self::GetMailbox((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_changes_mailbox(&self) -> Option<&ChangesResponse> { + pub fn unwrap_changes_mailbox(self) -> crate::Result { match self { - Self::ChangesMailbox((_, response, _)) => response.into(), - _ => None, + Self::ChangesMailbox((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_query_mailbox(&self) -> Option<&QueryResponse> { + pub fn unwrap_query_mailbox(self) -> crate::Result { match self { - Self::QueryMailbox((_, response, _)) => response.into(), - _ => None, + Self::QueryMailbox((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_query_changes_mailbox(&self) -> Option<&QueryChangesResponse> { + pub fn unwrap_query_changes_mailbox(self) -> crate::Result { match self { - Self::QueryChangesMailbox((_, response, _)) => response.into(), - _ => None, + Self::QueryChangesMailbox((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_set_mailbox(&self) -> Option<&SetResponse, mailbox::Property>> { + pub fn unwrap_set_mailbox(self) -> crate::Result { match self { - Self::SetMailbox((_, response, _)) => response.into(), - _ => None, + Self::SetMailbox((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_thread(&self) -> Option<&GetResponse> { + pub fn unwrap_get_thread(self) -> crate::Result { match self { - Self::GetThread((_, response, _)) => response.into(), - _ => None, + Self::GetThread((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_changes_thread(&self) -> Option<&ChangesResponse<()>> { + pub fn unwrap_changes_thread(self) -> crate::Result { match self { - Self::ChangesThread((_, response, _)) => response.into(), - _ => None, + Self::ChangesThread((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_email(&self) -> Option<&GetResponse> { + pub fn unwrap_get_email(self) -> crate::Result { match self { - Self::GetEmail((_, response, _)) => response.into(), - _ => None, + Self::GetEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_changes_email(&self) -> Option<&ChangesResponse<()>> { + pub fn unwrap_changes_email(self) -> crate::Result { match self { - Self::ChangesEmail((_, response, _)) => response.into(), - _ => None, + Self::ChangesEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_query_email(&self) -> Option<&QueryResponse> { + pub fn unwrap_query_email(self) -> crate::Result { match self { - Self::QueryEmail((_, response, _)) => response.into(), - _ => None, + Self::QueryEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_query_changes_email(&self) -> Option<&QueryChangesResponse> { + pub fn unwrap_query_changes_email(self) -> crate::Result { match self { - Self::QueryChangesEmail((_, response, _)) => response.into(), - _ => None, + Self::QueryChangesEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_set_email(&self) -> Option<&SetResponse> { + pub fn unwrap_set_email(self) -> crate::Result { match self { - Self::SetEmail((_, response, _)) => response.into(), - _ => None, + Self::SetEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_copy_email(&self) -> Option<&CopyResponse, email::Property>> { + pub fn unwrap_copy_email(self) -> crate::Result { match self { - Self::CopyEmail((_, response, _)) => response.into(), - _ => None, + Self::CopyEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_import_email(&self) -> Option<&EmailImportResponse> { + pub fn unwrap_import_email(self) -> crate::Result { match self { - Self::ImportEmail((_, response, _)) => response.into(), - _ => None, + Self::ImportEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_parse_email(&self) -> Option<&EmailParseResponse> { + pub fn unwrap_parse_email(self) -> crate::Result { match self { - Self::ParseEmail((_, response, _)) => response.into(), - _ => None, + Self::ParseEmail((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_search_snippet(&self) -> Option<&GetResponse> { + pub fn unwrap_get_search_snippet(self) -> crate::Result { match self { - Self::GetSearchSnippet((_, response, _)) => response.into(), - _ => None, + Self::GetSearchSnippet((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_identity(&self) -> Option<&GetResponse> { + pub fn unwrap_get_identity(self) -> crate::Result { match self { - Self::GetIdentity((_, response, _)) => response.into(), - _ => None, + Self::GetIdentity((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_changes_identity(&self) -> Option<&ChangesResponse<()>> { + pub fn unwrap_changes_identity(self) -> crate::Result { match self { - Self::ChangesIdentity((_, response, _)) => response.into(), - _ => None, + Self::ChangesIdentity((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_set_identity(&self) -> Option<&SetResponse> { + pub fn unwrap_set_identity(self) -> crate::Result { match self { - Self::SetIdentity((_, response, _)) => response.into(), - _ => None, + Self::SetIdentity((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_email_submission(&self) -> Option<&GetResponse> { + pub fn unwrap_get_email_submission(self) -> crate::Result { match self { - Self::GetEmailSubmission((_, response, _)) => response.into(), - _ => None, + Self::GetEmailSubmission((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_changes_email_submission(&self) -> Option<&ChangesResponse<()>> { + pub fn unwrap_changes_email_submission(self) -> crate::Result { match self { - Self::ChangesEmailSubmission((_, response, _)) => response.into(), - _ => None, + Self::ChangesEmailSubmission((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_set_email_submission( - &self, - ) -> Option<&SetResponse> { + pub fn unwrap_set_email_submission(self) -> crate::Result { match self { - Self::SetEmailSubmission((_, response, _)) => response.into(), - _ => None, + Self::SetEmailSubmission((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_query_email_submission(&self) -> Option<&QueryResponse> { + pub fn unwrap_query_email_submission(self) -> crate::Result { match self { - Self::QueryEmailSubmission((_, response, _)) => response.into(), - _ => None, + Self::QueryEmailSubmission((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_query_changes_email_submission(&self) -> Option<&QueryChangesResponse> { + pub fn unwrap_query_changes_email_submission(self) -> crate::Result { match self { - Self::QueryChangesEmailSubmission((_, response, _)) => response.into(), - _ => None, + Self::QueryChangesEmailSubmission((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_get_vacation_response(&self) -> Option<&GetResponse> { + pub fn unwrap_get_vacation_response(self) -> crate::Result { match self { - Self::GetVacationResponse((_, response, _)) => response.into(), - _ => None, + Self::GetVacationResponse((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_set_vacation_response( - &self, - ) -> Option<&SetResponse> { + pub fn unwrap_set_vacation_response(self) -> crate::Result { match self { - Self::SetVacationResponse((_, response, _)) => response.into(), - _ => None, + Self::SetVacationResponse((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } - pub fn as_echo(&self) -> Option<&serde_json::Value> { + pub fn unwrap_echo(self) -> crate::Result { match self { - Self::Echo((_, response, _)) => response.into(), - _ => None, - } - } - - pub fn as_error(&self) -> Option<&MethodError> { - match self { - Self::Error((_, response, _)) => response.into(), - _ => None, + Self::Echo((_, response, _)) => Ok(response), + Self::Error((_, err, _)) => Err(err.into()), + _ => Err("Response type mismatch".into()), } } diff --git a/src/core/session.rs b/src/core/session.rs index 3f5a3fe..2f9f099 100644 --- a/src/core/session.rs +++ b/src/core/session.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::email::{MailCapabilities, SubmissionCapabilities}; -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Session { #[serde(rename = "capabilities")] capabilities: HashMap, @@ -34,7 +34,7 @@ pub struct Session { state: String, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Account { #[serde(rename = "name")] name: String, @@ -49,7 +49,7 @@ pub struct Account { account_capabilities: HashMap, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Capabilities { Core(CoreCapabilities), @@ -59,7 +59,7 @@ pub enum Capabilities { Other(serde_json::Value), } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct CoreCapabilities { #[serde(rename = "maxSizeUpload")] max_size_upload: usize, @@ -86,7 +86,7 @@ pub struct CoreCapabilities { collation_algorithms: Vec, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmptyCapabilities {} impl Session { @@ -235,6 +235,14 @@ impl URLPart { } } + if !buf.is_empty() { + if !in_parameter { + parts.push(URLPart::Value(buf.clone())); + } else { + return Err(crate::Error::Internal(format!("Invalid URL: {}", url))); + } + } + Ok(parts) } } diff --git a/src/core/set.rs b/src/core/set.rs index f3a8e17..599764f 100644 --- a/src/core/set.rs +++ b/src/core/set.rs @@ -1,6 +1,11 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::{ + collections::HashMap, + fmt::{self, Display, Formatter}, +}; + +use crate::Error; use super::request::ResultReference; @@ -32,7 +37,7 @@ pub struct SetRequest { } #[derive(Debug, Clone, Deserialize)] -pub struct SetResponse { +pub struct SetResponse { #[serde(rename = "accountId")] account_id: String, @@ -62,7 +67,10 @@ pub struct SetResponse { } #[derive(Debug, Clone, Deserialize)] -pub struct SetError { +pub struct SetError +where + U: Display, +{ #[serde(rename = "type")] type_: SetErrorType, description: Option, @@ -189,7 +197,7 @@ impl SetRequest { } } -impl SetResponse { +impl SetResponse { pub fn account_id(&self) -> &str { &self.account_id } @@ -202,52 +210,66 @@ impl SetResponse { &self.new_state } - pub fn created(&self) -> Option> { - self.created.as_ref().map(|map| map.iter()) + 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(&self) -> Option)>> { - self.updated.as_ref().map(|map| map.iter()) + 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(&self) -> Option<&[String]> { - self.destroyed.as_deref() + 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 not_created(&self) -> Option)>> { - self.not_created.as_ref().map(|map| map.iter()) + pub fn created_ids(&self) -> Option> { + self.created.as_ref().map(|map| map.keys()) } - pub fn not_updated(&self) -> Option)>> { - self.not_updated.as_ref().map(|map| map.iter()) + pub fn updated_ids(&self) -> Option> { + self.updated.as_ref().map(|map| map.keys()) } - pub fn not_destroyed(&self) -> Option)>> { - self.not_destroyed.as_ref().map(|map| map.iter()) + pub fn destroyed_ids(&self) -> Option> { + self.destroyed.as_ref().map(|list| list.iter()) } - pub fn created_details(&self, id: &str) -> Option<&T> { - self.created.as_ref().and_then(|map| map.get(id)) + pub fn not_created_ids(&self) -> Option> { + self.not_created.as_ref().map(|map| map.keys()) } - pub fn updated_details(&self, id: &str) -> Option<&T> { - self.updated.as_ref().and_then(|map| map.get(id))?.as_ref() + pub fn not_updated_ids(&self) -> Option> { + self.not_updated.as_ref().map(|map| map.keys()) } - pub fn not_created_details(&self, id: &str) -> Option<&SetError> { - self.not_created.as_ref().and_then(|map| map.get(id)) - } - - pub fn not_updated_details(&self, id: &str) -> Option<&SetError> { - self.not_updated.as_ref().and_then(|map| map.get(id)) - } - - pub fn not_destroyed_details(&self, id: &str) -> Option<&SetError> { - self.not_destroyed.as_ref().and_then(|map| map.get(id)) + pub fn not_destroyed_ids(&self) -> Option> { + self.not_destroyed.as_ref().map(|map| map.keys()) } } -impl SetError { +impl SetError { pub fn error(&self) -> &SetErrorType { &self.type_ } @@ -259,6 +281,67 @@ impl SetError { 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"), + } + } } pub fn from_timestamp(timestamp: i64) -> DateTime { diff --git a/src/email/helpers.rs b/src/email/helpers.rs new file mode 100644 index 0000000..f9ce9cd --- /dev/null +++ b/src/email/helpers.rs @@ -0,0 +1,138 @@ +use crate::{ + client::Client, + core::{ + query::{Comparator, Filter, QueryResponse}, + response::{EmailGetResponse, EmailSetResponse}, + }, +}; + +use super::{import::EmailImportResponse, Email, Property}; + +impl Client { + pub async fn email_import( + &mut self, + raw_message: Vec, + mailbox_ids: T, + keywords: Option, + received_at: Option, + ) -> crate::Result + where + T: IntoIterator, + U: Into, + { + let blob_id = self + .upload(self.default_account_id(), None, raw_message) + .await? + .unwrap_blob_id(); + let mut request = self.build(); + let import_request = request + .import_email() + .email(blob_id) + .mailbox_ids(mailbox_ids); + + if let Some(keywords) = keywords { + import_request.keywords(keywords); + } + + if let Some(received_at) = received_at { + import_request.received_at(received_at); + } + + let id = import_request.create_id(); + request + .send_single::() + .await? + .created(&id) + } + + pub async fn email_set_mailbox( + &mut self, + id: &str, + mailbox_id: &str, + set: bool, + ) -> crate::Result> { + let mut request = self.build(); + request.set_email().update(id).mailbox_id(mailbox_id, set); + request.send_single::().await?.updated(id) + } + + pub async fn email_set_mailboxes( + &mut self, + id: &str, + mailbox_ids: T, + ) -> crate::Result> + where + T: IntoIterator, + U: Into, + { + let mut request = self.build(); + request.set_email().update(id).mailbox_ids(mailbox_ids); + request.send_single::().await?.updated(id) + } + + pub async fn email_set_keyword( + &mut self, + id: &str, + keyword: &str, + set: bool, + ) -> crate::Result> { + let mut request = self.build(); + request.set_email().update(id).keyword(keyword, set); + request.send_single::().await?.updated(id) + } + + pub async fn email_set_keywords( + &mut self, + id: &str, + keywords: T, + ) -> crate::Result> + where + T: IntoIterator, + U: Into, + { + let mut request = self.build(); + request.set_email().update(id).keywords(keywords); + request.send_single::().await?.updated(id) + } + + pub async fn email_destroy(&mut self, id: &str) -> crate::Result<()> { + let mut request = self.build(); + request.set_email().destroy([id]); + request + .send_single::() + .await? + .destroyed(id) + } + + pub async fn email_get( + &mut self, + id: &str, + properties: Option>, + ) -> crate::Result> { + let mut request = self.build(); + let get_request = request.get_email().ids([id]); + if let Some(properties) = properties { + get_request.properties(properties.into_iter()); + } + request + .send_single::() + .await + .map(|mut r| r.unwrap_list().pop()) + } + + pub async fn email_query( + &mut self, + filter: Option>>, + sort: Option>>, + ) -> crate::Result { + let mut request = self.build(); + let query_request = request.query_email(); + if let Some(filter) = filter { + query_request.filter(filter); + } + if let Some(sort) = sort { + query_request.sort(sort.into_iter()); + } + request.send_single::().await + } +} diff --git a/src/email/import.rs b/src/email/import.rs index 46c58c2..fae935b 100644 --- a/src/email/import.rs +++ b/src/email/import.rs @@ -3,7 +3,13 @@ use std::collections::HashMap; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::core::set::{from_timestamp, SetError}; +use crate::{ + core::{ + request::ResultReference, + set::{from_timestamp, SetError}, + }, + Error, +}; use super::{Email, Property}; @@ -28,7 +34,13 @@ pub struct EmailImport { blob_id: String, #[serde(rename = "mailboxIds")] - mailbox_ids: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + mailbox_ids: Option>, + + #[serde(rename = "#mailboxIds")] + #[serde(skip_deserializing)] + #[serde(skip_serializing_if = "Option::is_none")] + mailbox_ids_ref: Option, #[serde(rename = "keywords")] keywords: HashMap, @@ -86,19 +98,35 @@ impl EmailImport { EmailImport { create_id, blob_id, - mailbox_ids: HashMap::new(), + mailbox_ids: None, + mailbox_ids_ref: None, keywords: HashMap::new(), received_at: None, } } - pub fn mailbox_id(&mut self, mailbox_id: impl Into) -> &mut Self { - self.mailbox_ids.insert(mailbox_id.into(), true); + pub fn mailbox_ids(&mut self, mailbox_ids: T) -> &mut Self + where + T: IntoIterator, + U: Into, + { + self.mailbox_ids = Some(mailbox_ids.into_iter().map(|s| (s.into(), true)).collect()); + self.mailbox_ids_ref = None; self } - pub fn keyword(&mut self, keyword: impl Into) -> &mut Self { - self.keywords.insert(keyword.into(), true); + pub fn mailbox_ids_ref(&mut self, reference: ResultReference) -> &mut Self { + self.mailbox_ids_ref = reference.into(); + self.mailbox_ids = None; + self + } + + pub fn keywords(&mut self, keywords: T) -> &mut Self + where + T: IntoIterator, + U: Into, + { + self.keywords = keywords.into_iter().map(|s| (s.into(), true)).collect(); self } @@ -125,19 +153,21 @@ impl EmailImportResponse { &self.new_state } - pub fn created(&self) -> Option> { + 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 created_ids(&self) -> Option> { self.created.as_ref().map(|map| map.keys()) } - pub fn not_created(&self) -> Option> { + pub fn not_created_ids(&self) -> Option> { self.not_created.as_ref().map(|map| map.keys()) } - - pub fn created_details(&self, id: &str) -> Option<&Email> { - self.created.as_ref().and_then(|map| map.get(id)) - } - - pub fn not_created_reason(&self, id: &str) -> Option<&SetError> { - self.not_created.as_ref().and_then(|map| map.get(id)) - } } diff --git a/src/email/mod.rs b/src/email/mod.rs index b17d443..c42b0e0 100644 --- a/src/email/mod.rs +++ b/src/email/mod.rs @@ -1,4 +1,5 @@ pub mod get; +pub mod helpers; pub mod import; pub mod parse; pub mod query; @@ -7,7 +8,10 @@ pub mod set; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::{ + collections::HashMap, + fmt::{self, Display, Formatter}, +}; use crate::{core::request::ResultReference, Get}; @@ -318,7 +322,39 @@ pub enum BodyProperty { SubParts, } -#[derive(Debug, Clone, Deserialize)] +impl Display for Property { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Property::Id => write!(f, "id"), + Property::BlobId => write!(f, "blobId"), + Property::ThreadId => write!(f, "threadId"), + Property::MailboxIds => write!(f, "mailboxIds"), + Property::Keywords => write!(f, "keywords"), + Property::Size => write!(f, "size"), + Property::ReceivedAt => write!(f, "receivedAt"), + Property::MessageId => write!(f, "messageId"), + Property::InReplyTo => write!(f, "inReplyTo"), + Property::References => write!(f, "references"), + Property::Sender => write!(f, "sender"), + Property::From => write!(f, "from"), + Property::To => write!(f, "to"), + Property::Cc => write!(f, "cc"), + Property::Bcc => write!(f, "bcc"), + Property::ReplyTo => write!(f, "replyTo"), + Property::Subject => write!(f, "subject"), + Property::SentAt => write!(f, "sentAt"), + Property::BodyStructure => write!(f, "bodyStructure"), + Property::BodyValues => write!(f, "bodyValues"), + Property::TextBody => write!(f, "textBody"), + Property::HtmlBody => write!(f, "htmlBody"), + Property::Attachments => write!(f, "attachments"), + Property::HasAttachment => write!(f, "hasAttachment"), + Property::Preview => write!(f, "preview"), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct MailCapabilities { #[serde(rename = "maxMailboxesPerEmail")] max_mailboxes_per_email: Option, @@ -339,7 +375,7 @@ pub struct MailCapabilities { may_create_top_level_mailbox: bool, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubmissionCapabilities { #[serde(rename = "maxDelayedSend")] max_delayed_send: usize, diff --git a/src/email_submission/mod.rs b/src/email_submission/mod.rs index d261be8..755efcd 100644 --- a/src/email_submission/mod.rs +++ b/src/email_submission/mod.rs @@ -2,7 +2,7 @@ pub mod get; pub mod query; pub mod set; -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -149,3 +149,20 @@ pub enum Property { #[serde(rename = "mdnBlobIds")] MdnBlobIds, } + +impl Display for Property { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Property::Id => write!(f, "id"), + Property::IdentityId => write!(f, "identityId"), + Property::EmailId => write!(f, "emailId"), + Property::ThreadId => write!(f, "threadId"), + Property::Envelope => write!(f, "envelope"), + Property::SendAt => write!(f, "sendAt"), + Property::UndoStatus => write!(f, "undoStatus"), + Property::DeliveryStatus => write!(f, "deliveryStatus"), + Property::DsnBlobIds => write!(f, "dsnBlobIds"), + Property::MdnBlobIds => write!(f, "mdnBlobIds"), + } + } +} diff --git a/src/identity/mod.rs b/src/identity/mod.rs index 7d3123d..f251669 100644 --- a/src/identity/mod.rs +++ b/src/identity/mod.rs @@ -1,6 +1,8 @@ pub mod get; pub mod set; +use std::fmt::Display; + use crate::core::set::list_not_set; use crate::{email::EmailAddress, Get}; use serde::{Deserialize, Serialize}; @@ -65,3 +67,18 @@ pub enum Property { #[serde(rename = "mayDelete")] MayDelete, } + +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::Email => write!(f, "email"), + Property::ReplyTo => write!(f, "replyTo"), + Property::Bcc => write!(f, "bcc"), + Property::TextSignature => write!(f, "textSignature"), + Property::HtmlSignature => write!(f, "htmlSignature"), + Property::MayDelete => write!(f, "mayDelete"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 67635ed..ac2b01b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ +use crate::core::error::MethodError; use crate::core::error::ProblemDetails; +use crate::core::set::SetError; use std::{collections::HashMap, fmt::Display}; use serde::{Deserialize, Serialize}; @@ -130,12 +132,15 @@ pub struct Set; pub type Result = std::result::Result; +#[derive(Debug)] pub enum Error { Transport(reqwest::Error), Parse(serde_json::Error), Internal(String), Problem(ProblemDetails), - ServerError(String), + Server(String), + Method(MethodError), + Set(SetError), } impl From for Error { @@ -150,6 +155,24 @@ impl From for Error { } } +impl From for Error { + fn from(e: MethodError) -> Self { + Error::Method(e) + } +} + +impl From> for Error { + fn from(e: SetError) -> Self { + Error::Set(e) + } +} + +impl From<&str> for Error { + fn from(s: &str) -> Self { + Error::Internal(s.to_string()) + } +} + impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -157,7 +180,9 @@ impl Display for Error { Error::Parse(e) => write!(f, "Parse error: {}", e), Error::Internal(e) => write!(f, "Internal error: {}", e), Error::Problem(e) => write!(f, "Problem details: {}", e), - Error::ServerError(e) => write!(f, "Server error: {}", e), + Error::Server(e) => write!(f, "Server error: {}", e), + Error::Method(e) => write!(f, "Method error: {}", e), + Error::Set(e) => write!(f, "Set error: {}", e), } } } diff --git a/src/mailbox/helpers.rs b/src/mailbox/helpers.rs new file mode 100644 index 0000000..7cd63de --- /dev/null +++ b/src/mailbox/helpers.rs @@ -0,0 +1,130 @@ +use crate::{ + client::Client, + core::{ + query::{Comparator, Filter, QueryResponse}, + response::{MailboxGetResponse, MailboxSetResponse}, + set::Create, + }, +}; + +use super::{Mailbox, Property, Role}; + +impl Client { + pub async fn mailbox_create( + &mut self, + name: impl Into, + parent_id: Option>, + role: Role, + ) -> crate::Result { + let mut request = self.build(); + let id = request + .set_mailbox() + .create() + .name(name) + .role(role) + .parent_id(parent_id) + .create_id() + .unwrap(); + request + .send_single::() + .await? + .created(&id) + } + + pub async fn mailbox_rename( + &mut self, + id: &str, + name: impl Into, + ) -> crate::Result> { + let mut request = self.build(); + request.set_mailbox().update(id).name(name); + request + .send_single::() + .await? + .updated(id) + } + + pub async fn mailbox_move( + &mut self, + id: &str, + parent_id: Option>, + ) -> crate::Result> { + let mut request = self.build(); + request.set_mailbox().update(id).parent_id(parent_id); + request + .send_single::() + .await? + .updated(id) + } + + pub async fn mailbox_update_role( + &mut self, + id: &str, + role: Role, + ) -> crate::Result> { + let mut request = self.build(); + request.set_mailbox().update(id).role(role); + request + .send_single::() + .await? + .updated(id) + } + + pub async fn mailbox_update_sort_order( + &mut self, + id: &str, + sort_order: u32, + ) -> crate::Result> { + let mut request = self.build(); + request.set_mailbox().update(id).sort_order(sort_order); + request + .send_single::() + .await? + .updated(id) + } + + pub async fn mailbox_destroy(&mut self, id: &str, delete_emails: bool) -> crate::Result<()> { + let mut request = self.build(); + request + .set_mailbox() + .destroy([id]) + .arguments() + .on_destroy_remove_emails(delete_emails); + request + .send_single::() + .await? + .destroyed(id) + } + + pub async fn mailbox_get( + &mut self, + id: &str, + properties: Option>, + ) -> crate::Result> { + let mut request = self.build(); + let get_request = request.get_mailbox().ids([id]); + if let Some(properties) = properties { + get_request.properties(properties.into_iter()); + } + request + .send_single::() + .await + .map(|mut r| r.unwrap_list().pop()) + } + + pub async fn mailbox_query( + &mut self, + filter: Option>>, + sort: Option>>, + ) -> crate::Result { + let mut request = self.build(); + let query_request = request.query_mailbox(); + if let Some(filter) = filter { + query_request.filter(filter); + } + if let Some(sort) = sort { + query_request.sort(sort.into_iter()); + } + request.send_single::().await + } +} diff --git a/src/mailbox/mod.rs b/src/mailbox/mod.rs index 8a85c91..3ca9693 100644 --- a/src/mailbox/mod.rs +++ b/src/mailbox/mod.rs @@ -1,7 +1,10 @@ pub mod get; +pub mod helpers; pub mod query; pub mod set; +use std::fmt::Display; + use crate::core::set::string_not_set; use crate::mailbox::set::role_not_set; use crate::Get; @@ -10,7 +13,8 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Default)] pub struct SetArguments { #[serde(rename = "onDestroyRemoveEmails")] - on_destroy_remove_emails: bool, + #[serde(skip_serializing_if = "Option::is_none")] + on_destroy_remove_emails: Option, } #[derive(Debug, Clone, Serialize, Default)] @@ -149,6 +153,24 @@ pub enum Property { IsSubscribed, } +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::ParentId => write!(f, "parentId"), + Property::Role => write!(f, "role"), + Property::SortOrder => write!(f, "sortOrder"), + Property::TotalEmails => write!(f, "totalEmails"), + Property::UnreadEmails => write!(f, "unreadEmails"), + Property::TotalThreads => write!(f, "totalThreads"), + Property::UnreadThreads => write!(f, "unreadThreads"), + Property::MyRights => write!(f, "myRights"), + Property::IsSubscribed => write!(f, "isSubscribed"), + } + } +} + impl ChangesResponse { pub fn updated_properties(&self) -> Option<&[Property]> { self.updated_properties.as_deref() diff --git a/src/mailbox/set.rs b/src/mailbox/set.rs index 389c9a0..094a8be 100644 --- a/src/mailbox/set.rs +++ b/src/mailbox/set.rs @@ -58,7 +58,7 @@ impl Create for Mailbox { impl SetArguments { pub fn on_destroy_remove_emails(&mut self, value: bool) -> &mut Self { - self.on_destroy_remove_emails = value; + self.on_destroy_remove_emails = value.into(); self } } diff --git a/src/push_subscription/mod.rs b/src/push_subscription/mod.rs index ebaa533..f1e39a0 100644 --- a/src/push_subscription/mod.rs +++ b/src/push_subscription/mod.rs @@ -1,6 +1,8 @@ pub mod get; pub mod set; +use std::fmt::Display; + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -62,6 +64,20 @@ pub enum Property { Types, } +impl Display for Property { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Property::Id => write!(f, "id"), + Property::DeviceClientId => write!(f, "deviceClientId"), + Property::Url => write!(f, "url"), + Property::Keys => write!(f, "keys"), + Property::VerificationCode => write!(f, "verificationCode"), + Property::Expires => write!(f, "expires"), + Property::Types => write!(f, "types"), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Keys { p256dh: String, diff --git a/src/push_subscription/set.rs b/src/push_subscription/set.rs index c67d907..0cc0347 100644 --- a/src/push_subscription/set.rs +++ b/src/push_subscription/set.rs @@ -64,9 +64,4 @@ impl Keys { auth: base64::encode_config(&auth, base64::URL_SAFE), } } - - pub fn generate() -> Option { - let (p256dh, auth) = ece::generate_keypair_and_auth_secret().ok()?; - Self::new(&p256dh.pub_as_raw().ok()?, &auth).into() - } } diff --git a/src/vacation_response/mod.rs b/src/vacation_response/mod.rs index a5d8bab..bd6f569 100644 --- a/src/vacation_response/mod.rs +++ b/src/vacation_response/mod.rs @@ -1,6 +1,8 @@ pub mod get; pub mod set; +use std::fmt::Display; + use crate::core::set::date_not_set; use crate::core::set::string_not_set; use crate::Get; @@ -61,3 +63,17 @@ pub enum Property { #[serde(rename = "htmlBody")] HtmlBody, } + +impl Display for Property { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Property::Id => write!(f, "id"), + Property::IsEnabled => write!(f, "isEnabled"), + Property::FromDate => write!(f, "fromDate"), + Property::ToDate => write!(f, "toDate"), + Property::Subject => write!(f, "subject"), + Property::TextBody => write!(f, "textBody"), + Property::HtmlBody => write!(f, "htmlBody"), + } + } +}