summaryrefslogtreecommitdiff
path: root/crates/jmap-proto
diff options
context:
space:
mode:
authorMauro D <mauro@stalw.art>2023-04-28 08:35:53 +0000
committerMauro D <mauro@stalw.art>2023-04-28 08:35:53 +0000
commitc3a2b43ddbcd089b193eb33f26785f4736c92ee8 (patch)
treedd811092ba58f234c60e56706b25c98234e48f47 /crates/jmap-proto
parent46b5dc04253fe18748cfbdc77c68f0ec3e094efd (diff)
Email/set create tests passing
Diffstat (limited to 'crates/jmap-proto')
-rw-r--r--crates/jmap-proto/src/error/set.rs65
-rw-r--r--crates/jmap-proto/src/lib.rs105
-rw-r--r--crates/jmap-proto/src/method/set.rs60
-rw-r--r--crates/jmap-proto/src/response/references.rs49
-rw-r--r--crates/jmap-proto/src/types/blob.rs6
-rw-r--r--crates/jmap-proto/src/types/property.rs18
-rw-r--r--crates/jmap-proto/src/types/value.rs71
7 files changed, 259 insertions, 115 deletions
diff --git a/crates/jmap-proto/src/error/set.rs b/crates/jmap-proto/src/error/set.rs
index d7a9e267..8959079b 100644
--- a/crates/jmap-proto/src/error/set.rs
+++ b/crates/jmap-proto/src/error/set.rs
@@ -34,13 +34,19 @@ pub struct SetError {
description: Option<Cow<'static, str>>,
#[serde(skip_serializing_if = "Option::is_none")]
- properties: Option<Vec<Property>>,
+ properties: Option<Vec<InvalidProperty>>,
#[serde(rename = "existingId")]
#[serde(skip_serializing_if = "Option::is_none")]
existing_id: Option<Id>,
}
+#[derive(Debug, Clone)]
+pub enum InvalidProperty {
+ Property(Property),
+ Path(Vec<Property>),
+}
+
#[derive(Debug, Clone, serde::Serialize)]
pub enum SetErrorType {
#[serde(rename = "forbidden")]
@@ -142,13 +148,20 @@ impl SetError {
self
}
- pub fn with_property(mut self, property: Property) -> Self {
- self.properties = vec![property].into();
+ pub fn with_property(mut self, property: impl Into<InvalidProperty>) -> Self {
+ self.properties = vec![property.into()].into();
self
}
- pub fn with_properties(mut self, properties: impl IntoIterator<Item = Property>) -> Self {
- self.properties = properties.into_iter().collect::<Vec<_>>().into();
+ pub fn with_properties(
+ mut self,
+ properties: impl IntoIterator<Item = impl Into<InvalidProperty>>,
+ ) -> Self {
+ self.properties = properties
+ .into_iter()
+ .map(Into::into)
+ .collect::<Vec<_>>()
+ .into();
self
}
@@ -165,9 +178,49 @@ impl SetError {
Self::new(SetErrorType::Forbidden)
}
+ pub fn not_found() -> Self {
+ Self::new(SetErrorType::NotFound)
+ }
+
pub fn already_exists() -> Self {
Self::new(SetErrorType::AlreadyExists)
}
+
+ pub fn will_destroy() -> Self {
+ Self::new(SetErrorType::WillDestroy).with_description("ID will be destroyed.")
+ }
+}
+
+impl From<Property> for InvalidProperty {
+ fn from(property: Property) -> Self {
+ InvalidProperty::Property(property)
+ }
+}
+
+impl From<(Property, Property)> for InvalidProperty {
+ fn from((a, b): (Property, Property)) -> Self {
+ InvalidProperty::Path(vec![a, b])
+ }
}
-pub type Result<T> = std::result::Result<T, SetError>;
+impl serde::Serialize for InvalidProperty {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ InvalidProperty::Property(p) => p.serialize(serializer),
+ InvalidProperty::Path(p) => {
+ use std::fmt::Write;
+ let mut path = String::with_capacity(64);
+ for (i, p) in p.iter().enumerate() {
+ if i > 0 {
+ path.push('/');
+ }
+ let _ = write!(path, "{}", p);
+ }
+ path.serialize(serializer)
+ }
+ }
+ }
+}
diff --git a/crates/jmap-proto/src/lib.rs b/crates/jmap-proto/src/lib.rs
index b34bcb30..de61eab0 100644
--- a/crates/jmap-proto/src/lib.rs
+++ b/crates/jmap-proto/src/lib.rs
@@ -5,108 +5,3 @@ pub mod parser;
pub mod request;
pub mod response;
pub mod types;
-
-/*
-#[cfg(test)]
-mod tests {
- use std::{collections::BTreeMap, sync::Arc};
-
- #[test]
- fn gen_hash() {
- //let mut table = BTreeMap::new();
- for value in ["blobIds", "ifInState", "emails"] {
- let mut hash = 0;
- let mut shift = 0;
- let lower_first = false;
-
- for (pos, &ch) in value.as_bytes().iter().take(16).enumerate() {
- if pos == 0 && lower_first {
- hash |= (ch.to_ascii_lowercase() as u128) << shift;
- } else {
- hash |= (ch as u128) << shift;
- }
- shift += 8;
- }
-
- shift = 0;
- let mut hash2 = 0;
- for &ch in value.as_bytes().iter().skip(16).take(16) {
- hash2 |= (ch as u128) << shift;
- shift += 8;
- }
-
- println!(
- "0x{} => {{}} // {}",
- format!("{hash:x}")
- .as_bytes()
- .chunks(4)
- .into_iter()
- .map(|s| std::str::from_utf8(s).unwrap())
- .collect::<Vec<_>>()
- .join("_"),
- value
- );
- /*println!(
- "(0x{}, 0x{}) => Filter::{}(),",
- format!("{hash:x}")
- .as_bytes()
- .chunks(4)
- .into_iter()
- .map(|s| std::str::from_utf8(s).unwrap())
- .collect::<Vec<_>>()
- .join("_"),
- format!("{hash2:x}")
- .as_bytes()
- .chunks(4)
- .into_iter()
- .map(|s| std::str::from_utf8(s).unwrap())
- .collect::<Vec<_>>()
- .join("_"),
- value
- );*/
-
- /*let mut hash = 0;
- let mut shift = 0;
- let mut first_ch = 0;
- let mut name = Vec::new();
-
- for (pos, &ch) in value.as_bytes().iter().take(16).enumerate() {
- if pos == 0 {
- first_ch = ch.to_ascii_lowercase();
- name.push(ch.to_ascii_uppercase());
- } else {
- hash |= (ch as u128) << shift;
- shift += 8;
- name.push(ch);
- }
- }
-
- //println!("Property::{} => {{}}", std::str::from_utf8(&name).unwrap());
-
- table
- .entry(first_ch)
- .or_insert_with(|| vec![])
- .push((hash, name));*/
- }
-
- /*for (k, v) in table {
- println!("b'{}' => match hash {{", k as char);
- for (hash, value) in v {
- println!(
- " 0x{} => Property::{},",
- format!("{hash:x}")
- .as_bytes()
- .chunks(4)
- .into_iter()
- .map(|s| std::str::from_utf8(s).unwrap())
- .collect::<Vec<_>>()
- .join("_"),
- std::str::from_utf8(&value).unwrap()
- );
- }
- println!(" _ => parser.invalid_property()?,");
- println!("}}");
- }*/
- }
-}
-*/
diff --git a/crates/jmap-proto/src/method/set.rs b/crates/jmap-proto/src/method/set.rs
index 226545c0..e88c2a52 100644
--- a/crates/jmap-proto/src/method/set.rs
+++ b/crates/jmap-proto/src/method/set.rs
@@ -2,7 +2,10 @@ use ahash::AHashMap;
use utils::map::vec_map::VecMap;
use crate::{
- error::{method::MethodError, set::SetError},
+ error::{
+ method::MethodError,
+ set::{InvalidProperty, SetError},
+ },
object::{email_submission, mailbox, sieve, Object},
parser::{json::Parser, Error, JsonObjectParser, Token},
request::{
@@ -342,3 +345,58 @@ impl RequestPropertyParser for RequestArguments {
}
}
}
+
+impl SetRequest {
+ pub fn validate(&self, max_objects_in_set: usize) -> Result<(), MethodError> {
+ if self.create.as_ref().map_or(0, |objs| objs.len())
+ + self.update.as_ref().map_or(0, |objs| objs.len())
+ + self.destroy.as_ref().map_or(0, |objs| {
+ if let MaybeReference::Value(ids) = objs {
+ ids.len()
+ } else {
+ 0
+ }
+ })
+ > max_objects_in_set
+ {
+ Err(MethodError::RequestTooLarge)
+ } else {
+ Ok(())
+ }
+ }
+
+ pub fn unwrap_create(&mut self) -> VecMap<String, Object<SetValue>> {
+ self.create.take().unwrap_or_default()
+ }
+
+ pub fn unwrap_update(&mut self) -> VecMap<Id, Object<SetValue>> {
+ self.update.take().unwrap_or_default()
+ }
+
+ pub fn unwrap_destroy(&mut self) -> Vec<Id> {
+ self.destroy
+ .take()
+ .map(|ids| ids.unwrap())
+ .unwrap_or_default()
+ }
+}
+
+impl SetResponse {
+ pub fn invalid_property_create(&mut self, id: String, property: impl Into<InvalidProperty>) {
+ self.not_created.append(
+ id,
+ SetError::invalid_properties()
+ .with_property(property)
+ .with_description("Invalid property or value.".to_string()),
+ );
+ }
+
+ pub fn invalid_property_update(&mut self, id: Id, property: impl Into<InvalidProperty>) {
+ self.not_updated.append(
+ id,
+ SetError::invalid_properties()
+ .with_property(property)
+ .with_description("Invalid property or value.".to_string()),
+ );
+ }
+}
diff --git a/crates/jmap-proto/src/response/references.rs b/crates/jmap-proto/src/response/references.rs
index 04f38212..96a4d481 100644
--- a/crates/jmap-proto/src/response/references.rs
+++ b/crates/jmap-proto/src/response/references.rs
@@ -12,7 +12,7 @@ use crate::{
types::{
id::Id,
property::Property,
- value::{SetValue, Value},
+ value::{MaybePatchValue, SetValue, Value},
},
};
@@ -294,6 +294,53 @@ impl Response {
}
}
+impl Object<SetValue> {
+ pub fn iterate_and_eval_references(
+ self,
+ response: &Response,
+ ) -> impl Iterator<Item = Result<(Property, MaybePatchValue), MethodError>> + '_ {
+ self.properties
+ .into_iter()
+ .map(|(property, set_value)| match set_value {
+ SetValue::Value(value) => Ok((property, MaybePatchValue::Value(value))),
+ SetValue::Patch(patch) => Ok((property, MaybePatchValue::Patch(patch))),
+ SetValue::IdReference(MaybeReference::Reference(id_ref)) => {
+ if let Some(id) = response.created_ids.get(&id_ref) {
+ Ok((property, MaybePatchValue::Value(Value::Id(*id))))
+ } else {
+ Err(MethodError::InvalidResultReference(format!(
+ "Id reference {id_ref:?} not found."
+ )))
+ }
+ }
+ SetValue::IdReference(MaybeReference::Value(id)) => {
+ Ok((property, MaybePatchValue::Value(Value::Id(id))))
+ }
+ SetValue::IdReferences(id_refs) => {
+ let mut ids = Vec::with_capacity(id_refs.len());
+ for id_ref in id_refs {
+ match id_ref {
+ MaybeReference::Value(id) => {
+ ids.push(Value::Id(id));
+ }
+ MaybeReference::Reference(id_ref) => {
+ if let Some(id) = response.created_ids.get(&id_ref) {
+ ids.push(Value::Id(*id));
+ } else {
+ return Err(MethodError::InvalidResultReference(format!(
+ "Id reference {id_ref:?} not found."
+ )));
+ }
+ }
+ }
+ }
+ Ok((property, MaybePatchValue::Value(Value::List(ids))))
+ }
+ _ => unreachable!(),
+ })
+ }
+}
+
impl EvalResult {
pub fn unwrap_ids(self, rr: &ResultReference) -> Result<Vec<Id>, MethodError> {
if let EvalResult::Values(values) = self {
diff --git a/crates/jmap-proto/src/types/blob.rs b/crates/jmap-proto/src/types/blob.rs
index 9656cfe1..c9c40ff0 100644
--- a/crates/jmap-proto/src/types/blob.rs
+++ b/crates/jmap-proto/src/types/blob.rs
@@ -29,7 +29,7 @@ use store::{
BlobKind,
};
use utils::codec::{
- base32_custom::Base32Writer,
+ base32_custom::{Base32Reader, Base32Writer},
leb128::{Leb128Iterator, Leb128Writer},
};
@@ -109,6 +109,10 @@ impl BlobId {
}
}
+ pub fn from_base32(value: &str) -> Option<Self> {
+ BlobId::from_iter(&mut Base32Reader::new(value.as_bytes()))
+ }
+
#[allow(clippy::should_implement_trait)]
pub fn from_iter<T, U>(it: &mut T) -> Option<Self>
where
diff --git a/crates/jmap-proto/src/types/property.rs b/crates/jmap-proto/src/types/property.rs
index fd19d005..bbcf3421 100644
--- a/crates/jmap-proto/src/types/property.rs
+++ b/crates/jmap-proto/src/types/property.rs
@@ -668,6 +668,23 @@ impl Property {
Property::_T(value.to_string())
}
}
+
+ pub fn as_rfc_header(&self) -> RfcHeader {
+ match self {
+ Property::MessageId => RfcHeader::MessageId,
+ Property::InReplyTo => RfcHeader::InReplyTo,
+ Property::References => RfcHeader::References,
+ Property::Sender => RfcHeader::Sender,
+ Property::From => RfcHeader::From,
+ Property::To => RfcHeader::To,
+ Property::Cc => RfcHeader::Cc,
+ Property::Bcc => RfcHeader::Bcc,
+ Property::ReplyTo => RfcHeader::ReplyTo,
+ Property::Subject => RfcHeader::Subject,
+ Property::SentAt => RfcHeader::Date,
+ _ => unreachable!(),
+ }
+ }
}
impl Display for Property {
@@ -968,6 +985,7 @@ impl From<RfcHeader> for Property {
RfcHeader::InReplyTo => Property::InReplyTo,
RfcHeader::MessageId => Property::MessageId,
RfcHeader::References => Property::References,
+ RfcHeader::ResentMessageId => Property::EmailIds,
_ => unreachable!(),
}
}
diff --git a/crates/jmap-proto/src/types/value.rs b/crates/jmap-proto/src/types/value.rs
index e3690068..90fdcb6e 100644
--- a/crates/jmap-proto/src/types/value.rs
+++ b/crates/jmap-proto/src/types/value.rs
@@ -47,6 +47,12 @@ pub enum SetValue {
ResultReference(ResultReference),
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum MaybePatchValue {
+ Value(Value),
+ Patch(Vec<Value>),
+}
+
#[derive(Debug, Clone)]
pub struct SetValueMap<T> {
pub values: Vec<T>,
@@ -161,7 +167,70 @@ impl Value {
.unwrap_bool_or_null("")?
.map(Value::Bool)
.unwrap_or(Value::Null)),
- _ => Value::parse::<String, String>(parser.next_token()?, parser),
+ _ => Value::parse::<ObjectProperty, String>(parser.next_token()?, parser),
+ }
+ }
+
+ pub fn unwrap_id(self) -> Id {
+ match self {
+ Value::Id(id) => id,
+ _ => panic!("Expected id"),
+ }
+ }
+
+ pub fn unwrap_bool(self) -> bool {
+ match self {
+ Value::Bool(b) => b,
+ _ => panic!("Expected bool"),
+ }
+ }
+
+ pub fn unwrap_keyword(self) -> Keyword {
+ match self {
+ Value::Keyword(k) => k,
+ _ => panic!("Expected keyword"),
+ }
+ }
+
+ pub fn try_unwrap_string(self) -> Option<String> {
+ match self {
+ Value::Text(s) => Some(s),
+ _ => None,
+ }
+ }
+
+ pub fn try_unwrap_object(self) -> Option<Object<Value>> {
+ match self {
+ Value::Object(o) => Some(o),
+ _ => None,
+ }
+ }
+
+ pub fn try_unwrap_list(self) -> Option<Vec<Value>> {
+ match self {
+ Value::List(l) => Some(l),
+ _ => None,
+ }
+ }
+
+ pub fn try_unwrap_date(self) -> Option<UTCDate> {
+ match self {
+ Value::Date(d) => Some(d),
+ _ => None,
+ }
+ }
+
+ pub fn try_unwrap_blob_id(self) -> Option<BlobId> {
+ match self {
+ Value::BlobId(b) => Some(b),
+ _ => None,
+ }
+ }
+
+ pub fn try_unwrap_uint(self) -> Option<u64> {
+ match self {
+ Value::UnsignedInt(u) => Some(u),
+ _ => None,
}
}
}