diff --git a/Cargo.toml b/Cargo.toml index 063a9d7..dea0b89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ 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/core/set.rs b/src/core/set.rs index caa0247..5d13b3c 100644 --- a/src/core/set.rs +++ b/src/core/set.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::hash::Hash; @@ -66,4 +67,46 @@ pub enum SetErrorType { InvalidProperties, #[serde(rename = "singleton")] Singleton, + #[serde(rename = "mailboxHasChild")] + MailboxHasChild, + #[serde(rename = "mailboxHasEmail")] + MailboxHasEmail, + #[serde(rename = "blobNotFound")] + BlobNotFound, + #[serde(rename = "tooManyKeywords")] + TooManyKeywords, + #[serde(rename = "tooManyMailboxes")] + TooManyMailboxes, + #[serde(rename = "forbiddenFrom")] + ForbiddenFrom, + #[serde(rename = "invalidEmail")] + InvalidEmail, + #[serde(rename = "tooManyRecipients")] + TooManyRecipients, + #[serde(rename = "noRecipients")] + NoRecipients, + #[serde(rename = "invalidRecipients")] + InvalidRecipients, + #[serde(rename = "forbiddenMailFrom")] + ForbiddenMailFrom, + #[serde(rename = "forbiddenToSend")] + ForbiddenToSend, + #[serde(rename = "cannotUnsend")] + CannotUnsend, +} + +pub fn from_timestamp(timestamp: i64) -> DateTime { + DateTime::::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc) +} + +pub fn string_not_set(string: &Option) -> bool { + matches!(string, Some(string) if string.is_empty()) +} + +pub fn date_not_set(date: &Option>) -> bool { + matches!(date, Some(date) if date.timestamp() == 0) +} + +pub fn list_not_set(list: &Option>) -> bool { + matches!(list, Some(list) if list.is_empty() ) } diff --git a/src/email/get.rs b/src/email/get.rs new file mode 100644 index 0000000..5957bed --- /dev/null +++ b/src/email/get.rs @@ -0,0 +1,213 @@ +use crate::Get; + +use super::{ + Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field, +}; + +impl Email { + pub fn id(&self) -> &str { + self.id.as_ref().unwrap() + } + + pub fn blob_id(&self) -> &str { + self.blob_id.as_ref().unwrap() + } + + pub fn thread_id(&self) -> &str { + self.thread_id.as_ref().unwrap() + } + + pub fn mailbox_ids(&self) -> Vec<&str> { + self.mailbox_ids + .as_ref() + .unwrap() + .iter() + .filter(|(_, v)| **v) + .map(|(k, _)| k.as_str()) + .collect() + } + + pub fn keywords(&self) -> Vec<&str> { + self.keywords + .as_ref() + .unwrap() + .iter() + .filter(|(_, v)| **v) + .map(|(k, _)| k.as_str()) + .collect() + } + + pub fn size(&self) -> usize { + self.size.unwrap() + } + + pub fn received_at(&self) -> i64 { + self.received_at.as_ref().unwrap().timestamp() + } + + pub fn message_id(&self) -> Option<&[String]> { + self.message_id.as_deref() + } + + pub fn in_reply_to(&self) -> Option<&[String]> { + self.in_reply_to.as_deref() + } + + pub fn references(&self) -> Option<&[String]> { + self.references.as_deref() + } + + pub fn sender(&self) -> Option<&[EmailAddress]> { + self.sender.as_deref() + } + + pub fn from(&self) -> Option<&[EmailAddress]> { + self.from.as_deref() + } + + pub fn to(&self) -> Option<&[EmailAddress]> { + self.to.as_deref() + } + + pub fn cc(&self) -> Option<&[EmailAddress]> { + self.cc.as_deref() + } + + pub fn bcc(&self) -> Option<&[EmailAddress]> { + self.bcc.as_deref() + } + + pub fn subject(&self) -> Option<&str> { + self.subject.as_deref() + } + + pub fn sent_at(&self) -> Option { + self.sent_at.as_ref().map(|v| v.timestamp()) + } + + pub fn body_structure(&self) -> Option<&EmailBodyPart> { + self.body_structure.as_deref() + } + + pub fn body_value(&self, id: &str) -> Option<&EmailBodyValue> { + self.body_values.as_ref().and_then(|v| v.get(id)) + } + + pub fn text_body(&self) -> Option<&[EmailBodyPart]> { + self.text_body.as_deref() + } + + pub fn html_body(&self) -> Option<&[EmailBodyPart]> { + self.html_body.as_deref() + } + + pub fn attachments(&self) -> Option<&[EmailBodyPart]> { + self.attachments.as_deref() + } + + pub fn has_attachment(&self) -> bool { + *self.has_attachment.as_ref().unwrap_or(&false) + } + + pub fn header(&self, id: &str) -> Option<&Field> { + self.others.get(id).and_then(|v| v.as_ref()) + } + + pub fn has_header(&self, id: &str) -> bool { + self.others.contains_key(id) + } +} + +impl EmailBodyPart { + pub fn part_id(&self) -> Option<&str> { + self.part_id.as_deref() + } + + pub fn blob_id(&self) -> Option<&str> { + self.blob_id.as_deref() + } + + pub fn size(&self) -> usize { + *self.size.as_ref().unwrap_or(&0) + } + + pub fn headers(&self) -> Option<&[EmailHeader]> { + self.headers.as_deref() + } + + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn charset(&self) -> Option<&str> { + self.charset.as_deref() + } + + pub fn content_type(&self) -> Option<&str> { + self.type_.as_deref() + } + + pub fn content_disposition(&self) -> Option<&str> { + self.disposition.as_deref() + } + + pub fn content_id(&self) -> Option<&str> { + self.cid.as_deref() + } + + pub fn content_language(&self) -> Option<&[String]> { + self.language.as_deref() + } + + pub fn content_location(&self) -> Option<&str> { + self.location.as_deref() + } + + pub fn sub_parts(&self) -> Option<&[EmailBodyPart]> { + self.sub_parts.as_deref() + } +} + +impl EmailBodyValue { + pub fn value(&self) -> &str { + self.value.as_str() + } + + pub fn is_encoding_problem(&self) -> bool { + self.is_encoding_problem + } + + pub fn is_truncated(&self) -> bool { + self.is_truncated + } +} + +impl EmailAddress { + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn email(&self) -> &str { + self.email.as_str() + } +} + +impl EmailAddressGroup { + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn addresses(&self) -> &[EmailAddress] { + self.addresses.as_ref() + } +} + +impl EmailHeader { + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn value(&self) -> &str { + self.value.as_str() + } +} diff --git a/src/email/mod.rs b/src/email/mod.rs new file mode 100644 index 0000000..8e2545d --- /dev/null +++ b/src/email/mod.rs @@ -0,0 +1,307 @@ +pub mod get; +pub mod set; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::Get; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Email { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "id")] + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + + #[serde(rename = "blobId")] + #[serde(skip_serializing_if = "Option::is_none")] + blob_id: Option, + + #[serde(rename = "threadId")] + #[serde(skip_serializing_if = "Option::is_none")] + thread_id: Option, + + #[serde(rename = "mailboxIds")] + #[serde(skip_serializing_if = "Option::is_none")] + mailbox_ids: Option>, + + #[serde(rename = "keywords")] + #[serde(skip_serializing_if = "Option::is_none")] + keywords: Option>, + + #[serde(rename = "size")] + #[serde(skip_serializing_if = "Option::is_none")] + size: Option, + + #[serde(rename = "receivedAt")] + #[serde(skip_serializing_if = "Option::is_none")] + received_at: Option>, + + #[serde(rename = "messageId", alias = "header:Message-ID:asMessageIds")] + #[serde(skip_serializing_if = "Option::is_none")] + message_id: Option>, + + #[serde(rename = "inReplyTo", alias = "header:In-Reply-To:asMessageIds")] + #[serde(skip_serializing_if = "Option::is_none")] + in_reply_to: Option>, + + #[serde(rename = "references", alias = "header:References:asMessageIds")] + #[serde(skip_serializing_if = "Option::is_none")] + references: Option>, + + #[serde(rename = "sender", alias = "header:Sender:asAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + sender: Option>, + + #[serde(rename = "from", alias = "header:From:asAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + from: Option>, + + #[serde(rename = "to", alias = "header:To:asAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + to: Option>, + + #[serde(rename = "cc", alias = "header:Cc:asAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + cc: Option>, + + #[serde(rename = "bcc", alias = "header:Bcc:asAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + bcc: Option>, + + #[serde(rename = "replyTo", alias = "header:Reply-To:asAddresses")] + #[serde(skip_serializing_if = "Option::is_none")] + reply_to: Option>, + + #[serde(rename = "subject", alias = "header:Subject:asText")] + #[serde(skip_serializing_if = "Option::is_none")] + subject: Option, + + #[serde(rename = "sentAt", alias = "header:Date:asDate")] + #[serde(skip_serializing_if = "Option::is_none")] + sent_at: Option>, + + #[serde(rename = "bodyStructure")] + #[serde(skip_serializing_if = "Option::is_none")] + body_structure: Option>, + + #[serde(rename = "bodyValues")] + #[serde(skip_serializing_if = "Option::is_none")] + body_values: Option>, + + #[serde(rename = "textBody")] + #[serde(skip_serializing_if = "Option::is_none")] + text_body: Option>, + + #[serde(rename = "htmlBody")] + #[serde(skip_serializing_if = "Option::is_none")] + html_body: Option>, + + #[serde(rename = "attachments")] + #[serde(skip_serializing_if = "Option::is_none")] + attachments: Option>, + + #[serde(rename = "hasAttachment")] + #[serde(skip_serializing_if = "Option::is_none")] + has_attachment: Option, + + #[serde(rename = "preview")] + #[serde(skip_serializing_if = "Option::is_none")] + preview: Option, + + #[serde(flatten)] + #[serde(skip_serializing_if = "HashMap::is_empty")] + others: HashMap>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmailBodyPart { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "partId")] + #[serde(skip_serializing_if = "Option::is_none")] + part_id: Option, + + #[serde(rename = "blobId")] + #[serde(skip_serializing_if = "Option::is_none")] + blob_id: Option, + + #[serde(rename = "size")] + #[serde(skip_serializing_if = "Option::is_none")] + size: Option, + + #[serde(rename = "headers")] + #[serde(skip_serializing_if = "Option::is_none")] + headers: Option>, + + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + name: Option, + + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + type_: Option, + + #[serde(rename = "charset")] + #[serde(skip_serializing_if = "Option::is_none")] + charset: Option, + + #[serde(rename = "disposition")] + #[serde(skip_serializing_if = "Option::is_none")] + disposition: Option, + + #[serde(rename = "cid")] + #[serde(skip_serializing_if = "Option::is_none")] + cid: Option, + + #[serde(rename = "language")] + #[serde(skip_serializing_if = "Option::is_none")] + language: Option>, + + #[serde(rename = "location")] + #[serde(skip_serializing_if = "Option::is_none")] + location: Option, + + #[serde(rename = "subParts")] + #[serde(skip_serializing_if = "Option::is_none")] + sub_parts: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmailBodyValue { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "value")] + value: String, + + #[serde(rename = "isEncodingProblem")] + is_encoding_problem: bool, + + #[serde(rename = "isTruncated")] + is_truncated: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Field { + Text(String), + TextList(Vec), + Date(DateTime), + Addresses(Vec), + GroupedAddresses(Vec), + Bool(bool), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmailAddress { + #[serde(skip)] + _state: std::marker::PhantomData, + + name: Option, + email: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmailAddressGroup { + #[serde(skip)] + _state: std::marker::PhantomData, + + name: Option, + addresses: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmailHeader { + #[serde(skip)] + _state: std::marker::PhantomData, + + name: String, + value: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum EmailProperty { + #[serde(rename = "id")] + Id, + #[serde(rename = "blobId")] + BlobId, + #[serde(rename = "threadId")] + ThreadId, + #[serde(rename = "mailboxIds")] + MailboxIds, + #[serde(rename = "keywords")] + Keywords, + #[serde(rename = "size")] + Size, + #[serde(rename = "receivedAt")] + ReceivedAt, + #[serde(rename = "messageId")] + MessageId, + #[serde(rename = "inReplyTo")] + InReplyTo, + #[serde(rename = "references")] + References, + #[serde(rename = "sender")] + Sender, + #[serde(rename = "from")] + From, + #[serde(rename = "to")] + To, + #[serde(rename = "cc")] + Cc, + #[serde(rename = "bcc")] + Bcc, + #[serde(rename = "replyTo")] + ReplyTo, + #[serde(rename = "subject")] + Subject, + #[serde(rename = "sentAt")] + SentAt, + #[serde(rename = "bodyStructure")] + BodyStructure, + #[serde(rename = "bodyValues")] + BodyValues, + #[serde(rename = "textBody")] + TextBody, + #[serde(rename = "htmlBody")] + HtmlBody, + #[serde(rename = "attachments")] + Attachments, + #[serde(rename = "hasAttachment")] + HasAttachment, + #[serde(rename = "preview")] + Preview, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum EmailBodyProperty { + #[serde(rename = "partId")] + PartId, + #[serde(rename = "blobId")] + BlobId, + #[serde(rename = "size")] + Size, + #[serde(rename = "headers")] + Headers, + #[serde(rename = "name")] + Name, + #[serde(rename = "type")] + Type, + #[serde(rename = "charset")] + Charset, + #[serde(rename = "disposition")] + Disposition, + #[serde(rename = "cid")] + Cid, + #[serde(rename = "language")] + Language, + #[serde(rename = "location")] + Location, + #[serde(rename = "subParts")] + SubParts, +} diff --git a/src/email/set.rs b/src/email/set.rs new file mode 100644 index 0000000..5469886 --- /dev/null +++ b/src/email/set.rs @@ -0,0 +1,355 @@ +use std::collections::HashMap; + +use crate::{core::set::from_timestamp, Set}; + +use super::{ + Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field, +}; + +impl Email { + pub fn mailbox_ids(mut self, mailbox_ids: impl Iterator) -> Self { + self.mailbox_ids = Some(mailbox_ids.into_iter().map(|s| (s, true)).collect()); + self + } + + pub fn mailbox_id(mut self, mailbox_id: &str, set: bool) -> Self { + self.mailbox_ids = None; + self.others.insert( + format!("mailboxIds/{}", mailbox_id), + Field::Bool(set).into(), + ); + self + } + + pub fn keywords(mut self, keywords: impl Iterator) -> Self { + self.keywords = Some(keywords.into_iter().map(|s| (s, true)).collect()); + self + } + + pub fn keyword(mut self, keyword: &str, set: bool) -> Self { + self.keywords = None; + self.others + .insert(format!("keywords/{}", keyword), Field::Bool(set).into()); + self + } + + pub fn message_id(mut self, message_id: impl Iterator) -> Self { + self.message_id = Some(message_id.into_iter().collect()); + self + } + + pub fn in_reply_to(mut self, in_reply_to: impl Iterator) -> Self { + self.in_reply_to = Some(in_reply_to.into_iter().collect()); + self + } + + pub fn references(mut self, references: impl Iterator) -> Self { + self.references = Some(references.into_iter().collect()); + self + } + + pub fn sender(mut self, sender: T) -> Self + where + T: Iterator, + U: Into, + { + self.sender = Some(sender.map(|s| s.into()).collect()); + self + } + + pub fn from(mut self, from: T) -> Self + where + T: Iterator, + U: Into, + { + self.from = Some(from.map(|s| s.into()).collect()); + self + } + + pub fn to(mut self, to: T) -> Self + where + T: Iterator, + U: Into, + { + self.to = Some(to.map(|s| s.into()).collect()); + self + } + + pub fn cc(mut self, cc: T) -> Self + where + T: Iterator, + U: Into, + { + self.cc = Some(cc.map(|s| s.into()).collect()); + self + } + + pub fn bcc(mut self, bcc: T) -> Self + where + T: Iterator, + U: Into, + { + self.bcc = Some(bcc.map(|s| s.into()).collect()); + self + } + + pub fn reply_to(mut self, reply_to: T) -> Self + where + T: Iterator, + U: Into, + { + self.reply_to = Some(reply_to.map(|s| s.into()).collect()); + self + } + + pub fn subject(mut self, subject: String) -> Self { + self.subject = Some(subject); + self + } + + pub fn sent_at(mut self, sent_at: i64) -> Self { + self.sent_at = Some(from_timestamp(sent_at)); + self + } + + pub fn body_structure(mut self, body_structure: EmailBodyPart) -> Self { + self.body_structure = Some(body_structure.into()); + self + } + + pub fn body_value(mut self, id: String, body_value: impl Into) -> Self { + self.body_values + .get_or_insert_with(HashMap::new) + .insert(id, body_value.into()); + self + } + + pub fn text_body(mut self, text_body: EmailBodyPart) -> Self { + self.text_body.get_or_insert_with(Vec::new).push(text_body); + self + } + + pub fn html_body(mut self, html_body: EmailBodyPart) -> Self { + self.html_body.get_or_insert_with(Vec::new).push(html_body); + self + } + + pub fn attachment(mut self, attachment: EmailBodyPart) -> Self { + self.attachments + .get_or_insert_with(Vec::new) + .push(attachment); + self + } + + pub fn header(mut self, header: String, value: impl Into) -> Self { + self.others.insert(header, Some(value.into())); + self + } +} + +impl Email { + pub fn new() -> Email { + Email { + _state: Default::default(), + id: Default::default(), + blob_id: Default::default(), + thread_id: Default::default(), + mailbox_ids: Default::default(), + keywords: Default::default(), + size: Default::default(), + received_at: Default::default(), + message_id: Default::default(), + in_reply_to: Default::default(), + references: Default::default(), + sender: Default::default(), + from: Default::default(), + to: Default::default(), + cc: Default::default(), + bcc: Default::default(), + reply_to: Default::default(), + subject: Default::default(), + sent_at: Default::default(), + body_structure: Default::default(), + body_values: Default::default(), + text_body: Default::default(), + html_body: Default::default(), + attachments: Default::default(), + has_attachment: Default::default(), + preview: Default::default(), + others: Default::default(), + } + } +} + +impl EmailBodyPart { + pub fn new() -> EmailBodyPart { + EmailBodyPart { + part_id: None, + blob_id: None, + size: None, + headers: None, + name: None, + type_: None, + charset: None, + disposition: None, + cid: None, + language: None, + location: None, + sub_parts: None, + _state: Default::default(), + } + } +} + +impl EmailBodyPart { + pub fn part_id(mut self, part_id: String) -> Self { + self.part_id = Some(part_id); + self + } + + pub fn blob_id(mut self, blob_id: String) -> Self { + self.blob_id = Some(blob_id); + self + } + + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn content_type(mut self, content_type: String) -> Self { + self.type_ = Some(content_type); + self + } + + pub fn content_id(mut self, content_id: String) -> Self { + self.cid = Some(content_id); + self + } + + pub fn content_language(mut self, content_language: impl Iterator) -> Self { + self.language = Some(content_language.into_iter().collect()); + self + } + + pub fn content_location(mut self, content_location: String) -> Self { + self.location = Some(content_location); + self + } + + pub fn sub_part(mut self, sub_part: EmailBodyPart) -> Self { + self.sub_parts.get_or_insert_with(Vec::new).push(sub_part); + self + } +} + +impl From for EmailBodyValue { + fn from(value: String) -> Self { + EmailBodyValue { + value, + is_encoding_problem: false, + is_truncated: false, + _state: Default::default(), + } + } +} + +impl From<&str> for EmailBodyValue { + fn from(value: &str) -> Self { + EmailBodyValue { + value: value.to_string(), + is_encoding_problem: false, + is_truncated: false, + _state: Default::default(), + } + } +} + +impl EmailAddress { + pub fn new(email: String) -> EmailAddress { + EmailAddress { + _state: Default::default(), + name: None, + email, + } + } +} + +impl EmailAddress { + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } +} + +impl From for EmailAddress { + fn from(email: String) -> Self { + EmailAddress { + _state: Default::default(), + name: None, + email, + } + } +} + +impl From<(String, String)> for EmailAddress { + fn from(parts: (String, String)) -> Self { + EmailAddress { + _state: Default::default(), + name: parts.0.into(), + email: parts.1, + } + } +} + +impl From<&str> for EmailAddress { + fn from(email: &str) -> Self { + EmailAddress { + _state: Default::default(), + name: None, + email: email.to_string(), + } + } +} + +impl From<(&str, &str)> for EmailAddress { + fn from(parts: (&str, &str)) -> Self { + EmailAddress { + _state: Default::default(), + name: parts.0.to_string().into(), + email: parts.1.to_string(), + } + } +} + +impl EmailAddressGroup { + pub fn new() -> EmailAddressGroup { + EmailAddressGroup { + _state: Default::default(), + name: None, + addresses: Vec::new(), + } + } +} + +impl EmailAddressGroup { + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn address(mut self, address: impl Into) -> Self { + self.addresses.push(address.into()); + self + } +} + +impl EmailHeader { + pub fn new(name: String, value: String) -> EmailHeader { + EmailHeader { + _state: Default::default(), + name, + value, + } + } +} diff --git a/src/email_submission/get.rs b/src/email_submission/get.rs new file mode 100644 index 0000000..287415e --- /dev/null +++ b/src/email_submission/get.rs @@ -0,0 +1,80 @@ +use crate::Get; + +use super::{Address, Delivered, DeliveryStatus, Displayed, EmailSubmission, UndoStatus}; + +impl EmailSubmission { + pub fn id(&self) -> &str { + self.id.as_ref().unwrap() + } + + pub fn identity_id(&self) -> &str { + self.identity_id.as_ref().unwrap() + } + + pub fn email_id(&self) -> &str { + self.email_id.as_ref().unwrap() + } + + pub fn thread_id(&self) -> &str { + self.thread_id.as_ref().unwrap() + } + + pub fn mail_from(&self) -> Option<&Address> { + self.envelope.as_ref().map(|e| &e.mail_from) + } + + pub fn rcpt_to(&self) -> Option<&[Address]> { + self.envelope.as_ref().map(|e| e.rcpt_to.as_ref()) + } + + pub fn send_at(&self) -> i64 { + self.send_at.as_ref().unwrap().timestamp() + } + + pub fn undo_status(&self) -> &UndoStatus { + self.undo_status.as_ref().unwrap() + } + + pub fn delivery_status(&self, email: &str) -> Option<&DeliveryStatus> { + self.delivery_status.as_ref().and_then(|ds| ds.get(email)) + } + + pub fn dsn_blob_ids(&self) -> Option<&[String]> { + self.dsn_blob_ids.as_deref() + } + + pub fn mdn_blob_ids(&self) -> Option<&[String]> { + self.mdn_blob_ids.as_deref() + } +} + +impl Address { + pub fn email(&self) -> &str { + &self.email + } + + pub fn parameter(&self, param: &str) -> Option<&str> { + self.parameters.as_ref()?.get(param)?.as_deref() + } + + pub fn has_parameter(&self, param: &str) -> bool { + self.parameters + .as_ref() + .map(|ps| ps.contains_key(param)) + .unwrap_or(false) + } +} + +impl DeliveryStatus { + pub fn smtp_reply(&self) -> &str { + &self.smtp_reply + } + + pub fn delivered(&self) -> &Delivered { + &self.delivered + } + + pub fn displayed(&self) -> &Displayed { + &self.displayed + } +} diff --git a/src/email_submission/mod.rs b/src/email_submission/mod.rs new file mode 100644 index 0000000..ee95caf --- /dev/null +++ b/src/email_submission/mod.rs @@ -0,0 +1,139 @@ +pub mod get; +pub mod set; + +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::Get; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmailSubmission { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "id")] + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + + #[serde(rename = "identityId")] + #[serde(skip_serializing_if = "Option::is_none")] + identity_id: Option, + + #[serde(rename = "emailId")] + #[serde(skip_serializing_if = "Option::is_none")] + email_id: Option, + + #[serde(rename = "threadId")] + #[serde(skip_serializing_if = "Option::is_none")] + thread_id: Option, + + #[serde(rename = "envelope")] + #[serde(skip_serializing_if = "Option::is_none")] + envelope: Option, + + #[serde(rename = "sendAt")] + #[serde(skip_serializing_if = "Option::is_none")] + send_at: Option>, + + #[serde(rename = "undoStatus")] + #[serde(skip_serializing_if = "Option::is_none")] + undo_status: Option, + + #[serde(rename = "deliveryStatus")] + #[serde(skip_serializing_if = "Option::is_none")] + delivery_status: Option>, + + #[serde(rename = "dsnBlobIds")] + #[serde(skip_serializing_if = "Option::is_none")] + dsn_blob_ids: Option>, + + #[serde(rename = "mdnBlobIds")] + #[serde(skip_serializing_if = "Option::is_none")] + mdn_blob_ids: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Envelope { + #[serde(rename = "mailFrom")] + mail_from: Address, + + #[serde(rename = "rcptTo")] + rcpt_to: Vec
, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Address { + #[serde(skip)] + _state: std::marker::PhantomData, + + email: String, + parameters: Option>>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum UndoStatus { + #[serde(rename = "pending")] + Pending, + #[serde(rename = "final")] + Final, + #[serde(rename = "canceled")] + Canceled, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeliveryStatus { + #[serde(rename = "smtpReply")] + smtp_reply: String, + + #[serde(rename = "delivered")] + delivered: Delivered, + + #[serde(rename = "displayed")] + displayed: Displayed, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Delivered { + #[serde(rename = "queued")] + Queued, + #[serde(rename = "yes")] + Yes, + #[serde(rename = "no")] + No, + #[serde(rename = "unknown")] + Unknown, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Displayed { + #[serde(rename = "unknown")] + Unknown, + #[serde(rename = "yes")] + Yes, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum EmailSubmissionProperty { + #[serde(rename = "id")] + Id, + #[serde(rename = "identityId")] + IdentityId, + #[serde(rename = "emailId")] + EmailId, + #[serde(rename = "threadId")] + ThreadId, + #[serde(rename = "envelope")] + Envelope, + #[serde(rename = "sendAt")] + SendAt, + #[serde(rename = "undoStatus")] + UndoStatus, + #[serde(rename = "deliveryStatus")] + DeliveryStatus, + #[serde(rename = "dsnBlobIds")] + DsnBlobIds, + #[serde(rename = "mdnBlobIds")] + MdnBlobIds, +} diff --git a/src/email_submission/set.rs b/src/email_submission/set.rs new file mode 100644 index 0000000..2dedb52 --- /dev/null +++ b/src/email_submission/set.rs @@ -0,0 +1,91 @@ +use std::collections::HashMap; + +use crate::Set; + +use super::{Address, EmailSubmission, Envelope, UndoStatus}; + +impl EmailSubmission { + pub fn identity_id(mut self, identity_id: String) -> Self { + self.identity_id = Some(identity_id); + self + } + + pub fn email_id(mut self, email_id: String) -> Self { + self.email_id = Some(email_id); + self + } + + pub fn envelope(mut self, mail_from: U, rcpt_to: T) -> Self + where + T: Iterator, + U: Into
, + { + self.envelope = Some(Envelope { + mail_from: mail_from.into(), + rcpt_to: rcpt_to.map(|s| s.into()).collect(), + }); + self + } + + pub fn undo_status(mut self, undo_status: UndoStatus) -> Self { + self.undo_status = Some(undo_status); + self + } +} + +impl EmailSubmission { + pub fn new() -> EmailSubmission { + EmailSubmission { + _state: Default::default(), + id: None, + identity_id: None, + email_id: None, + thread_id: None, + envelope: None, + send_at: None, + undo_status: None, + delivery_status: None, + dsn_blob_ids: None, + mdn_blob_ids: None, + } + } +} + +impl Address { + pub fn new(email: String) -> Address { + Address { + _state: Default::default(), + email, + parameters: None, + } + } +} + +impl Address { + pub fn parameter(mut self, parameter: String, value: Option) -> Self { + self.parameters + .get_or_insert_with(HashMap::new) + .insert(parameter, value); + self + } +} + +impl From for Address { + fn from(email: String) -> Self { + Address { + _state: Default::default(), + email, + parameters: None, + } + } +} + +impl From<&str> for Address { + fn from(email: &str) -> Self { + Address { + _state: Default::default(), + email: email.to_string(), + parameters: None, + } + } +} diff --git a/src/identity/get.rs b/src/identity/get.rs new file mode 100644 index 0000000..eb5f8b3 --- /dev/null +++ b/src/identity/get.rs @@ -0,0 +1,37 @@ +use crate::{email::EmailAddress, Get}; + +use super::Identity; + +impl Identity { + pub fn id(&self) -> &str { + self.id.as_ref().unwrap() + } + + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn email(&self) -> &str { + self.email.as_ref().unwrap() + } + + pub fn reply_to(&self) -> Option<&[EmailAddress]> { + self.reply_to.as_deref() + } + + pub fn bcc(&self) -> Option<&[EmailAddress]> { + self.bcc.as_deref() + } + + pub fn text_signature(&self) -> Option<&str> { + self.text_signature.as_deref() + } + + pub fn html_signature(&self) -> Option<&str> { + self.html_signature.as_deref() + } + + pub fn may_delete(&self) -> bool { + self.may_delete.unwrap_or(false) + } +} diff --git a/src/identity/mod.rs b/src/identity/mod.rs new file mode 100644 index 0000000..c197be1 --- /dev/null +++ b/src/identity/mod.rs @@ -0,0 +1,64 @@ +pub mod get; +pub mod set; + +use crate::core::set::list_not_set; +use crate::{email::EmailAddress, Get}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Identity { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "id")] + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(rename = "email")] + #[serde(skip_serializing_if = "Option::is_none")] + pub email: Option, + + #[serde(rename = "replyTo")] + #[serde(skip_serializing_if = "list_not_set")] + pub reply_to: Option>, + + #[serde(rename = "bcc")] + #[serde(skip_serializing_if = "list_not_set")] + pub bcc: Option>, + + #[serde(rename = "textSignature")] + #[serde(skip_serializing_if = "Option::is_none")] + pub text_signature: Option, + + #[serde(rename = "htmlSignature")] + #[serde(skip_serializing_if = "Option::is_none")] + pub html_signature: Option, + + #[serde(rename = "mayDelete")] + #[serde(skip_serializing_if = "Option::is_none")] + pub may_delete: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum IdentityProperty { + #[serde(rename = "id")] + Id, + #[serde(rename = "name")] + Name, + #[serde(rename = "email")] + Email, + #[serde(rename = "replyTo")] + ReplyTo, + #[serde(rename = "bcc")] + Bcc, + #[serde(rename = "textSignature")] + TextSignature, + #[serde(rename = "htmlSignature")] + HtmlSignature, + #[serde(rename = "mayDelete")] + MayDelete, +} diff --git a/src/identity/set.rs b/src/identity/set.rs new file mode 100644 index 0000000..f570b16 --- /dev/null +++ b/src/identity/set.rs @@ -0,0 +1,59 @@ +use crate::{email::EmailAddress, Set}; + +use super::Identity; + +impl Identity { + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn email(mut self, email: String) -> Self { + self.email = Some(email); + self + } + + pub fn bcc(mut self, bcc: Option) -> Self + where + T: Iterator, + U: Into, + { + self.bcc = bcc.map(|s| s.map(|s| s.into()).collect()); + self + } + + pub fn reply_to(mut self, reply_to: Option) -> Self + where + T: Iterator, + U: Into, + { + self.reply_to = reply_to.map(|s| s.map(|s| s.into()).collect()); + self + } + + pub fn text_signature(mut self, text_signature: String) -> Self { + self.text_signature = Some(text_signature); + self + } + + pub fn html_signature(mut self, html_signature: String) -> Self { + self.html_signature = Some(html_signature); + self + } +} + +impl Identity { + pub fn new() -> Identity { + Identity { + _state: Default::default(), + id: None, + name: None, + email: None, + reply_to: Vec::with_capacity(0).into(), + bcc: Vec::with_capacity(0).into(), + text_signature: None, + html_signature: None, + may_delete: None, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b917968..d174c89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,13 @@ use serde::{Deserialize, Serialize}; pub mod blob; pub mod core; +pub mod email; +pub mod email_submission; +pub mod identity; +pub mod mailbox; pub mod push_subscription; +pub mod thread; +pub mod vacation_response; #[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)] pub enum URI { @@ -94,6 +100,7 @@ pub enum Object { Mailbox, Thread, Email, + EmailDelivery, SearchSnippet, Identity, EmailSubmission, @@ -112,3 +119,8 @@ pub struct StateChange { pub type_: StateChangeType, pub changed: HashMap>, } + +#[derive(Debug, Clone)] +pub struct Get; +#[derive(Debug, Clone)] +pub struct Set; diff --git a/src/mailbox/get.rs b/src/mailbox/get.rs new file mode 100644 index 0000000..5a7c3e2 --- /dev/null +++ b/src/mailbox/get.rs @@ -0,0 +1,81 @@ +use crate::Get; + +use super::{Mailbox, Role}; + +impl Mailbox { + pub fn id(&self) -> &str { + self.id.as_ref().unwrap() + } + + pub fn name(&self) -> &str { + self.name.as_ref().unwrap() + } + + pub fn parent_id(&self) -> Option<&str> { + self.parent_id.as_deref() + } + + pub fn role(&self) -> Role { + self.role.as_ref().cloned().unwrap_or(Role::None) + } + + pub fn sort_order(&self) -> u32 { + self.sort_order.as_ref().copied().unwrap_or(0) + } + + pub fn total_emails(&self) -> usize { + self.total_emails.as_ref().copied().unwrap_or(0) + } + + pub fn unread_emails(&self) -> usize { + self.unread_emails.as_ref().copied().unwrap_or(0) + } + + pub fn total_threads(&self) -> usize { + self.total_threads.as_ref().copied().unwrap_or(0) + } + + pub fn unread_threads(&self) -> usize { + self.unread_threads.as_ref().copied().unwrap_or(0) + } + + pub fn is_subscribed(&self) -> bool { + *self.is_subscribed.as_ref().unwrap_or(&false) + } + + pub fn may_read_items(&self) -> bool { + self.my_rights.as_ref().unwrap().may_read_items + } + + pub fn may_add_items(&self) -> bool { + self.my_rights.as_ref().unwrap().may_add_items + } + + pub fn may_remove_items(&self) -> bool { + self.my_rights.as_ref().unwrap().may_remove_items + } + + pub fn may_set_seen(&self) -> bool { + self.my_rights.as_ref().unwrap().may_set_seen + } + + pub fn may_set_keywords(&self) -> bool { + self.my_rights.as_ref().unwrap().may_set_keywords + } + + pub fn may_create_child(&self) -> bool { + self.my_rights.as_ref().unwrap().may_create_child + } + + pub fn may_rename(&self) -> bool { + self.my_rights.as_ref().unwrap().may_rename + } + + pub fn may_delete(&self) -> bool { + self.my_rights.as_ref().unwrap().may_delete + } + + pub fn may_submit(&self) -> bool { + self.my_rights.as_ref().unwrap().may_submit + } +} diff --git a/src/mailbox/mod.rs b/src/mailbox/mod.rs new file mode 100644 index 0000000..24d5ab8 --- /dev/null +++ b/src/mailbox/mod.rs @@ -0,0 +1,126 @@ +pub mod get; +pub mod set; + +use crate::core::set::string_not_set; +use crate::mailbox::set::role_not_set; +use crate::Get; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Mailbox { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "id")] + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + name: Option, + + #[serde(rename = "parentId")] + #[serde(skip_serializing_if = "string_not_set")] + parent_id: Option, + + #[serde(rename = "role")] + #[serde(skip_serializing_if = "role_not_set")] + role: Option, + + #[serde(rename = "sortOrder")] + #[serde(skip_serializing_if = "Option::is_none")] + sort_order: Option, + + #[serde(rename = "totalEmails")] + #[serde(skip_serializing_if = "Option::is_none")] + total_emails: Option, + + #[serde(rename = "unreadEmails")] + #[serde(skip_serializing_if = "Option::is_none")] + unread_emails: Option, + + #[serde(rename = "totalThreads")] + #[serde(skip_serializing_if = "Option::is_none")] + total_threads: Option, + + #[serde(rename = "unreadThreads")] + #[serde(skip_serializing_if = "Option::is_none")] + unread_threads: Option, + + #[serde(rename = "myRights")] + #[serde(skip_serializing_if = "Option::is_none")] + my_rights: Option, + + #[serde(rename = "isSubscribed")] + #[serde(skip_serializing_if = "Option::is_none")] + is_subscribed: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Role { + Archive, + Drafts, + Important, + Inbox, + Junk, + Sent, + Trash, + None, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MailboxRights { + #[serde(rename = "mayReadItems")] + may_read_items: bool, + + #[serde(rename = "mayAddItems")] + may_add_items: bool, + + #[serde(rename = "mayRemoveItems")] + may_remove_items: bool, + + #[serde(rename = "maySetSeen")] + may_set_seen: bool, + + #[serde(rename = "maySetKeywords")] + may_set_keywords: bool, + + #[serde(rename = "mayCreateChild")] + may_create_child: bool, + + #[serde(rename = "mayRename")] + may_rename: bool, + + #[serde(rename = "mayDelete")] + may_delete: bool, + + #[serde(rename = "maySubmit")] + may_submit: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MailboxProperty { + #[serde(rename = "id")] + Id, + #[serde(rename = "name")] + Name, + #[serde(rename = "parentId")] + ParentId, + #[serde(rename = "role")] + Role, + #[serde(rename = "sortOrder")] + SortOrder, + #[serde(rename = "totalEmails")] + TotalEmails, + #[serde(rename = "unreadEmails")] + UnreadEmails, + #[serde(rename = "totalThreads")] + TotalThreads, + #[serde(rename = "unreadThreads")] + UnreadThreads, + #[serde(rename = "myRights")] + MyRights, + #[serde(rename = "isSubscribed")] + IsSubscribed, +} diff --git a/src/mailbox/set.rs b/src/mailbox/set.rs new file mode 100644 index 0000000..be8394c --- /dev/null +++ b/src/mailbox/set.rs @@ -0,0 +1,52 @@ +use crate::Set; + +use super::{Mailbox, Role}; + +impl Mailbox { + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn parent_id(mut self, parent_id: Option) -> Self { + self.parent_id = parent_id; + self + } + + pub fn role(mut self, role: Role) -> Self { + if !matches!(role, Role::None) { + self.role = Some(role); + } else { + self.role = None; + } + self + } + + pub fn sort_order(mut self, sort_order: u32) -> Self { + self.sort_order = sort_order.into(); + self + } +} + +pub fn role_not_set(role: &Option) -> bool { + matches!(role, Some(Role::None)) +} + +impl Mailbox { + pub fn new() -> Mailbox { + Mailbox { + _state: Default::default(), + id: None, + name: None, + parent_id: "".to_string().into(), + role: Role::None.into(), + sort_order: None, + total_emails: None, + unread_emails: None, + total_threads: None, + unread_threads: None, + my_rights: None, + is_subscribed: None, + } + } +} diff --git a/src/push_subscription/get.rs b/src/push_subscription/get.rs index e69de29..d3d39ac 100644 --- a/src/push_subscription/get.rs +++ b/src/push_subscription/get.rs @@ -0,0 +1,43 @@ +use crate::{Get, Object}; + +use super::{Keys, PushSubscription}; + +impl PushSubscription { + pub fn id(&self) -> &str { + self.id.as_ref().unwrap() + } + + pub fn device_client_id(&self) -> &str { + self.device_client_id.as_ref().unwrap() + } + + pub fn url(&self) -> &str { + self.url.as_ref().unwrap() + } + + pub fn keys(&self) -> Option<&Keys> { + self.keys.as_ref() + } + + pub fn verification_code(&self) -> Option<&str> { + self.verification_code.as_deref() + } + + pub fn expires(&self) -> Option { + self.expires.map(|v| v.timestamp()) + } + + pub fn types(&self) -> Option<&[Object]> { + self.types.as_deref() + } +} + +impl Keys { + pub fn p256dh(&self) -> Option> { + base64::decode_config(&self.p256dh, base64::URL_SAFE).ok() + } + + pub fn auth(&self) -> Option> { + base64::decode_config(&self.auth, base64::URL_SAFE).ok() + } +} diff --git a/src/push_subscription/mod.rs b/src/push_subscription/mod.rs index 1976b72..6b148c2 100644 --- a/src/push_subscription/mod.rs +++ b/src/push_subscription/mod.rs @@ -1,13 +1,17 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; - -use crate::Object; - pub mod get; pub mod set; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::core::set::list_not_set; +use crate::{Get, Object}; + #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PushSubscription { +pub struct PushSubscription { + #[serde(skip)] + _state: std::marker::PhantomData, + #[serde(rename = "id")] #[serde(skip_serializing_if = "Option::is_none")] id: Option, @@ -33,7 +37,7 @@ pub struct PushSubscription { expires: Option>, #[serde(rename = "types")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "list_not_set")] types: Option>, } diff --git a/src/push_subscription/set.rs b/src/push_subscription/set.rs index e69de29..0b896eb 100644 --- a/src/push_subscription/set.rs +++ b/src/push_subscription/set.rs @@ -0,0 +1,64 @@ +use crate::{core::set::from_timestamp, Object, Set}; + +use super::{Keys, PushSubscription}; + +impl PushSubscription { + pub fn device_client_id(mut self, device_client_id: String) -> Self { + self.device_client_id = Some(device_client_id); + self + } + + pub fn url(mut self, url: String) -> Self { + self.url = Some(url); + self + } + + pub fn verification_code(mut self, verification_code: String) -> Self { + self.verification_code = Some(verification_code); + self + } + + pub fn keys(mut self, keys: Keys) -> Self { + self.keys = Some(keys); + self + } + + pub fn expires(mut self, expires: i64) -> Self { + self.expires = Some(from_timestamp(expires)); + self + } + + pub fn types(mut self, types: Option>) -> Self { + self.types = types.map(|s| s.collect()); + self + } +} + +impl PushSubscription { + pub fn new() -> PushSubscription { + PushSubscription { + _state: Default::default(), + id: None, + device_client_id: None, + url: None, + keys: None, + verification_code: None, + expires: None, + types: Vec::with_capacity(0).into(), + } + } +} + +impl Keys { + pub fn new(p256dh: &[u8], auth: &[u8]) -> Self { + Keys { + p256dh: base64::encode_config(&p256dh, base64::URL_SAFE), + 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/thread/get.rs b/src/thread/get.rs new file mode 100644 index 0000000..8e1c627 --- /dev/null +++ b/src/thread/get.rs @@ -0,0 +1,11 @@ +use super::Thread; + +impl Thread { + pub fn id(&self) -> &str { + &self.id + } + + pub fn email_ids(&self) -> &[String] { + &self.email_ids + } +} diff --git a/src/thread/mod.rs b/src/thread/mod.rs new file mode 100644 index 0000000..162c420 --- /dev/null +++ b/src/thread/mod.rs @@ -0,0 +1,10 @@ +pub mod get; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Thread { + id: String, + #[serde(rename = "emailIds")] + email_ids: Vec, +} diff --git a/src/vacation_response/get.rs b/src/vacation_response/get.rs new file mode 100644 index 0000000..c49e4c6 --- /dev/null +++ b/src/vacation_response/get.rs @@ -0,0 +1,33 @@ +use crate::Get; + +use super::VacationResponse; + +impl VacationResponse { + pub fn id(&self) -> &str { + self.id.as_ref().unwrap() + } + + pub fn is_enabled(&self) -> bool { + self.is_enabled.unwrap_or(false) + } + + pub fn from_date(&self) -> Option { + self.from_date.as_ref().map(|dt| dt.timestamp()) + } + + pub fn to_date(&self) -> Option { + self.to_date.as_ref().map(|dt| dt.timestamp()) + } + + pub fn subject(&self) -> Option<&str> { + self.subject.as_deref() + } + + pub fn text_body(&self) -> Option<&str> { + self.text_body.as_deref() + } + + pub fn html_body(&self) -> Option<&str> { + self.html_body.as_deref() + } +} diff --git a/src/vacation_response/mod.rs b/src/vacation_response/mod.rs new file mode 100644 index 0000000..70aa76b --- /dev/null +++ b/src/vacation_response/mod.rs @@ -0,0 +1,42 @@ +pub mod get; +pub mod set; + +use crate::core::set::date_not_set; +use crate::core::set::string_not_set; +use crate::Get; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VacationResponse { + #[serde(skip)] + _state: std::marker::PhantomData, + + #[serde(rename = "id")] + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + + #[serde(rename = "isEnabled")] + #[serde(skip_serializing_if = "Option::is_none")] + is_enabled: Option, + + #[serde(rename = "fromDate")] + #[serde(skip_serializing_if = "date_not_set")] + from_date: Option>, + + #[serde(rename = "toDate")] + #[serde(skip_serializing_if = "date_not_set")] + to_date: Option>, + + #[serde(rename = "subject")] + #[serde(skip_serializing_if = "string_not_set")] + subject: Option, + + #[serde(rename = "textBody")] + #[serde(skip_serializing_if = "string_not_set")] + text_body: Option, + + #[serde(rename = "htmlBody")] + #[serde(skip_serializing_if = "string_not_set")] + html_body: Option, +} diff --git a/src/vacation_response/set.rs b/src/vacation_response/set.rs new file mode 100644 index 0000000..144bca2 --- /dev/null +++ b/src/vacation_response/set.rs @@ -0,0 +1,50 @@ +use crate::{core::set::from_timestamp, Set}; + +use super::VacationResponse; + +impl VacationResponse { + pub fn is_enabled(mut self, is_enabled: bool) -> Self { + self.is_enabled = Some(is_enabled); + self + } + + pub fn from_date(mut self, from_date: Option) -> Self { + self.from_date = from_date.map(from_timestamp); + self + } + + pub fn to_date(mut self, to_date: Option) -> Self { + self.to_date = to_date.map(from_timestamp); + self + } + + pub fn subject(mut self, subject: Option) -> Self { + self.subject = subject; + self + } + + pub fn text_body(mut self, text_body: Option) -> Self { + self.text_body = text_body; + self + } + + pub fn html_body(mut self, html_body: Option) -> Self { + self.html_body = html_body; + self + } +} + +impl VacationResponse { + pub fn new() -> VacationResponse { + VacationResponse { + _state: Default::default(), + id: None, + is_enabled: None, + from_date: from_timestamp(0).into(), + to_date: from_timestamp(0).into(), + subject: "".to_string().into(), + text_body: "".to_string().into(), + html_body: "".to_string().into(), + } + } +}