Complete implementation (untested) except SearchSnippet and EventSource.

main
Mauro D 2022-05-11 16:52:35 +00:00
parent 17cecbdb63
commit e10834ecaa
31 changed files with 1540 additions and 166 deletions

53
src/blob/download.rs Normal file
View File

@ -0,0 +1,53 @@
use std::time::Duration;
use reqwest::header::CONTENT_TYPE;
use crate::{client::Client, core::session::URLPart};
impl Client {
pub async fn download(&self, account_id: &str, blob_id: &str) -> crate::Result<Vec<u8>> {
let mut download_url = String::with_capacity(
self.session().download_url().len() + account_id.len() + blob_id.len(),
);
for part in self.download_url() {
match part {
URLPart::Value(value) => {
download_url.push_str(value);
}
URLPart::Parameter(param) => match param {
super::URLParameter::AccountId => {
download_url.push_str(account_id);
}
super::URLParameter::BlobId => {
download_url.push_str(blob_id);
}
super::URLParameter::Name => {
download_url.push_str("none");
}
super::URLParameter::Type => {
download_url.push_str("application/octet-stream");
}
},
}
}
let mut headers = self.headers().clone();
headers.remove(CONTENT_TYPE);
Client::handle_error(
reqwest::Client::builder()
.timeout(Duration::from_millis(self.timeout()))
.default_headers(headers)
.build()?
.get(download_url)
.send()
.await?,
)
.await?
.bytes()
.await
.map(|bytes| bytes.to_vec())
.map_err(|err| err.into())
}
}

View File

@ -1 +1,24 @@
use crate::core::session::URLParser;
pub mod copy;
pub mod download;
pub mod upload;
pub enum URLParameter {
AccountId,
BlobId,
Name,
Type,
}
impl URLParser for URLParameter {
fn parse(value: &str) -> Option<Self> {
match value {
"accountId" => Some(URLParameter::AccountId),
"blobId" => Some(URLParameter::BlobId),
"name" => Some(URLParameter::Name),
"type" => Some(URLParameter::Type),
_ => None,
}
}
}

85
src/blob/upload.rs Normal file
View File

@ -0,0 +1,85 @@
use std::time::Duration;
use reqwest::header::CONTENT_TYPE;
use serde::Deserialize;
use crate::{client::Client, core::session::URLPart};
#[derive(Debug, Deserialize)]
pub struct UploadResponse {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "blobId")]
blob_id: String,
#[serde(rename = "type")]
type_: String,
#[serde(rename = "size")]
size: usize,
}
impl Client {
pub async fn upload(
&self,
account_id: &str,
content_type: Option<&str>,
blob: Vec<u8>,
) -> crate::Result<UploadResponse> {
let mut upload_url =
String::with_capacity(self.session().upload_url().len() + account_id.len());
for part in self.upload_url() {
match part {
URLPart::Value(value) => {
upload_url.push_str(value);
}
URLPart::Parameter(param) => {
if let super::URLParameter::AccountId = param {
upload_url.push_str(account_id);
}
}
}
}
serde_json::from_slice::<UploadResponse>(
&Client::handle_error(
reqwest::Client::builder()
.timeout(Duration::from_millis(self.timeout()))
.default_headers(self.headers().clone())
.build()?
.post(upload_url)
.header(
CONTENT_TYPE,
content_type.unwrap_or("application/octet-stream"),
)
.body(blob)
.send()
.await?,
)
.await?
.bytes()
.await?,
)
.map_err(|err| err.into())
}
}
impl UploadResponse {
pub fn account_id(&self) -> &str {
&self.account_id
}
pub fn blob_id(&self) -> &str {
&self.blob_id
}
pub fn content_type(&self) -> &str {
&self.type_
}
pub fn size(&self) -> usize {
self.size
}
}

View File

@ -1,36 +1,226 @@
use std::time::Duration;
use crate::core::request::Request;
use reqwest::{
header::{self},
Response,
};
use crate::{
blob,
core::{
request::Request,
session::{Session, URLPart},
},
event_source, Error,
};
const DEFAULT_TIMEOUT_MS: u64 = 10 * 1000;
static USER_AGENT: &str = concat!("stalwart-jmap/", env!("CARGO_PKG_VERSION"));
pub struct Client {
client: reqwest::ClientBuilder,
session: Session,
session_url: String,
upload_url: Vec<URLPart<blob::URLParameter>>,
download_url: Vec<URLPart<blob::URLParameter>>,
event_source_url: Vec<URLPart<event_source::URLParameter>>,
timeout: u64,
headers: header::HeaderMap,
default_account_id: String,
}
impl Client {
pub fn connect(url: &str) -> Self {
Client {
client: reqwest::Client::builder().timeout(Duration::from_millis(DEFAULT_TIMEOUT_MS)),
default_account_id: "co".to_string(),
pub async fn connect(url: &str) -> crate::Result<Self> {
let mut headers = header::HeaderMap::new();
headers.insert(
header::USER_AGENT,
header::HeaderValue::from_static(USER_AGENT),
);
headers.insert(
header::AUTHORIZATION,
header::HeaderValue::from_static("Basic test"),
);
let session: Session = serde_json::from_slice(
&Client::handle_error(
reqwest::Client::builder()
.timeout(Duration::from_millis(DEFAULT_TIMEOUT_MS))
.default_headers(headers.clone())
.build()?
.get(url)
.send()
.await?,
)
.await?
.bytes()
.await?,
)?;
let default_account_id = session
.primary_accounts()
.next()
.map(|a| a.0.to_string())
.unwrap_or_default();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
);
Ok(Client {
download_url: URLPart::parse(session.download_url())?,
upload_url: URLPart::parse(session.upload_url())?,
event_source_url: URLPart::parse(session.event_source_url())?,
session,
session_url: url.to_string(),
timeout: DEFAULT_TIMEOUT_MS,
headers,
default_account_id,
})
}
pub fn set_timeout(&mut self, timeout: u64) {
self.timeout = timeout;
}
pub fn timeout(&self) -> u64 {
self.timeout
}
pub fn session(&self) -> &Session {
&self.session
}
pub fn headers(&self) -> &header::HeaderMap {
&self.headers
}
pub async fn update_session(&mut self, new_state: &str) -> crate::Result<()> {
if new_state != self.session.state() {
let session: Session = serde_json::from_slice(
&Client::handle_error(
reqwest::Client::builder()
.timeout(Duration::from_millis(DEFAULT_TIMEOUT_MS))
.default_headers(self.headers.clone())
.build()?
.get(&self.session_url)
.send()
.await?,
)
.await?
.bytes()
.await?,
)?;
self.download_url = URLPart::parse(session.download_url())?;
self.upload_url = URLPart::parse(session.upload_url())?;
self.event_source_url = URLPart::parse(session.event_source_url())?;
self.session = session;
}
Ok(())
}
pub fn set_default_account_id(&mut self, defaul_account_id: impl Into<String>) {
self.default_account_id = defaul_account_id.into();
}
pub fn default_account_id(&self) -> &str {
&self.default_account_id
}
pub fn request(&self) -> Request<'_> {
pub fn request(&mut self) -> Request<'_> {
Request::new(self)
}
pub fn download_url(&self) -> &[URLPart<blob::URLParameter>] {
&self.download_url
}
pub fn upload_url(&self) -> &[URLPart<blob::URLParameter>] {
&self.upload_url
}
pub fn event_source_url(&self) -> &[URLPart<event_source::URLParameter>] {
&self.event_source_url
}
pub async fn handle_error(response: Response) -> crate::Result<Response> {
if response.status().is_success() {
Ok(response)
} else if let Some(b"application/problem+json") = response
.headers()
.get(header::CONTENT_TYPE)
.map(|h| h.as_bytes())
{
Err(Error::Problem(serde_json::from_slice(
&response.bytes().await?,
)?))
} else {
Err(Error::ServerError(format!("{}", response.status())))
}
}
}
#[cfg(test)]
mod tests {
use crate::core::response::Response;
#[test]
fn test_serialize() {
let r: Response = serde_json::from_slice(
br#"{"sessionState": "123", "methodResponses": [[ "Email/query", {
"accountId": "A1",
"queryState": "abcdefg",
"canCalculateChanges": true,
"position": 0,
"total": 101,
"ids": [ "msg1023", "msg223", "msg110", "msg93", "msg91",
"msg38", "msg36", "msg33", "msg11", "msg1" ]
}, "t0" ],
[ "Email/get", {
"accountId": "A1",
"state": "123456",
"list": [{
"id": "msg1023",
"threadId": "trd194"
}, {
"id": "msg223",
"threadId": "trd114"
}
],
"notFound": []
}, "t1" ],
[ "Thread/get", {
"accountId": "A1",
"state": "123456",
"list": [{
"id": "trd194",
"emailIds": [ "msg1020", "msg1021", "msg1023" ]
}, {
"id": "trd114",
"emailIds": [ "msg201", "msg223" ]
}
],
"notFound": []
}, "t2" ]]}"#,
)
.unwrap();
println!("{:?}", r);
/*let mut client = Client::connect("coco");
let mut request = client.request();
let set = request.set_email();
set.create().from(["pepe"]).subject("coco");
set.update("id").keyword("keyword", true);
set.destroy(["1", "2"]);
let ref_ = request.result_reference("/pepe/1");
let get = request.get_email();
get.ids_ref(ref_);
println!("{}", serde_json::to_string_pretty(&request).unwrap());*/
/*let mut client = Client::connect("coco");
client.request().email_set().create(

View File

@ -4,9 +4,12 @@ use serde::{Deserialize, Serialize};
pub struct ChangesRequest {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "sinceState")]
since_state: String,
#[serde(rename = "maxChanges")]
#[serde(skip_serializing_if = "Option::is_none")]
max_changes: Option<usize>,
}
@ -14,14 +17,20 @@ pub struct ChangesRequest {
pub struct ChangesResponse<A> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: String,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "hasMoreChanges")]
has_more_changes: bool,
created: Vec<String>,
updated: Vec<String>,
destroyed: Vec<String>,
#[serde(flatten)]

View File

@ -2,20 +2,22 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::set::SetError;
use super::set::{Create, SetError};
#[derive(Debug, Clone, Serialize)]
pub struct CopyRequest<T> {
pub struct CopyRequest<T: Create> {
#[serde(rename = "fromAccountId")]
from_account_id: String,
#[serde(rename = "ifFromInState")]
#[serde(skip_serializing_if = "Option::is_none")]
if_from_in_state: Option<String>,
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "ifInState")]
#[serde(skip_serializing_if = "Option::is_none")]
if_in_state: Option<String>,
#[serde(rename = "create")]
@ -25,6 +27,7 @@ pub struct CopyRequest<T> {
on_success_destroy_original: bool,
#[serde(rename = "destroyFromIfInState")]
#[serde(skip_serializing_if = "Option::is_none")]
destroy_from_if_in_state: Option<String>,
}
@ -32,19 +35,24 @@ pub struct CopyRequest<T> {
pub struct CopyResponse<T, U> {
#[serde(rename = "fromAccountId")]
from_account_id: String,
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: Option<String>,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "created")]
created: Option<HashMap<String, T>>,
#[serde(rename = "notCreated")]
not_created: Option<HashMap<String, SetError<U>>>,
}
impl<T> CopyRequest<T> {
impl<T: Create> CopyRequest<T> {
pub fn new(from_account_id: String, account_id: String) -> Self {
CopyRequest {
from_account_id,
@ -72,9 +80,12 @@ impl<T> CopyRequest<T> {
self
}
pub fn create(&mut self, id: impl Into<String>, value: T) -> &mut Self {
self.create.insert(id.into(), value);
self
pub fn create(&mut self) -> &mut T {
let create_id = self.create.len();
let create_id_str = format!("c{}", create_id);
self.create
.insert(create_id_str.clone(), T::new(create_id.into()));
self.create.get_mut(&create_id_str).unwrap()
}
pub fn on_success_destroy_original(&mut self, on_success_destroy_original: bool) -> &mut Self {

View File

@ -1,3 +1,5 @@
use std::fmt::Display;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
@ -99,3 +101,28 @@ impl MethodError {
&self.p_type
}
}
impl Display for ProblemDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.p_type {
ProblemType::UnknownCapability => write!(f, "Unknown capability")?,
ProblemType::NotJSON => write!(f, "Not JSON")?,
ProblemType::NotRequest => write!(f, "Not request")?,
ProblemType::Limit => write!(f, "Limit")?,
}
if let Some(status) = self.status {
write!(f, " (status {})", status)?
}
if let Some(title) = &self.title {
write!(f, ": {}", title)?
}
if let Some(detail) = &self.detail {
write!(f, ". Details: {}", detail)?
}
Ok(())
}
}

View File

@ -1,10 +1,21 @@
use serde::{Deserialize, Serialize};
use super::request::ResultReference;
#[derive(Debug, Clone, Serialize)]
pub struct GetRequest<T, A: Default> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
ids: Option<Vec<String>>,
#[serde(rename = "#ids")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "Option::is_none")]
ids_ref: Option<ResultReference>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<Vec<T>>,
#[serde(flatten)]
@ -15,8 +26,11 @@ pub struct GetRequest<T, A: Default> {
pub struct GetResponse<T> {
#[serde(rename = "accountId")]
account_id: String,
state: String,
list: Vec<T>,
#[serde(rename = "notFound")]
not_found: Vec<String>,
}
@ -26,6 +40,7 @@ impl<T, A: Default> GetRequest<T, A> {
GetRequest {
account_id,
ids: None,
ids_ref: None,
properties: None,
arguments: A::default(),
}
@ -42,6 +57,13 @@ impl<T, A: Default> GetRequest<T, A> {
V: Into<String>,
{
self.ids = Some(ids.into_iter().map(|v| v.into()).collect());
self.ids_ref = None;
self
}
pub fn ids_ref(&mut self, reference: ResultReference) -> &mut Self {
self.ids_ref = reference.into();
self.ids = None;
self
}

View File

@ -6,21 +6,25 @@ pub struct QueryRequest<F, S, A: Default> {
account_id: String,
#[serde(rename = "filter")]
#[serde(skip_serializing_if = "Option::is_none")]
filter: Option<Filter<F>>,
#[serde(rename = "sort")]
#[serde(skip_serializing_if = "Option::is_none")]
sort: Option<Vec<Comparator<S>>>,
#[serde(rename = "position")]
position: i32,
#[serde(rename = "anchor")]
#[serde(skip_serializing_if = "Option::is_none")]
anchor: Option<String>,
#[serde(rename = "anchorOffset")]
anchor_offset: i32,
#[serde(rename = "limit")]
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<usize>,
#[serde(rename = "calculateTotal")]

View File

@ -6,16 +6,26 @@ use super::query::{Comparator, Filter};
pub struct QueryChangesRequest<F, S, A: Default> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "filter")]
#[serde(skip_serializing_if = "Option::is_none")]
filter: Option<Filter<F>>,
#[serde(rename = "sort")]
#[serde(skip_serializing_if = "Option::is_none")]
sort: Option<Vec<Comparator<S>>>,
#[serde(rename = "sinceQueryState")]
since_query_state: String,
#[serde(rename = "maxChanges")]
#[serde(skip_serializing_if = "Option::is_none")]
max_changes: Option<usize>,
#[serde(rename = "upToId")]
#[serde(skip_serializing_if = "Option::is_none")]
up_to_id: Option<String>,
#[serde(rename = "calculateTotal")]
calculate_total: bool,

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, time::Duration};
use serde::Serialize;
@ -17,17 +17,19 @@ use crate::{
use super::{
changes::ChangesRequest, copy::CopyRequest, get::GetRequest, query::QueryRequest,
query_changes::QueryChangesRequest, set::SetRequest,
query_changes::QueryChangesRequest, response::Response, set::SetRequest,
};
#[derive(Serialize)]
pub struct Request<'x> {
#[serde(skip)]
client: &'x Client,
client: &'x mut Client,
using: Vec<URI>,
#[serde(rename = "methodCalls")]
method_calls: Vec<(Method, Arguments, String)>,
#[serde(rename = "createdIds")]
#[serde(skip_serializing_if = "Option::is_none")]
created_ids: Option<HashMap<String, String>>,
@ -37,7 +39,7 @@ pub struct Request<'x> {
pub struct ResultReference {
#[serde(rename = "resultOf")]
result_of: String,
name: String,
name: Method,
path: String,
}
@ -392,7 +394,7 @@ impl Arguments {
}
impl<'x> Request<'x> {
pub fn new(client: &'x Client) -> Self {
pub fn new(client: &'x mut Client) -> Self {
Request {
using: vec![URI::Core, URI::Mail],
method_calls: vec![],
@ -401,6 +403,26 @@ impl<'x> Request<'x> {
}
}
pub async fn send(&mut self) -> crate::Result<Response> {
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)
}
fn add_method_call(&mut self, method: Method, arguments: Arguments) -> &mut Arguments {
let call_id = format!("s{}", self.method_calls.len());
self.method_calls.push((method, arguments, call_id));
@ -613,6 +635,9 @@ impl<'x> Request<'x> {
}
pub fn get_email_submission(&mut self) -> &mut GetRequest<email_submission::Property, ()> {
if !self.using.contains(&URI::Submission) {
self.using.push(URI::Submission);
}
self.add_method_call(
Method::GetEmailSubmission,
Arguments::email_submission_get(self.client.default_account_id().to_string()),
@ -624,6 +649,9 @@ impl<'x> Request<'x> {
&mut self,
since_state: impl Into<String>,
) -> &mut ChangesRequest {
if !self.using.contains(&URI::Submission) {
self.using.push(URI::Submission);
}
self.add_method_call(
Method::ChangesEmailSubmission,
Arguments::changes(
@ -638,6 +666,9 @@ impl<'x> Request<'x> {
&mut self,
) -> &mut QueryRequest<email_submission::query::Filter, email_submission::query::Comparator, ()>
{
if !self.using.contains(&URI::Submission) {
self.using.push(URI::Submission);
}
self.add_method_call(
Method::QueryEmailSubmission,
Arguments::email_submission_query(self.client.default_account_id().to_string()),
@ -653,6 +684,9 @@ impl<'x> Request<'x> {
email_submission::query::Comparator,
(),
> {
if !self.using.contains(&URI::Submission) {
self.using.push(URI::Submission);
}
self.add_method_call(
Method::QueryChangesEmailSubmission,
Arguments::email_submission_query_changes(
@ -666,6 +700,9 @@ impl<'x> Request<'x> {
pub fn set_email_submission(
&mut self,
) -> &mut SetRequest<EmailSubmission<Set>, email_submission::SetArguments> {
if !self.using.contains(&URI::Submission) {
self.using.push(URI::Submission);
}
self.add_method_call(
Method::SetEmailSubmission,
Arguments::email_submission_set(self.client.default_account_id().to_string()),
@ -674,6 +711,9 @@ impl<'x> Request<'x> {
}
pub fn get_vacation_response(&mut self) -> &mut GetRequest<vacation_response::Property, ()> {
if !self.using.contains(&URI::VacationResponse) {
self.using.push(URI::VacationResponse);
}
self.add_method_call(
Method::GetVacationResponse,
Arguments::vacation_response_get(self.client.default_account_id().to_string()),
@ -682,10 +722,22 @@ impl<'x> Request<'x> {
}
pub fn set_vacation_response(&mut self) -> &mut SetRequest<VacationResponse<Set>, ()> {
if !self.using.contains(&URI::VacationResponse) {
self.using.push(URI::VacationResponse);
}
self.add_method_call(
Method::SetVacationResponse,
Arguments::vacation_response_set(self.client.default_account_id().to_string()),
)
.vacation_response_set_mut()
}
pub fn result_reference(&self, path: impl Into<String>) -> ResultReference {
let last_method = self.method_calls.last().unwrap();
ResultReference {
result_of: last_method.2.clone(),
name: last_method.0.clone(),
path: path.into(),
}
}
}

View File

@ -2,17 +2,635 @@ use std::collections::HashMap;
use serde::Deserialize;
use crate::Method;
use crate::{
blob::copy::CopyBlobResponse,
email::{self, import::EmailImportResponse, parse::EmailParseResponse, Email},
email_submission::{self, EmailSubmission},
identity::{self, Identity},
mailbox::{self, Mailbox},
push_subscription::{self, PushSubscription},
thread::Thread,
vacation_response::{self, VacationResponse},
Get, Method,
};
#[derive(Debug, Clone, Deserialize)]
use super::{
changes::ChangesResponse, copy::CopyResponse, error::MethodError, get::GetResponse,
query::QueryResponse, query_changes::QueryChangesResponse, set::SetResponse,
};
#[derive(Debug, Deserialize)]
pub struct Response {
#[serde(rename = "methodResponses")]
method_calls: Vec<(Method, Result, String)>,
method_responses: Vec<MethodResponse>,
#[serde(rename = "createdIds")]
created_ids: Option<HashMap<String, String>>,
#[serde(rename = "sessionState")]
session_state: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Result {}
impl Response {
pub fn method_responses(&self) -> &[MethodResponse] {
self.method_responses.as_ref()
}
pub fn method_response(&self, id: &str) -> Option<&MethodResponse> {
self.method_responses
.iter()
.find(|response| response.call_id() == id)
}
pub fn created_ids(&self) -> Option<impl Iterator<Item = (&String, &String)>> {
self.created_ids.as_ref().map(|map| map.iter())
}
pub fn session_state(&self) -> &str {
&self.session_state
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum MethodResponse {
CopyBlob((CopyBlob, CopyBlobResponse, String)),
GetPushSubscription(
(
GetPushSubscription,
GetResponse<PushSubscription<Get>>,
String,
),
),
SetPushSubscription(
(
SetPushSubscription,
SetResponse<PushSubscription<Get>, push_subscription::Property>,
String,
),
),
GetMailbox((GetMailbox, GetResponse<Mailbox<Get>>, String)),
ChangesMailbox(
(
ChangesMailbox,
ChangesResponse<mailbox::ChangesResponse>,
String,
),
),
QueryMailbox((QueryMailbox, QueryResponse, String)),
QueryChangesMailbox((QueryChangesMailbox, QueryChangesResponse, String)),
SetMailbox(
(
SetMailbox,
SetResponse<Mailbox<Get>, mailbox::Property>,
String,
),
),
GetThread((GetThread, GetResponse<Thread>, String)),
ChangesThread((ChangesThread, ChangesResponse<()>, String)),
GetEmail((GetEmail, GetResponse<Email<Get>>, String)),
ChangesEmail((ChangesEmail, ChangesResponse<()>, String)),
QueryEmail((QueryEmail, QueryResponse, String)),
QueryChangesEmail((QueryChangesEmail, QueryChangesResponse, String)),
SetEmail((SetEmail, SetResponse<Email<Get>, email::Property>, String)),
CopyEmail((CopyEmail, CopyResponse<Email<Get>, email::Property>, String)),
ImportEmail((ImportEmail, EmailImportResponse, String)),
ParseEmail((ParseEmail, EmailParseResponse, String)),
GetSearchSnippet((GetSearchSnippet, GetResponse<String>, String)),
GetIdentity((GetIdentity, GetResponse<Identity<Get>>, String)),
ChangesIdentity((ChangesIdentity, ChangesResponse<()>, String)),
SetIdentity(
(
SetIdentity,
SetResponse<Identity<Get>, identity::Property>,
String,
),
),
GetEmailSubmission(
(
GetEmailSubmission,
GetResponse<EmailSubmission<Get>>,
String,
),
),
ChangesEmailSubmission((ChangesEmailSubmission, ChangesResponse<()>, String)),
QueryEmailSubmission((QueryEmailSubmission, QueryResponse, String)),
QueryChangesEmailSubmission((QueryChangesEmailSubmission, QueryChangesResponse, String)),
SetEmailSubmission(
(
SetEmailSubmission,
SetResponse<EmailSubmission<Get>, email_submission::Property>,
String,
),
),
GetVacationResponse(
(
GetVacationResponse,
GetResponse<VacationResponse<Get>>,
String,
),
),
SetVacationResponse(
(
SetVacationResponse,
SetResponse<VacationResponse<Get>, vacation_response::Property>,
String,
),
),
Echo((Echo, serde_json::Value, String)),
Error((Error, MethodError, String)),
}
impl MethodResponse {
pub fn call_id(&self) -> &str {
match self {
Self::CopyBlob((_, _, id)) => id,
Self::GetPushSubscription((_, _, id)) => id,
Self::SetPushSubscription((_, _, id)) => id,
Self::GetMailbox((_, _, id)) => id,
Self::ChangesMailbox((_, _, id)) => id,
Self::QueryMailbox((_, _, id)) => id,
Self::QueryChangesMailbox((_, _, id)) => id,
Self::SetMailbox((_, _, id)) => id,
Self::GetThread((_, _, id)) => id,
Self::ChangesThread((_, _, id)) => id,
Self::GetEmail((_, _, id)) => id,
Self::ChangesEmail((_, _, id)) => id,
Self::QueryEmail((_, _, id)) => id,
Self::QueryChangesEmail((_, _, id)) => id,
Self::SetEmail((_, _, id)) => id,
Self::CopyEmail((_, _, id)) => id,
Self::ImportEmail((_, _, id)) => id,
Self::ParseEmail((_, _, id)) => id,
Self::GetSearchSnippet((_, _, id)) => id,
Self::GetIdentity((_, _, id)) => id,
Self::ChangesIdentity((_, _, id)) => id,
Self::SetIdentity((_, _, id)) => id,
Self::GetEmailSubmission((_, _, id)) => id,
Self::ChangesEmailSubmission((_, _, id)) => id,
Self::QueryEmailSubmission((_, _, id)) => id,
Self::QueryChangesEmailSubmission((_, _, id)) => id,
Self::SetEmailSubmission((_, _, id)) => id,
Self::GetVacationResponse((_, _, id)) => id,
Self::SetVacationResponse((_, _, id)) => id,
Self::Echo((_, _, id)) => id,
Self::Error((_, _, id)) => id,
}
}
pub fn is_type(&self, type_: Method) -> bool {
matches!(
(self, type_),
(Self::CopyBlob(_), Method::CopyBlob)
| (Self::GetPushSubscription(_), Method::GetPushSubscription)
| (Self::SetPushSubscription(_), Method::SetPushSubscription)
| (Self::GetMailbox(_), Method::GetMailbox)
| (Self::ChangesMailbox(_), Method::ChangesMailbox)
| (Self::QueryMailbox(_), Method::QueryMailbox)
| (Self::QueryChangesMailbox(_), Method::QueryChangesMailbox)
| (Self::SetMailbox(_), Method::SetMailbox)
| (Self::GetThread(_), Method::GetThread)
| (Self::ChangesThread(_), Method::ChangesThread)
| (Self::GetEmail(_), Method::GetEmail)
| (Self::ChangesEmail(_), Method::ChangesEmail)
| (Self::QueryEmail(_), Method::QueryEmail)
| (Self::QueryChangesEmail(_), Method::QueryChangesEmail)
| (Self::SetEmail(_), Method::SetEmail)
| (Self::CopyEmail(_), Method::CopyEmail)
| (Self::ImportEmail(_), Method::ImportEmail)
| (Self::ParseEmail(_), Method::ParseEmail)
| (Self::GetSearchSnippet(_), Method::GetSearchSnippet)
| (Self::GetIdentity(_), Method::GetIdentity)
| (Self::ChangesIdentity(_), Method::ChangesIdentity)
| (Self::SetIdentity(_), Method::SetIdentity)
| (Self::GetEmailSubmission(_), Method::GetEmailSubmission)
| (
Self::ChangesEmailSubmission(_),
Method::ChangesEmailSubmission
)
| (Self::QueryEmailSubmission(_), Method::QueryEmailSubmission)
| (
Self::QueryChangesEmailSubmission(_),
Method::QueryChangesEmailSubmission
)
| (Self::SetEmailSubmission(_), Method::SetEmailSubmission)
| (Self::GetVacationResponse(_), Method::GetVacationResponse)
| (Self::SetVacationResponse(_), Method::SetVacationResponse)
| (Self::Echo(_), Method::Echo)
| (Self::Error(_), Method::Error)
)
}
pub fn as_copy_copy(&self) -> Option<&CopyBlobResponse> {
match self {
Self::CopyBlob((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_push_subscription(&self) -> Option<&GetResponse<PushSubscription<Get>>> {
match self {
Self::GetPushSubscription((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_set_push_subscription(
&self,
) -> Option<&SetResponse<PushSubscription<Get>, push_subscription::Property>> {
match self {
Self::SetPushSubscription((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_mailbox(&self) -> Option<&GetResponse<Mailbox<Get>>> {
match self {
Self::GetMailbox((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_changes_mailbox(&self) -> Option<&ChangesResponse<mailbox::ChangesResponse>> {
match self {
Self::ChangesMailbox((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_query_mailbox(&self) -> Option<&QueryResponse> {
match self {
Self::QueryMailbox((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_query_changes_mailbox(&self) -> Option<&QueryChangesResponse> {
match self {
Self::QueryChangesMailbox((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_set_mailbox(&self) -> Option<&SetResponse<Mailbox<Get>, mailbox::Property>> {
match self {
Self::SetMailbox((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_thread(&self) -> Option<&GetResponse<Thread>> {
match self {
Self::GetThread((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_changes_thread(&self) -> Option<&ChangesResponse<()>> {
match self {
Self::ChangesThread((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_email(&self) -> Option<&GetResponse<Email>> {
match self {
Self::GetEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_changes_email(&self) -> Option<&ChangesResponse<()>> {
match self {
Self::ChangesEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_query_email(&self) -> Option<&QueryResponse> {
match self {
Self::QueryEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_query_changes_email(&self) -> Option<&QueryChangesResponse> {
match self {
Self::QueryChangesEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_set_email(&self) -> Option<&SetResponse<Email, email::Property>> {
match self {
Self::SetEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_copy_email(&self) -> Option<&CopyResponse<Email<Get>, email::Property>> {
match self {
Self::CopyEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_import_email(&self) -> Option<&EmailImportResponse> {
match self {
Self::ImportEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_parse_email(&self) -> Option<&EmailParseResponse> {
match self {
Self::ParseEmail((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_search_snippet(&self) -> Option<&GetResponse<String>> {
match self {
Self::GetSearchSnippet((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_identity(&self) -> Option<&GetResponse<Identity>> {
match self {
Self::GetIdentity((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_changes_identity(&self) -> Option<&ChangesResponse<()>> {
match self {
Self::ChangesIdentity((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_set_identity(&self) -> Option<&SetResponse<Identity, identity::Property>> {
match self {
Self::SetIdentity((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_email_submission(&self) -> Option<&GetResponse<EmailSubmission>> {
match self {
Self::GetEmailSubmission((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_changes_email_submission(&self) -> Option<&ChangesResponse<()>> {
match self {
Self::ChangesEmailSubmission((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_set_email_submission(
&self,
) -> Option<&SetResponse<EmailSubmission, email_submission::Property>> {
match self {
Self::SetEmailSubmission((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_query_email_submission(&self) -> Option<&QueryResponse> {
match self {
Self::QueryEmailSubmission((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_query_changes_email_submission(&self) -> Option<&QueryChangesResponse> {
match self {
Self::QueryChangesEmailSubmission((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_get_vacation_response(&self) -> Option<&GetResponse<VacationResponse>> {
match self {
Self::GetVacationResponse((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_set_vacation_response(
&self,
) -> Option<&SetResponse<VacationResponse, vacation_response::Property>> {
match self {
Self::SetVacationResponse((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_echo(&self) -> Option<&serde_json::Value> {
match self {
Self::Echo((_, response, _)) => response.into(),
_ => None,
}
}
pub fn as_error(&self) -> Option<&MethodError> {
match self {
Self::Error((_, response, _)) => response.into(),
_ => None,
}
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error(_))
}
}
#[derive(Debug, Deserialize)]
pub enum Echo {
#[serde(rename = "Core/echo")]
V,
}
#[derive(Debug, Deserialize)]
pub enum CopyBlob {
#[serde(rename = "Blob/copy")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetPushSubscription {
#[serde(rename = "PushSubscription/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum SetPushSubscription {
#[serde(rename = "PushSubscription/set")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetMailbox {
#[serde(rename = "Mailbox/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ChangesMailbox {
#[serde(rename = "Mailbox/changes")]
V,
}
#[derive(Debug, Deserialize)]
pub enum QueryMailbox {
#[serde(rename = "Mailbox/query")]
V,
}
#[derive(Debug, Deserialize)]
pub enum QueryChangesMailbox {
#[serde(rename = "Mailbox/queryChanges")]
V,
}
#[derive(Debug, Deserialize)]
pub enum SetMailbox {
#[serde(rename = "Mailbox/set")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetThread {
#[serde(rename = "Thread/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ChangesThread {
#[serde(rename = "Thread/changes")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetEmail {
#[serde(rename = "Email/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ChangesEmail {
#[serde(rename = "Email/changes")]
V,
}
#[derive(Debug, Deserialize)]
pub enum QueryEmail {
#[serde(rename = "Email/query")]
V,
}
#[derive(Debug, Deserialize)]
pub enum QueryChangesEmail {
#[serde(rename = "Email/queryChanges")]
V,
}
#[derive(Debug, Deserialize)]
pub enum SetEmail {
#[serde(rename = "Email/set")]
V,
}
#[derive(Debug, Deserialize)]
pub enum CopyEmail {
#[serde(rename = "Email/copy")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ImportEmail {
#[serde(rename = "Email/import")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ParseEmail {
#[serde(rename = "Email/parse")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetSearchSnippet {
#[serde(rename = "SearchSnippet/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetIdentity {
#[serde(rename = "Identity/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ChangesIdentity {
#[serde(rename = "Identity/changes")]
V,
}
#[derive(Debug, Deserialize)]
pub enum SetIdentity {
#[serde(rename = "Identity/set")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetEmailSubmission {
#[serde(rename = "EmailSubmission/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum ChangesEmailSubmission {
#[serde(rename = "EmailSubmission/changes")]
V,
}
#[derive(Debug, Deserialize)]
pub enum QueryEmailSubmission {
#[serde(rename = "EmailSubmission/query")]
V,
}
#[derive(Debug, Deserialize)]
pub enum QueryChangesEmailSubmission {
#[serde(rename = "EmailSubmission/queryChanges")]
V,
}
#[derive(Debug, Deserialize)]
pub enum SetEmailSubmission {
#[serde(rename = "EmailSubmission/set")]
V,
}
#[derive(Debug, Deserialize)]
pub enum GetVacationResponse {
#[serde(rename = "VacationResponse/get")]
V,
}
#[derive(Debug, Deserialize)]
pub enum SetVacationResponse {
#[serde(rename = "VacationResponse/set")]
V,
}
#[derive(Debug, Deserialize)]
pub enum Error {
#[serde(rename = "error")]
V,
}

View File

@ -106,12 +106,8 @@ impl Session {
self.accounts.get(account)
}
pub fn primary_accounts(&self) -> impl Iterator<Item = &String> {
self.primary_accounts.keys()
}
pub fn primary_account(&self, account: &str) -> Option<&String> {
self.primary_accounts.get(account)
pub fn primary_accounts(&self) -> impl Iterator<Item = (&String, &String)> {
self.primary_accounts.iter()
}
pub fn username(&self) -> &str {
@ -194,3 +190,51 @@ impl CoreCapabilities {
&self.collation_algorithms
}
}
pub trait URLParser: Sized {
fn parse(value: &str) -> Option<Self>;
}
pub enum URLPart<T: URLParser> {
Value(String),
Parameter(T),
}
impl<T: URLParser> URLPart<T> {
pub fn parse(url: &str) -> crate::Result<Vec<URLPart<T>>> {
let mut parts = Vec::new();
let mut buf = String::with_capacity(url.len());
let mut in_parameter = false;
for ch in url.chars() {
match ch {
'{' => {
if !buf.is_empty() {
parts.push(URLPart::Value(buf.clone()));
buf.clear();
}
in_parameter = true;
}
'}' => {
if in_parameter && !buf.is_empty() {
parts.push(URLPart::Parameter(T::parse(&buf).ok_or_else(|| {
crate::Error::Internal(format!(
"Invalid parameter '{}' in URL: {}",
buf, url
))
})?));
buf.clear();
} else {
return Err(crate::Error::Internal(format!("Invalid URL: {}", url)));
}
in_parameter = false;
}
_ => {
buf.push(ch);
}
}
}
Ok(parts)
}
}

View File

@ -2,16 +2,31 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::request::ResultReference;
#[derive(Debug, Clone, Serialize)]
pub struct SetRequest<T, A: Default> {
pub struct SetRequest<T: Create, A: Default> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "ifInState")]
#[serde(skip_serializing_if = "Option::is_none")]
if_in_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
create: Option<HashMap<String, T>>,
#[serde(skip_serializing_if = "Option::is_none")]
update: Option<HashMap<String, T>>,
#[serde(skip_serializing_if = "Option::is_none")]
destroy: Option<Vec<String>>,
#[serde(rename = "#destroy")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "Option::is_none")]
destroy_ref: Option<ResultReference>,
#[serde(flatten)]
arguments: A,
}
@ -20,20 +35,28 @@ pub struct SetRequest<T, A: Default> {
pub struct SetResponse<T, U> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: Option<String>,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "created")]
created: Option<HashMap<String, T>>,
#[serde(rename = "updated")]
updated: Option<HashMap<String, Option<T>>>,
#[serde(rename = "destroyed")]
destroyed: Option<Vec<String>>,
#[serde(rename = "notCreated")]
not_created: Option<HashMap<String, SetError<U>>>,
#[serde(rename = "notUpdated")]
not_updated: Option<HashMap<String, SetError<U>>>,
#[serde(rename = "notDestroyed")]
not_destroyed: Option<HashMap<String, SetError<U>>>,
}
@ -94,23 +117,12 @@ pub enum SetErrorType {
CannotUnsend,
}
pub fn from_timestamp(timestamp: i64) -> DateTime<Utc> {
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc)
pub trait Create: Sized {
fn new(create_id: Option<usize>) -> Self;
fn create_id(&self) -> Option<String>;
}
pub fn string_not_set(string: &Option<String>) -> bool {
matches!(string, Some(string) if string.is_empty())
}
pub fn date_not_set(date: &Option<DateTime<Utc>>) -> bool {
matches!(date, Some(date) if date.timestamp() == 0)
}
pub fn list_not_set<T>(list: &Option<Vec<T>>) -> bool {
matches!(list, Some(list) if list.is_empty() )
}
impl<T, A: Default> SetRequest<T, A> {
impl<T: Create, A: Default> SetRequest<T, A> {
pub fn new(account_id: String) -> Self {
Self {
account_id,
@ -118,6 +130,7 @@ impl<T, A: Default> SetRequest<T, A> {
create: None,
update: None,
destroy: None,
destroy_ref: None,
arguments: Default::default(),
}
}
@ -132,22 +145,42 @@ impl<T, A: Default> SetRequest<T, A> {
self
}
pub fn create(&mut self, id: impl Into<String>, value: T) -> &mut Self {
pub fn create(&mut self) -> &mut T {
let create_id = self.create.as_ref().map_or(0, |c| c.len());
let create_id_str = format!("c{}", create_id);
self.create
.get_or_insert_with(HashMap::new)
.insert(id.into(), value);
self
.insert(create_id_str.clone(), T::new(create_id.into()));
self.create
.as_mut()
.unwrap()
.get_mut(&create_id_str)
.unwrap()
}
pub fn update(&mut self, id: impl Into<String>, value: T) -> &mut Self {
pub fn update(&mut self, id: impl Into<String>) -> &mut T {
let id: String = id.into();
self.update
.get_or_insert_with(HashMap::new)
.insert(id.into(), value);
.insert(id.clone(), T::new(None));
self.update.as_mut().unwrap().get_mut(&id).unwrap()
}
pub fn destroy<U, V>(&mut self, ids: U) -> &mut Self
where
U: IntoIterator<Item = V>,
V: Into<String>,
{
self.destroy
.get_or_insert_with(Vec::new)
.extend(ids.into_iter().map(|id| id.into()));
self.destroy_ref = None;
self
}
pub fn destroy(&mut self, id: impl Into<String>) -> &mut Self {
self.destroy.get_or_insert_with(Vec::new).push(id.into());
pub fn destroy_ref(&mut self, reference: ResultReference) -> &mut Self {
self.destroy_ref = reference.into();
self.destroy = None;
self
}
@ -169,40 +202,28 @@ impl<T, U> SetResponse<T, U> {
&self.new_state
}
pub fn created(&self) -> Option<impl Iterator<Item = &String>> {
self.created.as_ref().map(|map| map.keys())
pub fn created(&self) -> Option<impl Iterator<Item = (&String, &T)>> {
self.created.as_ref().map(|map| map.iter())
}
pub fn updated(&self) -> Option<impl Iterator<Item = &String>> {
self.updated.as_ref().map(|map| map.keys())
pub fn updated(&self) -> Option<impl Iterator<Item = (&String, &Option<T>)>> {
self.updated.as_ref().map(|map| map.iter())
}
pub fn destroyed(&self) -> Option<&[String]> {
self.destroyed.as_deref()
}
pub fn not_created(&self) -> Option<impl Iterator<Item = &String>> {
self.not_created.as_ref().map(|map| map.keys())
pub fn not_created(&self) -> Option<impl Iterator<Item = (&String, &SetError<U>)>> {
self.not_created.as_ref().map(|map| map.iter())
}
pub fn not_updated(&self) -> Option<impl Iterator<Item = &String>> {
self.not_updated.as_ref().map(|map| map.keys())
pub fn not_updated(&self) -> Option<impl Iterator<Item = (&String, &SetError<U>)>> {
self.not_updated.as_ref().map(|map| map.iter())
}
pub fn not_destroyed(&self) -> Option<impl Iterator<Item = &String>> {
self.not_destroyed.as_ref().map(|map| map.keys())
}
pub fn not_created_reason(&self, id: &str) -> Option<&SetError<U>> {
self.not_created.as_ref().and_then(|map| map.get(id))
}
pub fn not_updated_reason(&self, id: &str) -> Option<&SetError<U>> {
self.not_updated.as_ref().and_then(|map| map.get(id))
}
pub fn not_destroyed_reason(&self, id: &str) -> Option<&SetError<U>> {
self.not_destroyed.as_ref().and_then(|map| map.get(id))
pub fn not_destroyed(&self) -> Option<impl Iterator<Item = (&String, &SetError<U>)>> {
self.not_destroyed.as_ref().map(|map| map.iter())
}
pub fn created_details(&self, id: &str) -> Option<&T> {
@ -212,6 +233,18 @@ impl<T, U> SetResponse<T, U> {
pub fn updated_details(&self, id: &str) -> Option<&T> {
self.updated.as_ref().and_then(|map| map.get(id))?.as_ref()
}
pub fn not_created_details(&self, id: &str) -> Option<&SetError<U>> {
self.not_created.as_ref().and_then(|map| map.get(id))
}
pub fn not_updated_details(&self, id: &str) -> Option<&SetError<U>> {
self.not_updated.as_ref().and_then(|map| map.get(id))
}
pub fn not_destroyed_details(&self, id: &str) -> Option<&SetError<U>> {
self.not_destroyed.as_ref().and_then(|map| map.get(id))
}
}
impl<U> SetError<U> {
@ -227,3 +260,19 @@ impl<U> SetError<U> {
self.properties.as_deref()
}
}
pub fn from_timestamp(timestamp: i64) -> DateTime<Utc> {
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc)
}
pub fn string_not_set(string: &Option<String>) -> bool {
matches!(string, Some(string) if string.is_empty())
}
pub fn date_not_set(date: &Option<DateTime<Utc>>) -> bool {
matches!(date, Some(date) if date.timestamp() == 0)
}
pub fn list_not_set<T>(list: &Option<Vec<T>>) -> bool {
matches!(list, Some(list) if list.is_empty() )
}

View File

@ -11,7 +11,9 @@ use super::{Email, Property};
pub struct EmailImportRequest {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "ifInState")]
#[serde(skip_serializing_if = "Option::is_none")]
if_in_state: Option<String>,
emails: HashMap<String, EmailImport>,
@ -19,6 +21,9 @@ pub struct EmailImportRequest {
#[derive(Debug, Clone, Serialize)]
pub struct EmailImport {
#[serde(skip)]
create_id: usize,
#[serde(rename = "blobId")]
blob_id: String,
@ -37,12 +42,16 @@ pub struct EmailImport {
pub struct EmailImportResponse {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: Option<String>,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "created")]
created: Option<HashMap<String, Email>>,
#[serde(rename = "notCreated")]
not_created: Option<HashMap<String, SetError<Property>>>,
}
@ -56,20 +65,26 @@ impl EmailImportRequest {
}
}
pub fn if_in_state(&mut self, if_in_state: String) -> &mut Self {
self.if_in_state = Some(if_in_state);
pub fn if_in_state(&mut self, if_in_state: impl Into<String>) -> &mut Self {
self.if_in_state = Some(if_in_state.into());
self
}
pub fn add_email(&mut self, id: String, email_import: EmailImport) -> &mut Self {
self.emails.insert(id, email_import);
self
pub fn email(&mut self, blob_id: impl Into<String>) -> &mut EmailImport {
let create_id = self.emails.len();
let create_id_str = format!("i{}", create_id);
self.emails.insert(
create_id_str.clone(),
EmailImport::new(blob_id.into(), create_id),
);
self.emails.get_mut(&create_id_str).unwrap()
}
}
impl EmailImport {
pub fn new(blob_id: String) -> Self {
fn new(blob_id: String, create_id: usize) -> Self {
EmailImport {
create_id,
blob_id,
mailbox_ids: HashMap::new(),
keywords: HashMap::new(),
@ -77,20 +92,24 @@ impl EmailImport {
}
}
pub fn mailbox_id(mut self, mailbox_id: String) -> Self {
self.mailbox_ids.insert(mailbox_id, true);
pub fn mailbox_id(&mut self, mailbox_id: impl Into<String>) -> &mut Self {
self.mailbox_ids.insert(mailbox_id.into(), true);
self
}
pub fn keyword(mut self, keyword: String) -> Self {
self.keywords.insert(keyword, true);
pub fn keyword(&mut self, keyword: impl Into<String>) -> &mut Self {
self.keywords.insert(keyword.into(), true);
self
}
pub fn received_at(mut self, received_at: i64) -> Self {
pub fn received_at(&mut self, received_at: i64) -> &mut Self {
self.received_at = Some(from_timestamp(received_at));
self
}
pub fn create_id(&self) -> String {
format!("i{}", self.create_id)
}
}
impl EmailImportResponse {

View File

@ -9,10 +9,13 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::Get;
use crate::{core::request::ResultReference, Get};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Email<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,
@ -32,6 +35,11 @@ pub struct Email<State = Get> {
#[serde(skip_serializing_if = "Option::is_none")]
mailbox_ids: Option<HashMap<String, bool>>,
#[serde(rename = "#mailboxIds")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "Option::is_none")]
mailbox_ids_ref: Option<ResultReference>,
#[serde(rename = "keywords")]
#[serde(skip_serializing_if = "Option::is_none")]
keywords: Option<HashMap<String, bool>>,

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use super::{BodyProperty, Email, Property};
@ -31,7 +31,7 @@ pub struct EmailParseRequest {
max_body_value_bytes: usize,
}
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct EmailParseResponse {
#[serde(rename = "accountId")]
account_id: String,

View File

@ -1,22 +1,35 @@
use std::collections::HashMap;
use crate::{core::set::from_timestamp, Set};
use crate::{
core::{
request::ResultReference,
set::{from_timestamp, Create},
},
Set,
};
use super::{
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field,
};
impl Email<Set> {
pub fn mailbox_ids<T, U>(mut self, mailbox_ids: T) -> Self
pub fn mailbox_ids<T, U>(&mut self, mailbox_ids: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<String>,
{
self.mailbox_ids = Some(mailbox_ids.into_iter().map(|s| (s.into(), true)).collect());
self.mailbox_ids_ref = None;
self
}
pub fn mailbox_id(mut self, mailbox_id: &str, set: bool) -> Self {
pub fn mailbox_ids_ref(&mut self, reference: ResultReference) -> &mut Self {
self.mailbox_ids_ref = reference.into();
self.mailbox_ids = None;
self
}
pub fn mailbox_id(&mut self, mailbox_id: &str, set: bool) -> &mut Self {
self.mailbox_ids = None;
self.others.insert(
format!("mailboxIds/{}", mailbox_id),
@ -25,7 +38,7 @@ impl Email<Set> {
self
}
pub fn keywords<T, U>(mut self, keywords: T) -> Self
pub fn keywords<T, U>(&mut self, keywords: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<String>,
@ -34,14 +47,14 @@ impl Email<Set> {
self
}
pub fn keyword(mut self, keyword: &str, set: bool) -> Self {
pub fn keyword(&mut self, keyword: &str, set: bool) -> &mut Self {
self.keywords = None;
self.others
.insert(format!("keywords/{}", keyword), Field::Bool(set).into());
self
}
pub fn message_id<T, U>(mut self, message_id: T) -> Self
pub fn message_id<T, U>(&mut self, message_id: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<String>,
@ -50,7 +63,7 @@ impl Email<Set> {
self
}
pub fn in_reply_to<T, U>(mut self, in_reply_to: T) -> Self
pub fn in_reply_to<T, U>(&mut self, in_reply_to: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<String>,
@ -59,7 +72,7 @@ impl Email<Set> {
self
}
pub fn references<T, U>(mut self, references: T) -> Self
pub fn references<T, U>(&mut self, references: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<String>,
@ -68,7 +81,7 @@ impl Email<Set> {
self
}
pub fn sender<T, U>(mut self, sender: T) -> Self
pub fn sender<T, U>(&mut self, sender: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<EmailAddress>,
@ -77,7 +90,7 @@ impl Email<Set> {
self
}
pub fn from<T, U>(mut self, from: T) -> Self
pub fn from<T, U>(&mut self, from: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<EmailAddress>,
@ -86,7 +99,7 @@ impl Email<Set> {
self
}
pub fn to<T, U>(mut self, to: T) -> Self
pub fn to<T, U>(&mut self, to: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<EmailAddress>,
@ -95,7 +108,7 @@ impl Email<Set> {
self
}
pub fn cc<T, U>(mut self, cc: T) -> Self
pub fn cc<T, U>(&mut self, cc: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<EmailAddress>,
@ -104,7 +117,7 @@ impl Email<Set> {
self
}
pub fn bcc<T, U>(mut self, bcc: T) -> Self
pub fn bcc<T, U>(&mut self, bcc: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<EmailAddress>,
@ -113,7 +126,7 @@ impl Email<Set> {
self
}
pub fn reply_to<T, U>(mut self, reply_to: T) -> Self
pub fn reply_to<T, U>(&mut self, reply_to: T) -> &mut Self
where
T: IntoIterator<Item = U>,
U: Into<EmailAddress>,
@ -122,59 +135,61 @@ impl Email<Set> {
self
}
pub fn subject(mut self, subject: impl Into<String>) -> Self {
pub fn subject(&mut self, subject: impl Into<String>) -> &mut Self {
self.subject = Some(subject.into());
self
}
pub fn sent_at(mut self, sent_at: i64) -> Self {
pub fn sent_at(&mut self, sent_at: i64) -> &mut Self {
self.sent_at = Some(from_timestamp(sent_at));
self
}
pub fn body_structure(mut self, body_structure: EmailBodyPart) -> Self {
pub fn body_structure(&mut self, body_structure: EmailBodyPart) -> &mut Self {
self.body_structure = Some(body_structure.into());
self
}
pub fn body_value(mut self, id: String, body_value: impl Into<EmailBodyValue>) -> Self {
pub fn body_value(&mut self, id: String, body_value: impl Into<EmailBodyValue>) -> &mut 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 {
pub fn text_body(&mut self, text_body: EmailBodyPart) -> &mut Self {
self.text_body.get_or_insert_with(Vec::new).push(text_body);
self
}
pub fn html_body(mut self, html_body: EmailBodyPart) -> Self {
pub fn html_body(&mut self, html_body: EmailBodyPart) -> &mut Self {
self.html_body.get_or_insert_with(Vec::new).push(html_body);
self
}
pub fn attachment(mut self, attachment: EmailBodyPart) -> Self {
pub fn attachment(&mut self, attachment: EmailBodyPart) -> &mut Self {
self.attachments
.get_or_insert_with(Vec::new)
.push(attachment);
self
}
pub fn header(mut self, header: String, value: impl Into<Field>) -> Self {
pub fn header(&mut self, header: String, value: impl Into<Field>) -> &mut Self {
self.others.insert(header, Some(value.into()));
self
}
}
impl Email {
pub fn new() -> Email<Set> {
impl Create for Email<Set> {
fn new(_create_id: Option<usize>) -> Email<Set> {
Email {
_create_id,
_state: Default::default(),
id: Default::default(),
blob_id: Default::default(),
thread_id: Default::default(),
mailbox_ids: Default::default(),
mailbox_ids_ref: Default::default(),
keywords: Default::default(),
size: Default::default(),
received_at: Default::default(),
@ -199,6 +214,10 @@ impl Email {
others: Default::default(),
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}
impl EmailBodyPart {

View File

@ -19,6 +19,9 @@ pub struct SetArguments {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailSubmission<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,

View File

@ -1,21 +1,21 @@
use std::collections::HashMap;
use crate::Set;
use crate::{core::set::Create, Set};
use super::{Address, EmailSubmission, Envelope, UndoStatus};
impl EmailSubmission<Set> {
pub fn identity_id(mut self, identity_id: String) -> Self {
self.identity_id = Some(identity_id);
pub fn identity_id(&mut self, identity_id: impl Into<String>) -> &mut Self {
self.identity_id = Some(identity_id.into());
self
}
pub fn email_id(mut self, email_id: String) -> Self {
self.email_id = Some(email_id);
pub fn email_id(&mut self, email_id: impl Into<String>) -> &mut Self {
self.email_id = Some(email_id.into());
self
}
pub fn envelope<T, U>(mut self, mail_from: U, rcpt_to: T) -> Self
pub fn envelope<T, U>(&mut self, mail_from: U, rcpt_to: T) -> &mut Self
where
T: Iterator<Item = U>,
U: Into<Address>,
@ -27,15 +27,16 @@ impl EmailSubmission<Set> {
self
}
pub fn undo_status(mut self, undo_status: UndoStatus) -> Self {
pub fn undo_status(&mut self, undo_status: UndoStatus) -> &mut Self {
self.undo_status = Some(undo_status);
self
}
}
impl EmailSubmission {
pub fn new() -> EmailSubmission<Set> {
impl Create for EmailSubmission<Set> {
fn new(_create_id: Option<usize>) -> Self {
EmailSubmission {
_create_id,
_state: Default::default(),
id: None,
identity_id: None,
@ -49,6 +50,10 @@ impl EmailSubmission {
mdn_blob_ids: None,
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}
impl Address {

18
src/event_source/mod.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::core::session::URLParser;
pub enum URLParameter {
Types,
CloseAfter,
Ping,
}
impl URLParser for URLParameter {
fn parse(value: &str) -> Option<Self> {
match value {
"types" => Some(URLParameter::Types),
"closeafter" => Some(URLParameter::CloseAfter),
"ping" => Some(URLParameter::Ping),
_ => None,
}
}
}

View File

@ -7,6 +7,9 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Identity<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,

View File

@ -1,19 +1,19 @@
use crate::{email::EmailAddress, Set};
use crate::{core::set::Create, email::EmailAddress, Set};
use super::Identity;
impl Identity<Set> {
pub fn name(mut self, name: String) -> Self {
pub fn name(&mut self, name: String) -> &mut Self {
self.name = Some(name);
self
}
pub fn email(mut self, email: String) -> Self {
pub fn email(&mut self, email: String) -> &mut Self {
self.email = Some(email);
self
}
pub fn bcc<T, U>(mut self, bcc: Option<T>) -> Self
pub fn bcc<T, U>(&mut self, bcc: Option<T>) -> &mut Self
where
T: Iterator<Item = U>,
U: Into<EmailAddress>,
@ -22,7 +22,7 @@ impl Identity<Set> {
self
}
pub fn reply_to<T, U>(mut self, reply_to: Option<T>) -> Self
pub fn reply_to<T, U>(&mut self, reply_to: Option<T>) -> &mut Self
where
T: Iterator<Item = U>,
U: Into<EmailAddress>,
@ -31,20 +31,21 @@ impl Identity<Set> {
self
}
pub fn text_signature(mut self, text_signature: String) -> Self {
pub fn text_signature(&mut self, text_signature: String) -> &mut Self {
self.text_signature = Some(text_signature);
self
}
pub fn html_signature(mut self, html_signature: String) -> Self {
pub fn html_signature(&mut self, html_signature: String) -> &mut Self {
self.html_signature = Some(html_signature);
self
}
}
impl Identity {
pub fn new() -> Identity<Set> {
impl Create for Identity<Set> {
fn new(_create_id: Option<usize>) -> Self {
Identity {
_create_id,
_state: Default::default(),
id: None,
name: None,
@ -56,4 +57,8 @@ impl Identity {
may_delete: None,
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}

View File

@ -1,4 +1,5 @@
use std::collections::HashMap;
use crate::core::error::ProblemDetails;
use std::{collections::HashMap, fmt::Display};
use serde::{Deserialize, Serialize};
@ -7,6 +8,7 @@ pub mod client;
pub mod core;
pub mod email;
pub mod email_submission;
pub mod event_source;
pub mod identity;
pub mod mailbox;
pub mod push_subscription;
@ -125,3 +127,37 @@ pub struct StateChange {
pub struct Get;
#[derive(Debug, Clone)]
pub struct Set;
pub type Result<T> = std::result::Result<T, Error>;
pub enum Error {
Transport(reqwest::Error),
Parse(serde_json::Error),
Internal(String),
Problem(ProblemDetails),
ServerError(String),
}
impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
Error::Transport(e)
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Parse(e)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Transport(e) => write!(f, "Transport error: {}", e),
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),
}
}
}

View File

@ -21,8 +21,17 @@ pub struct QueryArguments {
filter_as_tree: bool,
}
#[derive(Debug, Deserialize, Default)]
pub struct ChangesResponse {
#[serde(rename = "updatedProperties")]
updated_properties: Option<Vec<Property>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mailbox<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,
@ -139,3 +148,9 @@ pub enum Property {
#[serde(rename = "isSubscribed")]
IsSubscribed,
}
impl ChangesResponse {
pub fn updated_properties(&self) -> Option<&[Property]> {
self.updated_properties.as_deref()
}
}

View File

@ -2,7 +2,7 @@ use serde::Serialize;
use crate::core::query::{self};
use super::Role;
use super::{QueryArguments, Role};
#[derive(Serialize, Clone, Debug)]
#[serde(untagged)]
@ -81,3 +81,15 @@ impl Comparator {
query::Comparator::new(Comparator::ParentId)
}
}
impl QueryArguments {
pub fn sort_as_tree(&mut self, value: bool) -> &mut Self {
self.sort_as_tree = value;
self
}
pub fn filter_as_tree(&mut self, value: bool) -> &mut Self {
self.filter_as_tree = value;
self
}
}

View File

@ -1,19 +1,19 @@
use crate::Set;
use crate::{core::set::Create, Set};
use super::{Mailbox, Role};
use super::{Mailbox, Role, SetArguments};
impl Mailbox<Set> {
pub fn name(mut self, name: String) -> Self {
self.name = Some(name);
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
self.name = Some(name.into());
self
}
pub fn parent_id(mut self, parent_id: Option<String>) -> Self {
self.parent_id = parent_id;
pub fn parent_id(&mut self, parent_id: Option<impl Into<String>>) -> &mut Self {
self.parent_id = parent_id.map(|s| s.into());
self
}
pub fn role(mut self, role: Role) -> Self {
pub fn role(&mut self, role: Role) -> &mut Self {
if !matches!(role, Role::None) {
self.role = Some(role);
} else {
@ -22,7 +22,7 @@ impl Mailbox<Set> {
self
}
pub fn sort_order(mut self, sort_order: u32) -> Self {
pub fn sort_order(&mut self, sort_order: u32) -> &mut Self {
self.sort_order = sort_order.into();
self
}
@ -32,9 +32,10 @@ pub fn role_not_set(role: &Option<Role>) -> bool {
matches!(role, Some(Role::None))
}
impl Mailbox {
pub fn new() -> Mailbox<Set> {
impl Create for Mailbox<Set> {
fn new(_create_id: Option<usize>) -> Self {
Mailbox {
_create_id,
_state: Default::default(),
id: None,
name: None,
@ -49,4 +50,15 @@ impl Mailbox {
is_subscribed: None,
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}
impl SetArguments {
pub fn on_destroy_remove_emails(&mut self, value: bool) -> &mut Self {
self.on_destroy_remove_emails = value;
self
}
}

View File

@ -9,6 +9,9 @@ use crate::{Get, Object};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushSubscription<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,

View File

@ -1,42 +1,46 @@
use crate::{core::set::from_timestamp, Object, Set};
use crate::{
core::set::{from_timestamp, Create},
Object, Set,
};
use super::{Keys, PushSubscription};
impl PushSubscription<Set> {
pub fn device_client_id(mut self, device_client_id: String) -> Self {
self.device_client_id = Some(device_client_id);
pub fn device_client_id(&mut self, device_client_id: impl Into<String>) -> &mut Self {
self.device_client_id = Some(device_client_id.into());
self
}
pub fn url(mut self, url: String) -> Self {
self.url = Some(url);
pub fn url(&mut self, url: impl Into<String>) -> &mut Self {
self.url = Some(url.into());
self
}
pub fn verification_code(mut self, verification_code: String) -> Self {
self.verification_code = Some(verification_code);
pub fn verification_code(&mut self, verification_code: impl Into<String>) -> &mut Self {
self.verification_code = Some(verification_code.into());
self
}
pub fn keys(mut self, keys: Keys) -> Self {
pub fn keys(&mut self, keys: Keys) -> &mut Self {
self.keys = Some(keys);
self
}
pub fn expires(mut self, expires: i64) -> Self {
pub fn expires(&mut self, expires: i64) -> &mut Self {
self.expires = Some(from_timestamp(expires));
self
}
pub fn types(mut self, types: Option<impl Iterator<Item = Object>>) -> Self {
pub fn types(&mut self, types: Option<impl Iterator<Item = Object>>) -> &mut Self {
self.types = types.map(|s| s.collect());
self
}
}
impl PushSubscription {
pub fn new() -> PushSubscription<Set> {
impl Create for PushSubscription<Set> {
fn new(_create_id: Option<usize>) -> Self {
PushSubscription {
_create_id,
_state: Default::default(),
id: None,
device_client_id: None,
@ -47,6 +51,10 @@ impl PushSubscription {
types: Vec::with_capacity(0).into(),
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}
impl Keys {

View File

@ -9,6 +9,9 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VacationResponse<State = Get> {
#[serde(skip)]
_create_id: Option<usize>,
#[serde(skip)]
_state: std::marker::PhantomData<State>,

View File

@ -1,42 +1,46 @@
use crate::{core::set::from_timestamp, Set};
use crate::{
core::set::{from_timestamp, Create},
Set,
};
use super::VacationResponse;
impl VacationResponse<Set> {
pub fn is_enabled(mut self, is_enabled: bool) -> Self {
pub fn is_enabled(&mut self, is_enabled: bool) -> &mut Self {
self.is_enabled = Some(is_enabled);
self
}
pub fn from_date(mut self, from_date: Option<i64>) -> Self {
pub fn from_date(&mut self, from_date: Option<i64>) -> &mut Self {
self.from_date = from_date.map(from_timestamp);
self
}
pub fn to_date(mut self, to_date: Option<i64>) -> Self {
pub fn to_date(&mut self, to_date: Option<i64>) -> &mut Self {
self.to_date = to_date.map(from_timestamp);
self
}
pub fn subject(mut self, subject: Option<String>) -> Self {
self.subject = subject;
pub fn subject(&mut self, subject: Option<impl Into<String>>) -> &mut Self {
self.subject = subject.map(|s| s.into());
self
}
pub fn text_body(mut self, text_body: Option<String>) -> Self {
self.text_body = text_body;
pub fn text_body(&mut self, text_body: Option<impl Into<String>>) -> &mut Self {
self.text_body = text_body.map(|s| s.into());
self
}
pub fn html_body(mut self, html_body: Option<String>) -> Self {
self.html_body = html_body;
pub fn html_body(&mut self, html_body: Option<impl Into<String>>) -> &mut Self {
self.html_body = html_body.map(|s| s.into());
self
}
}
impl VacationResponse {
pub fn new() -> VacationResponse<Set> {
impl Create for VacationResponse<Set> {
fn new(_create_id: Option<usize>) -> Self {
VacationResponse {
_create_id,
_state: Default::default(),
id: None,
is_enabled: None,
@ -47,4 +51,8 @@ impl VacationResponse {
html_body: "".to_string().into(),
}
}
fn create_id(&self) -> Option<String> {
self._create_id.map(|id| format!("c{}", id))
}
}