Files
Stalwart/crates/jmap-proto/src/method/set.rs
2023-04-20 16:39:28 +00:00

345 lines
14 KiB
Rust

use ahash::AHashMap;
use utils::map::vec_map::VecMap;
use crate::{
error::{method::MethodError, set::SetError},
object::{email_submission, mailbox, sieve, Object},
parser::{json::Parser, Error, JsonObjectParser, Token},
request::{
method::MethodObject,
reference::{MaybeReference, ResultReference},
RequestProperty, RequestPropertyParser,
},
types::{
acl::Acl,
blob::BlobId,
date::UTCDate,
id::Id,
keyword::Keyword,
property::{HeaderForm, ObjectProperty, Property, SetProperty},
state::State,
type_state::TypeState,
value::{SetValue, SetValueMap, Value},
},
};
use super::ahash_is_empty;
#[derive(Debug, Clone)]
pub struct SetRequest {
pub account_id: Id,
pub if_in_state: Option<State>,
pub create: Option<VecMap<String, Object<SetValue>>>,
pub update: Option<VecMap<Id, Object<SetValue>>>,
pub destroy: Option<MaybeReference<Vec<Id>, ResultReference>>,
pub arguments: RequestArguments,
}
#[derive(Debug, Clone)]
pub enum RequestArguments {
Email,
Mailbox(mailbox::SetArguments),
Identity,
EmailSubmission(email_submission::SetArguments),
PushSubscription,
SieveScript(sieve::SetArguments),
VacationResponse,
Principal,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct SetResponse {
#[serde(rename = "accountId")]
#[serde(skip_serializing_if = "Option::is_none")]
pub account_id: Option<Id>,
#[serde(rename = "oldState")]
#[serde(skip_serializing_if = "Option::is_none")]
pub old_state: Option<State>,
#[serde(rename = "newState")]
#[serde(skip_serializing_if = "Option::is_none")]
pub new_state: Option<State>,
#[serde(rename = "created")]
#[serde(skip_serializing_if = "ahash_is_empty")]
pub created: AHashMap<String, Object<Value>>,
#[serde(rename = "updated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub updated: VecMap<Id, Option<Object<Value>>>,
#[serde(rename = "destroyed")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub destroyed: Vec<Id>,
#[serde(rename = "notCreated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_created: VecMap<String, SetError>,
#[serde(rename = "notUpdated")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_updated: VecMap<Id, SetError>,
#[serde(rename = "notDestroyed")]
#[serde(skip_serializing_if = "VecMap::is_empty")]
pub not_destroyed: VecMap<Id, SetError>,
}
impl JsonObjectParser for SetRequest {
fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
where
Self: Sized,
{
let mut request = SetRequest {
arguments: match &parser.ctx {
MethodObject::Email => RequestArguments::Email,
MethodObject::Mailbox => RequestArguments::Mailbox(Default::default()),
MethodObject::Identity => RequestArguments::Identity,
MethodObject::EmailSubmission => {
RequestArguments::EmailSubmission(Default::default())
}
MethodObject::PushSubscription => RequestArguments::PushSubscription,
MethodObject::VacationResponse => RequestArguments::VacationResponse,
MethodObject::SieveScript => RequestArguments::SieveScript(Default::default()),
MethodObject::Principal => RequestArguments::Principal,
_ => {
return Err(Error::Method(MethodError::UnknownMethod(format!(
"{}/set",
parser.ctx
))))
}
},
account_id: Id::default(),
if_in_state: None,
create: None,
update: None,
destroy: None,
};
parser
.next_token::<String>()?
.assert_jmap(Token::DictStart)?;
while let Some(key) = parser.next_dict_key::<RequestProperty>()? {
match &key.hash[0] {
0x6449_746e_756f_6363_61 if !key.is_ref => {
request.account_id = parser.next_token::<Id>()?.unwrap_string("accountId")?;
}
0x6574_6165_7263 if !key.is_ref => {
request.create = <Option<VecMap<String, Object<SetValue>>>>::parse(parser)?;
}
0x6574_6164_7075 if !key.is_ref => {
request.update = <Option<VecMap<Id, Object<SetValue>>>>::parse(parser)?;
}
0x0079_6f72_7473_6564 => {
request.destroy = if !key.is_ref {
<Option<Vec<Id>>>::parse(parser)?.map(MaybeReference::Value)
} else {
Some(MaybeReference::Reference(ResultReference::parse(parser)?))
};
}
0x6574_6174_536e_4966_69 if !key.is_ref => {
request.if_in_state = parser
.next_token::<State>()?
.unwrap_string_or_null("ifInState")?;
}
_ => {
if !request.arguments.parse(parser, key)? {
parser.skip_token(parser.depth_array, parser.depth_dict)?;
}
}
}
}
Ok(request)
}
}
impl JsonObjectParser for Object<SetValue> {
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
where
Self: Sized,
{
let mut obj = Object {
properties: VecMap::with_capacity(8),
};
parser
.next_token::<String>()?
.assert_jmap(Token::DictStart)?;
while let Some(mut key) = parser.next_dict_key::<SetProperty>()? {
let value = if !key.is_ref {
match &key.property {
Property::Id | Property::ThreadId => parser
.next_token::<Id>()?
.unwrap_string_or_null("")?
.map(|id| SetValue::Value(Value::Id(id)))
.unwrap_or(SetValue::Value(Value::Null)),
Property::BlobId | Property::Picture => parser
.next_token::<BlobId>()?
.unwrap_string_or_null("")?
.map(|id| SetValue::Value(Value::BlobId(id)))
.unwrap_or(SetValue::Value(Value::Null)),
Property::SentAt
| Property::ReceivedAt
| Property::Expires
| Property::FromDate
| Property::ToDate => parser
.next_token::<UTCDate>()?
.unwrap_string_or_null("")?
.map(|date| SetValue::Value(Value::Date(date)))
.unwrap_or(SetValue::Value(Value::Null)),
Property::Subject
| Property::Preview
| Property::Name
| Property::Description
| Property::Timezone
| Property::Email
| Property::Secret
| Property::DeviceClientId
| Property::Url
| Property::VerificationCode
| Property::HtmlSignature
| Property::TextSignature
| Property::Type
| Property::Charset
| Property::Disposition
| Property::Language
| Property::Location
| Property::Cid
| Property::Role
| Property::PartId => parser
.next_token::<String>()?
.unwrap_string_or_null("")?
.map(|text| SetValue::Value(Value::Text(text)))
.unwrap_or(SetValue::Value(Value::Null)),
Property::TextBody | Property::HtmlBody => {
if let MethodObject::Email = &parser.ctx {
SetValue::Value(Value::parse::<ObjectProperty, String>(
parser.next_token()?,
parser,
)?)
} else {
parser
.next_token::<String>()?
.unwrap_string_or_null("")?
.map(|text| SetValue::Value(Value::Text(text)))
.unwrap_or(SetValue::Value(Value::Null))
}
}
Property::HasAttachment
| Property::IsSubscribed
| Property::IsEnabled
| Property::IsActive => parser
.next_token::<String>()?
.unwrap_bool_or_null("")?
.map(|bool| SetValue::Value(Value::Bool(bool)))
.unwrap_or(SetValue::Value(Value::Null)),
Property::Size | Property::SortOrder | Property::Quota => parser
.next_token::<String>()?
.unwrap_uint_or_null("")?
.map(|uint| SetValue::Value(Value::UnsignedInt(uint)))
.unwrap_or(SetValue::Value(Value::Null)),
Property::ParentId | Property::EmailId | Property::IdentityId => parser
.next_token::<MaybeReference<Id, String>>()?
.unwrap_string_or_null("")?
.map(SetValue::IdReference)
.unwrap_or(SetValue::Value(Value::Null)),
Property::MailboxIds => {
if key.patch.is_empty() {
SetValue::IdReferences(
<SetValueMap<MaybeReference<Id, String>>>::parse(parser)?.values,
)
} else {
key.patch.push(Value::Bool(bool::parse(parser)?));
SetValue::Patch(key.patch)
}
}
Property::Keywords => {
if key.patch.is_empty() {
SetValue::Value(Value::List(
<SetValueMap<Keyword>>::parse(parser)?
.values
.into_iter()
.map(Value::Keyword)
.collect(),
))
} else {
key.patch.push(Value::Bool(bool::parse(parser)?));
SetValue::Patch(key.patch)
}
}
Property::Acl => {
SetValue::Value(Value::parse::<String, Acl>(parser.next_token()?, parser)?)
}
Property::Aliases
| Property::Attachments
| Property::Bcc
| Property::BodyStructure
| Property::BodyValues
| Property::Capabilities
| Property::Cc
| Property::Envelope
| Property::From
| Property::Headers
| Property::InReplyTo
| Property::Keys
| Property::MessageId
| Property::References
| Property::ReplyTo
| Property::Sender
| Property::SubParts
| Property::To
| Property::UndoStatus => {
SetValue::Value(Value::parse::<ObjectProperty, String>(
parser.next_token()?,
parser,
)?)
}
Property::Members => SetValue::Value(Value::parse::<ObjectProperty, Id>(
parser.next_token()?,
parser,
)?),
Property::Header(h) => SetValue::Value(if matches!(h.form, HeaderForm::Date) {
Value::parse::<ObjectProperty, UTCDate>(parser.next_token()?, parser)
} else {
Value::parse::<ObjectProperty, String>(parser.next_token()?, parser)
}?),
Property::Types => SetValue::Value(Value::parse::<ObjectProperty, TypeState>(
parser.next_token()?,
parser,
)?),
_ => {
parser.skip_token(parser.depth_array, parser.depth_dict)?;
SetValue::Value(Value::Null)
}
}
} else {
SetValue::ResultReference(ResultReference::parse(parser)?)
};
obj.properties.append(key.property, value);
}
Ok(obj)
}
}
impl RequestPropertyParser for RequestArguments {
fn parse(
&mut self,
parser: &mut Parser,
property: RequestProperty,
) -> crate::parser::Result<bool> {
match self {
RequestArguments::Mailbox(args) => args.parse(parser, property),
RequestArguments::EmailSubmission(args) => args.parse(parser, property),
RequestArguments::SieveScript(args) => args.parse(parser, property),
_ => Ok(false),
}
}
}