diff options
author | Mauro D <mauro@stalw.art> | 2023-04-28 08:35:53 +0000 |
---|---|---|
committer | Mauro D <mauro@stalw.art> | 2023-04-28 08:35:53 +0000 |
commit | c3a2b43ddbcd089b193eb33f26785f4736c92ee8 (patch) | |
tree | dd811092ba58f234c60e56706b25c98234e48f47 /crates/jmap-proto | |
parent | 46b5dc04253fe18748cfbdc77c68f0ec3e094efd (diff) |
Email/set create tests passing
Diffstat (limited to 'crates/jmap-proto')
-rw-r--r-- | crates/jmap-proto/src/error/set.rs | 65 | ||||
-rw-r--r-- | crates/jmap-proto/src/lib.rs | 105 | ||||
-rw-r--r-- | crates/jmap-proto/src/method/set.rs | 60 | ||||
-rw-r--r-- | crates/jmap-proto/src/response/references.rs | 49 | ||||
-rw-r--r-- | crates/jmap-proto/src/types/blob.rs | 6 | ||||
-rw-r--r-- | crates/jmap-proto/src/types/property.rs | 18 | ||||
-rw-r--r-- | crates/jmap-proto/src/types/value.rs | 71 |
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, } } } |