summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMauro D <mauro@stalw.art>2023-05-03 15:55:32 +0000
committerMauro D <mauro@stalw.art>2023-05-03 15:55:32 +0000
commit60fa410aa55f02d7956d9c5fcae8b8cc69f91627 (patch)
tree07bc4f742398683c944893be5ff50b6d692e85e2
parenta3a0396772cfbfe8573ad15851acd19cacdc6499 (diff)
Mailbox tests passing.
-rw-r--r--crates/jmap-proto/src/object/index.rs304
-rw-r--r--crates/jmap-proto/src/response/references.rs83
-rw-r--r--crates/jmap/src/api/http.rs2
-rw-r--r--crates/jmap/src/api/request.rs11
-rw-r--r--crates/jmap/src/email/import.rs5
-rw-r--r--crates/jmap/src/email/set.rs29
-rw-r--r--crates/jmap/src/mailbox/get.rs3
-rw-r--r--crates/jmap/src/mailbox/query.rs8
-rw-r--r--crates/jmap/src/mailbox/set.rs111
-rw-r--r--crates/store/src/backend/foundationdb/write.rs1
-rw-r--r--crates/store/src/backend/sqlite/write.rs3
-rw-r--r--crates/store/src/query/log.rs2
-rw-r--r--crates/store/src/query/mod.rs9
-rw-r--r--tests/src/jmap/email_get.rs21
-rw-r--r--tests/src/jmap/email_query.rs5
-rw-r--r--tests/src/jmap/email_set.rs51
-rw-r--r--tests/src/jmap/mailbox.rs3
-rw-r--r--tests/src/jmap/thread_get.rs19
-rw-r--r--tests/src/jmap/thread_merge.rs176
19 files changed, 327 insertions, 519 deletions
diff --git a/crates/jmap-proto/src/object/index.rs b/crates/jmap-proto/src/object/index.rs
index f74fe7d1..21701983 100644
--- a/crates/jmap-proto/src/object/index.rs
+++ b/crates/jmap-proto/src/object/index.rs
@@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::HashSet};
use store::{
fts::builder::ToTokens,
- write::{BatchBuilder, IntoOperations, Operation},
+ write::{BatchBuilder, BitmapFamily, IntoOperations, Operation},
Serialize, BM_TAG, HASH_EXACT,
};
@@ -124,7 +124,7 @@ impl IntoOperations for ObjectIndexBuilder {
}
(Some(current), None) => {
// Deletion
- build_batch(batch, self.index, &current, true);
+ build_batch(batch, self.index, &current, false);
batch.ops.push(Operation::Value {
field: Property::Value.into(),
family: 0,
@@ -149,190 +149,186 @@ fn merge_batch(
if current_value == &value {
continue;
}
- match index
- .iter()
- .find_map(|i| {
- if i.property == property {
- Some(i.index_as)
- } else {
- None
- }
- })
- .unwrap_or_default()
- {
- IndexAs::Text { tokenize, index } => {
- // Remove current text from index
- let mut add_tokens = HashSet::new();
- let mut remove_tokens = HashSet::new();
- if let Some(text) = current_value.as_string() {
- if index {
- batch.ops.push(Operation::Index {
- field: property.clone().into(),
- key: text.serialize(),
- set: false,
- });
- }
- if tokenize {
- remove_tokens = text.to_tokens();
- }
- }
- // Add new text to index
- if let Some(text) = value.as_string() {
- if index {
- batch.ops.push(Operation::Index {
- field: property.clone().into(),
- key: text.serialize(),
- set: true,
- });
+ for index_property in index {
+ if index_property.property != property {
+ continue;
+ }
+ match index_property.index_as {
+ IndexAs::Text { tokenize, index } => {
+ // Remove current text from index
+ let mut add_tokens = HashSet::new();
+ let mut remove_tokens = HashSet::new();
+ if let Some(text) = current_value.as_string() {
+ if index {
+ batch.ops.push(Operation::Index {
+ field: property.clone().into(),
+ key: text.serialize(),
+ set: false,
+ });
+ }
+ if tokenize {
+ remove_tokens = text.to_tokens();
+ }
}
- if tokenize {
- for token in text.to_tokens() {
- if !remove_tokens.remove(&token) {
- add_tokens.insert(token);
+
+ // Add new text to index
+ if let Some(text) = value.as_string() {
+ if index {
+ batch.ops.push(Operation::Index {
+ field: property.clone().into(),
+ key: text.serialize(),
+ set: true,
+ });
+ }
+ if tokenize {
+ for token in text.to_tokens() {
+ if !remove_tokens.remove(&token) {
+ add_tokens.insert(token);
+ }
}
}
}
- }
- // Update tokens
- for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
- for token in token {
- batch.ops.push(Operation::hash(
- &token,
- HASH_EXACT,
- property.clone().into(),
- set,
- ));
+ // Update tokens
+ for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
+ for token in token {
+ batch.ops.push(Operation::hash(
+ &token,
+ HASH_EXACT,
+ property.clone().into(),
+ set,
+ ));
+ }
}
}
- }
- IndexAs::TextList { tokenize, index } => {
- let mut add_tokens = HashSet::new();
- let mut remove_tokens = HashSet::new();
- let mut add_values = HashSet::new();
- let mut remove_values = HashSet::new();
-
- // Remove current text from index
- if let Some(current_values) = current_value.as_list() {
- for current_value in current_values {
- if let Some(text) = current_value.as_string() {
- if index {
- remove_values.insert(text);
- }
- if tokenize {
- remove_tokens.extend(text.to_tokens());
+ IndexAs::TextList { tokenize, index } => {
+ let mut add_tokens = HashSet::new();
+ let mut remove_tokens = HashSet::new();
+ let mut add_values = HashSet::new();
+ let mut remove_values = HashSet::new();
+
+ // Remove current text from index
+ if let Some(current_values) = current_value.as_list() {
+ for current_value in current_values {
+ if let Some(text) = current_value.as_string() {
+ if index {
+ remove_values.insert(text);
+ }
+ if tokenize {
+ remove_tokens.extend(text.to_tokens());
+ }
}
}
}
- }
- // Add new text to index
- if let Some(values) = value.as_list() {
- for value in values {
- if let Some(text) = value.as_string() {
- if index && !remove_values.remove(text) {
- add_values.insert(text);
- }
- if tokenize {
- for token in text.to_tokens() {
- if !remove_tokens.remove(&token) {
- add_tokens.insert(token);
+ // Add new text to index
+ if let Some(values) = value.as_list() {
+ for value in values {
+ if let Some(text) = value.as_string() {
+ if index && !remove_values.remove(text) {
+ add_values.insert(text);
+ }
+ if tokenize {
+ for token in text.to_tokens() {
+ if !remove_tokens.remove(&token) {
+ add_tokens.insert(token);
+ }
}
}
}
}
}
- }
- // Update index
- for (values, set) in [(add_values, true), (remove_values, false)] {
- for value in values {
+ // Update index
+ for (values, set) in [(add_values, true), (remove_values, false)] {
+ for value in values {
+ batch.ops.push(Operation::Index {
+ field: property.clone().into(),
+ key: value.serialize(),
+ set,
+ });
+ }
+ }
+
+ // Update tokens
+ for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
+ for token in token {
+ batch.ops.push(Operation::hash(
+ &token,
+ HASH_EXACT,
+ property.clone().into(),
+ set,
+ ));
+ }
+ }
+ }
+ index_as @ (IndexAs::Integer | IndexAs::LongInteger) => {
+ if let Some(current_value) = current_value.try_cast_uint() {
batch.ops.push(Operation::Index {
field: property.clone().into(),
- key: value.serialize(),
- set,
+ key: current_value.into_index(index_as),
+ set: false,
});
}
- }
-
- // Update tokens
- for (token, set) in [(add_tokens, true), (remove_tokens, false)] {
- for token in token {
- batch.ops.push(Operation::hash(
- &token,
- HASH_EXACT,
- property.clone().into(),
- set,
- ));
+ if let Some(value) = value.try_cast_uint() {
+ batch.ops.push(Operation::Index {
+ field: property.clone().into(),
+ key: value.into_index(index_as),
+ set: true,
+ });
}
}
- }
- index_as @ (IndexAs::Integer | IndexAs::LongInteger) => {
- if let Some(current_value) = current_value.try_cast_uint() {
- batch.ops.push(Operation::Index {
- field: property.clone().into(),
- key: current_value.into_index(index_as),
- set: false,
- });
- }
- if let Some(value) = value.try_cast_uint() {
- batch.ops.push(Operation::Index {
- field: property.clone().into(),
- key: value.into_index(index_as),
- set: false,
- });
- }
- }
- IndexAs::IntegerList => {
- let mut add_values = HashSet::new();
- let mut remove_values = HashSet::new();
-
- if let Some(current_values) = current_value.as_list() {
- for current_value in current_values {
- if let Some(current_value) = current_value.try_cast_uint() {
- remove_values.insert(current_value);
+ IndexAs::IntegerList => {
+ let mut add_values = HashSet::new();
+ let mut remove_values = HashSet::new();
+
+ if let Some(current_values) = current_value.as_list() {
+ for current_value in current_values {
+ if let Some(current_value) = current_value.try_cast_uint() {
+ remove_values.insert(current_value);
+ }
}
}
- }
- if let Some(values) = value.as_list() {
- for value in values {
- if let Some(value) = value.try_cast_uint() {
- if !remove_values.remove(&value) {
- add_values.insert(value);
+ if let Some(values) = value.as_list() {
+ for value in values {
+ if let Some(value) = value.try_cast_uint() {
+ if !remove_values.remove(&value) {
+ add_values.insert(value);
+ }
}
}
}
- }
- for (values, set) in [(add_values, true), (remove_values, false)] {
- for value in values {
- batch.ops.push(Operation::Index {
+ for (values, set) in [(add_values, true), (remove_values, false)] {
+ for value in values {
+ batch.ops.push(Operation::Index {
+ field: property.clone().into(),
+ key: (value as u32).serialize(),
+ set,
+ });
+ }
+ }
+ }
+ IndexAs::HasProperty => {
+ if current_value == &Value::Null {
+ batch.ops.push(Operation::Bitmap {
+ family: ().family(),
field: property.clone().into(),
- key: (value as u32).serialize(),
- set,
+ key: vec![],
+ set: true,
+ });
+ } else if value == Value::Null {
+ batch.ops.push(Operation::Bitmap {
+ family: ().family(),
+ field: property.clone().into(),
+ key: vec![],
+ set: false,
});
}
}
+ IndexAs::None => (),
}
- IndexAs::HasProperty => {
- if current_value == &Value::Null {
- batch.ops.push(Operation::Bitmap {
- family: BM_TAG,
- field: property.clone().into(),
- key: vec![],
- set: true,
- });
- } else if value == Value::Null {
- batch.ops.push(Operation::Bitmap {
- family: BM_TAG,
- field: property.clone().into(),
- key: vec![],
- set: false,
- });
- }
- }
- IndexAs::None => (),
}
if value != Value::Null {
current.set(property, value);
@@ -373,7 +369,7 @@ fn build_batch(
&token,
HASH_EXACT,
(&item.property).into(),
- true,
+ set,
));
}
}
@@ -403,7 +399,7 @@ fn build_batch(
&token,
HASH_EXACT,
(&item.property).into(),
- true,
+ set,
));
}
}
@@ -440,7 +436,7 @@ fn build_batch(
}
(value, IndexAs::HasProperty) if value != &Value::Null => {
batch.ops.push(Operation::Bitmap {
- family: BM_TAG,
+ family: ().family(),
field: (&item.property).into(),
key: vec![],
set,
diff --git a/crates/jmap-proto/src/response/references.rs b/crates/jmap-proto/src/response/references.rs
index 2e67e2a9..590c973d 100644
--- a/crates/jmap-proto/src/response/references.rs
+++ b/crates/jmap-proto/src/response/references.rs
@@ -4,6 +4,7 @@ use utils::map::vec_map::VecMap;
use crate::{
error::{method::MethodError, set::SetError},
+ method::set::SetResponse,
object::Object,
request::{
reference::{MaybeReference, ResultReference},
@@ -305,52 +306,52 @@ impl Response {
}
}
-impl Object<SetValue> {
- pub fn iterate_and_eval_references(
- self,
- response: &Response,
- ) -> impl Iterator<Item = Result<(Property, MaybePatchValue), SetError>> + '_ {
- // Resolve id references, which were previously validated.
- // If the ID is not found it means that set failed for that id, so we return a setError
- // instead of failing the entire request with a MethodError::InvalidResultReference.
- 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(SetError::not_found()
- .with_description(format!("Id reference {id_ref:?} not found.")))
- }
+impl SetResponse {
+ pub fn eval_object_references(&self, set_value: SetValue) -> Result<MaybePatchValue, SetError> {
+ match set_value {
+ SetValue::Value(value) => Ok(MaybePatchValue::Value(value)),
+ SetValue::Patch(patch) => Ok(MaybePatchValue::Patch(patch)),
+ SetValue::IdReference(MaybeReference::Reference(id_ref)) => {
+ if let Some(Value::Id(id)) = self
+ .created
+ .get(&id_ref)
+ .and_then(|obj| obj.properties.get(&Property::Id))
+ {
+ Ok(MaybePatchValue::Value(Value::Id(*id)))
+ } else {
+ Err(SetError::not_found()
+ .with_description(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(SetError::not_found().with_description(format!(
- "Id reference {id_ref:?} not found."
- )));
- }
+ }
+ SetValue::IdReference(MaybeReference::Value(id)) => {
+ Ok(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(Value::Id(id)) = self
+ .created
+ .get(&id_ref)
+ .and_then(|obj| obj.properties.get(&Property::Id))
+ {
+ ids.push(Value::Id(*id));
+ } else {
+ return Err(SetError::not_found().with_description(format!(
+ "Id reference {id_ref:?} not found."
+ )));
}
}
}
- Ok((property, MaybePatchValue::Value(Value::List(ids))))
}
- _ => unreachable!(),
- })
+ Ok(MaybePatchValue::Value(Value::List(ids)))
+ }
+ _ => unreachable!(),
+ }
}
}
diff --git a/crates/jmap/src/api/http.rs b/crates/jmap/src/api/http.rs
index 50c398ee..d8a159c3 100644
--- a/crates/jmap/src/api/http.rs
+++ b/crates/jmap/src/api/http.rs
@@ -39,7 +39,7 @@ impl JMAP {
("", &Method::POST) => {
return match fetch_body(req, self.config.request_max_size).await {
Ok(bytes) => {
- let delete = "fd";
+ //let delete = "fd";
//println!("<- {}", String::from_utf8_lossy(&bytes));
match self.handle_request(&bytes).await {
diff --git a/crates/jmap/src/api/request.rs b/crates/jmap/src/api/request.rs
index c1d696c9..d5521a1d 100644
--- a/crates/jmap/src/api/request.rs
+++ b/crates/jmap/src/api/request.rs
@@ -53,11 +53,10 @@ impl JMAP {
query::RequestArguments::Principal => todo!(),
},
RequestMethod::Set(mut req) => match req.take_arguments() {
- set::RequestArguments::Email => self.email_set(req, &response).await.into(),
- set::RequestArguments::Mailbox(arguments) => self
- .mailbox_set(req.with_arguments(arguments), &response)
- .await
- .into(),
+ set::RequestArguments::Email => self.email_set(req).await.into(),
+ set::RequestArguments::Mailbox(arguments) => {
+ self.mailbox_set(req.with_arguments(arguments)).await.into()
+ }
set::RequestArguments::Identity => todo!(),
set::RequestArguments::EmailSubmission(_) => todo!(),
set::RequestArguments::PushSubscription => todo!(),
@@ -65,7 +64,7 @@ impl JMAP {
set::RequestArguments::VacationResponse => todo!(),
set::RequestArguments::Principal => todo!(),
},
- RequestMethod::Changes(_) => todo!(),
+ RequestMethod::Changes(req) => self.changes(req).await.into(),
RequestMethod::Copy(_) => todo!(),
RequestMethod::CopyBlob(_) => todo!(),
RequestMethod::ImportEmail(req) => self.email_import(req).await.into(),
diff --git a/crates/jmap/src/email/import.rs b/crates/jmap/src/email/import.rs
index 5d2ad2d4..ecc009ce 100644
--- a/crates/jmap/src/email/import.rs
+++ b/crates/jmap/src/email/import.rs
@@ -47,8 +47,7 @@ impl JMAP {
);
continue;
}
- let enable = "true";
- /*for mailbox_id in &mailbox_ids {
+ for mailbox_id in &mailbox_ids {
if !valid_mailbox_ids.contains(*mailbox_id) {
not_created.append(
id,
@@ -58,7 +57,7 @@ impl JMAP {
);
continue 'outer;
}
- }*/
+ }
// Fetch raw message to import
let raw_message = match self.blob_download(&email.blob_id, account_id).await {
diff --git a/crates/jmap/src/email/set.rs b/crates/jmap/src/email/set.rs
index 8692e8e3..39f67061 100644
--- a/crates/jmap/src/email/set.rs
+++ b/crates/jmap/src/email/set.rs
@@ -7,7 +7,6 @@ use jmap_proto::{
},
method::set::{RequestArguments, SetRequest, SetResponse},
object::Object,
- response::Response,
types::{
collection::Collection,
id::Id,
@@ -46,7 +45,6 @@ impl JMAP {
pub async fn email_set(
&self,
mut request: SetRequest<RequestArguments>,
- response: &Response,
) -> Result<SetResponse, MethodError> {
// Prepare response
let account_id = request.account_id.document_id();
@@ -60,13 +58,6 @@ impl JMAP {
.await?
.unwrap_or_default();
- let remove = "fdf";
- mailbox_ids.insert(0);
- mailbox_ids.insert(1);
- mailbox_ids.insert(2);
- mailbox_ids.insert(3);
- mailbox_ids.insert(4);
-
let will_destroy = request.unwrap_destroy();
// Process creates
@@ -107,15 +98,15 @@ impl JMAP {
let mut size_attachments = 0;
// Parse properties
- for item in object.iterate_and_eval_references(response) {
- let item = match item {
- Ok(item) => item,
+ for (property, value) in object.properties {
+ let value = match set_response.eval_object_references(value) {
+ Ok(value) => value,
Err(err) => {
set_response.not_created.append(id, err);
continue 'create;
}
};
- match item {
+ match (property, value) {
(Property::MailboxIds, MaybePatchValue::Value(Value::List(ids))) => {
mailboxes = ids
.into_iter()
@@ -736,15 +727,15 @@ impl JMAP {
.with_account_id(account_id)
.with_collection(Collection::Email);
- for item in object.iterate_and_eval_references(response) {
- let item = match item {
- Ok(item) => item,
+ for (property, value) in object.properties {
+ let value = match set_response.eval_object_references(value) {
+ Ok(value) => value,
Err(err) => {
set_response.not_updated.append(id, err);
continue 'update;
}
};
- match item {
+ match (property, value) {
(Property::MailboxIds, MaybePatchValue::Value(Value::List(ids))) => {
mailboxes.set(
ids.into_iter()
@@ -899,9 +890,7 @@ impl JMAP {
set_response.destroyed.push(destroy_id);
}
Err(err) => {
- set_response
- .not_destroyed
- .append(destroy_id, SetError::not_found());
+ set_response.not_destroyed.append(destroy_id, err);
}
}
} else {
diff --git a/crates/jmap/src/mailbox/get.rs b/crates/jmap/src/mailbox/get.rs
index 42052b63..f992ad78 100644
--- a/crates/jmap/src/mailbox/get.rs
+++ b/crates/jmap/src/mailbox/get.rs
@@ -181,6 +181,9 @@ impl JMAP {
mailbox.append(property.clone(), value);
}
+
+ // Add result to response
+ response.list.push(mailbox);
}
Ok(response)
}
diff --git a/crates/jmap/src/mailbox/query.rs b/crates/jmap/src/mailbox/query.rs
index ccda5836..f9069a4e 100644
--- a/crates/jmap/src/mailbox/query.rs
+++ b/crates/jmap/src/mailbox/query.rs
@@ -47,11 +47,7 @@ impl JMAP {
}
Filter::Role(role) => {
if let Some(role) = role {
- filters.push(query::Filter::has_text(
- Property::Role,
- &role,
- Language::None,
- ));
+ filters.push(query::Filter::eq(Property::Role, role));
} else {
filters.push(query::Filter::Not);
filters.push(query::Filter::is_in_bitmap(Property::Role, ()));
@@ -170,7 +166,7 @@ impl JMAP {
for comparator in request
.sort
.and_then(|s| if !s.is_empty() { s.into() } else { None })
- .unwrap_or_else(|| vec![Comparator::descending(SortProperty::ParentId)])
+ .unwrap_or_else(|| vec![Comparator::ascending(SortProperty::ParentId)])
{
comparators.push(match comparator.property {
SortProperty::Name => {
diff --git a/crates/jmap/src/mailbox/set.rs b/crates/jmap/src/mailbox/set.rs
index e16b0d03..a3dc3ea3 100644
--- a/crates/jmap/src/mailbox/set.rs
+++ b/crates/jmap/src/mailbox/set.rs
@@ -9,7 +9,6 @@ use jmap_proto::{
mailbox::SetArguments,
Object,
},
- response::Response,
types::{
collection::Collection,
id::Id,
@@ -27,10 +26,10 @@ use crate::JMAP;
use super::{INBOX_ID, TRASH_ID};
-struct SetContext<'x> {
+struct SetContext {
account_id: u32,
primary_id: u32,
- response: &'x Response,
+ set_response: SetResponse,
mailbox_ids: RoaringBitmap,
will_destroy: Vec<Id>,
}
@@ -42,12 +41,10 @@ static SCHEMA: &[IndexProperty] = &[
index: true,
})
.required(),
- IndexProperty::new(Property::Role)
- .index_as(IndexAs::Text {
- tokenize: false,
- index: true,
- })
- .required(),
+ IndexProperty::new(Property::Role).index_as(IndexAs::Text {
+ tokenize: false,
+ index: true,
+ }),
IndexProperty::new(Property::Role).index_as(IndexAs::HasProperty),
IndexProperty::new(Property::ParentId).index_as(IndexAs::Integer),
IndexProperty::new(Property::SortOrder).index_as(IndexAs::Integer),
@@ -59,18 +56,16 @@ impl JMAP {
pub async fn mailbox_set(
&self,
mut request: SetRequest<SetArguments>,
- response: &Response,
) -> Result<SetResponse, MethodError> {
// Prepare response
let account_id = request.account_id.document_id();
- let mut set_response = self
- .prepare_set_response(&request, Collection::Mailbox)
- .await?;
let on_destroy_remove_emails = request.arguments.on_destroy_remove_emails.unwrap_or(false);
let mut ctx = SetContext {
account_id,
primary_id: account_id,
- response,
+ set_response: self
+ .prepare_set_response(&request, Collection::Mailbox)
+ .await?,
mailbox_ids: self
.get_document_ids(account_id, Collection::Mailbox)
.await?
@@ -104,10 +99,10 @@ impl JMAP {
MethodError::ServerPartialFail
})?;
- set_response.created(id, document_id);
+ ctx.set_response.created(id, document_id);
}
Err(err) => {
- set_response.not_created.append(id, err);
+ ctx.set_response.not_created.append(id, err);
continue 'create;
}
}
@@ -143,7 +138,7 @@ impl JMAP {
match self.store.write(batch.build()).await {
Ok(_) => (),
Err(store::Error::AssertValueFailed) => {
- set_response.not_updated.append(id, SetError::forbidden().with_description(
+ ctx.set_response.not_updated.append(id, SetError::forbidden().with_description(
"Another process modified this mailbox, please try again.",
));
continue 'update;
@@ -159,15 +154,17 @@ impl JMAP {
}
}
}
- set_response.updated.append(id, None);
+ ctx.set_response.updated.append(id, None);
}
Err(err) => {
- set_response.not_updated.append(id, err);
+ ctx.set_response.not_updated.append(id, err);
continue 'update;
}
}
} else {
- set_response.not_updated.append(id, SetError::not_found());
+ ctx.set_response
+ .not_updated
+ .append(id, SetError::not_found());
}
}
@@ -175,8 +172,9 @@ impl JMAP {
'destroy: for id in ctx.will_destroy {
let document_id = id.document_id();
// Internal folders cannot be deleted
+ #[cfg(not(feature = "test_mode"))]
if document_id == INBOX_ID || document_id == TRASH_ID {
- set_response.not_destroyed.append(
+ ctx.set_response.not_destroyed.append(
id,
SetError::forbidden()
.with_description("You are not allowed to delete Inbox or Trash folders."),
@@ -195,7 +193,7 @@ impl JMAP {
.results
.is_empty()
{
- set_response.not_destroyed.append(
+ ctx.set_response.not_destroyed.append(
id,
SetError::new(SetErrorType::MailboxHasChild)
.with_description("Mailbox has at least one children."),
@@ -262,7 +260,7 @@ impl JMAP {
Id::from_parts(thread_id, message_id),
),
Err(store::Error::AssertValueFailed) => {
- set_response.not_destroyed.append(
+ ctx.set_response.not_destroyed.append(
id,
SetError::forbidden().with_description(
concat!("Another process modified a message in this mailbox ",
@@ -296,7 +294,7 @@ impl JMAP {
} else {
// Delete message
if let Ok(mut change) =
- self.email_delete(account_id, document_id).await?
+ self.email_delete(account_id, message_id).await?
{
change.changes.remove(&(Collection::Mailbox as u8));
changes.merge(change);
@@ -314,7 +312,7 @@ impl JMAP {
}
}
} else {
- set_response.not_destroyed.append(
+ ctx.set_response.not_destroyed.append(
id,
SetError::new(SetErrorType::MailboxHasEmail)
.with_description("Mailbox is not empty."),
@@ -342,9 +340,12 @@ impl JMAP {
.custom(ObjectIndexBuilder::new(SCHEMA).with_current(mailbox.inner));
match self.store.write(batch.build()).await {
- Ok(_) => changes.log_delete(Collection::Mailbox, document_id),
+ Ok(_) => {
+ changes.log_delete(Collection::Mailbox, document_id);
+ ctx.set_response.destroyed.push(id);
+ }
Err(store::Error::AssertValueFailed) => {
- set_response.not_destroyed.append(
+ ctx.set_response.not_destroyed.append(
id,
SetError::forbidden().with_description(concat!(
"Another process modified this mailbox ",
@@ -364,16 +365,18 @@ impl JMAP {
}
}
} else {
- set_response.not_destroyed.append(id, SetError::not_found());
+ ctx.set_response
+ .not_destroyed
+ .append(id, SetError::not_found());
}
}
// Write changes
if !changes.is_empty() {
- set_response.new_state = self.commit_changes(account_id, changes).await?.into();
+ ctx.set_response.new_state = self.commit_changes(account_id, changes).await?.into();
}
- Ok(set_response)
+ Ok(ctx.set_response)
}
#[allow(clippy::blocks_in_if_conditions)]
@@ -381,22 +384,22 @@ impl JMAP {
&self,
changes_: Object<SetValue>,
update: Option<(u32, Object<Value>)>,
- ctx: &SetContext<'_>,
+ ctx: &SetContext,
) -> Result<Result<ObjectIndexBuilder, SetError>, MethodError> {
// Parse properties
let mut changes = Object::with_capacity(changes_.properties.len());
- for item in changes_.iterate_and_eval_references(ctx.response) {
- let item = match item {
- Ok(item) => item,
+ for (property, value) in changes_.properties {
+ let value = match ctx.set_response.eval_object_references(value) {
+ Ok(value) => value,
Err(err) => {
return Ok(Err(err));
}
};
- match item {
+ let value = match (&property, value) {
(Property::Name, MaybePatchValue::Value(Value::Text(value))) => {
let value = value.trim();
if !value.is_empty() && value.len() < self.config.mailbox_name_max_len {
- changes.append(Property::Name, Value::Text(value.to_string()));
+ Value::Text(value.to_string())
} else {
return Ok(Err(SetError::invalid_properties()
.with_property(Property::Name)
@@ -420,11 +423,9 @@ impl JMAP {
.with_description("Parent ID does not exist.")));
}
- changes.append(Property::ParentId, Value::Id((parent_id + 1).into()));
- }
- (Property::ParentId, MaybePatchValue::Value(Value::Null)) => {
- changes.append(Property::ParentId, Value::Id(0u64.into()))
+ Value::Id((parent_id + 1).into())
}
+ (Property::ParentId, MaybePatchValue::Value(Value::Null)) => Value::Id(0u64.into()),
(Property::IsSubscribed, MaybePatchValue::Value(Value::Bool(subscribe))) => {
let fixme = "true";
let account_id = Value::Id(ctx.primary_id.into());
@@ -459,16 +460,14 @@ impl JMAP {
}
}
}
- changes.append(
- Property::IsSubscribed,
- if let Some(new_value) = new_value {
- new_value
- } else if subscribe {
- Value::List(vec![account_id])
- } else {
- continue;
- },
- );
+
+ if let Some(new_value) = new_value {
+ new_value
+ } else if subscribe {
+ Value::List(vec![account_id])
+ } else {
+ continue;
+ }
}
(Property::Role, MaybePatchValue::Value(Value::Text(value))) => {
let role = value.trim().to_lowercase();
@@ -477,28 +476,28 @@ impl JMAP {
]
.contains(&role.as_str())
{
- changes.append(Property::Role, Value::Text(role));
+ Value::Text(role)
} else {
return Ok(Err(SetError::invalid_properties()
.with_property(Property::Role)
.with_description(format!("Invalid role {role:?}."))));
}
}
- (Property::Role, MaybePatchValue::Value(Value::Null)) => {
- changes.append(Property::Role, Value::Null)
- }
+ (Property::Role, MaybePatchValue::Value(Value::Null)) => Value::Null,
(Property::SortOrder, MaybePatchValue::Value(Value::UnsignedInt(value))) => {
- changes.append(Property::SortOrder, Value::UnsignedInt(value));
+ Value::UnsignedInt(value)
}
(Property::Acl, _) => {
todo!()
}
- (property, _) => {
+ _ => {
return Ok(Err(SetError::invalid_properties()
.with_property(property)
.with_description("Invalid property or value.".to_string())))
}
};
+
+ changes.append(property, value);
}
// Validate depth and circular parent-child relationship
diff --git a/crates/store/src/backend/foundationdb/write.rs b/crates/store/src/backend/foundationdb/write.rs
index 5331e402..ec2ae66f 100644
--- a/crates/store/src/backend/foundationdb/write.rs
+++ b/crates/store/src/backend/foundationdb/write.rs
@@ -354,6 +354,7 @@ impl Store {
}
}
+ #[cfg(feature = "test_mode")]
pub async fn destroy(&self) {
let trx = self.db.create_trx().unwrap();
trx.clear_range(&[0u8], &[u8::MAX]);
diff --git a/crates/store/src/backend/sqlite/write.rs b/crates/store/src/backend/sqlite/write.rs
index 8dd85458..109840ee 100644
--- a/crates/store/src/backend/sqlite/write.rs
+++ b/crates/store/src/backend/sqlite/write.rs
@@ -129,7 +129,7 @@ impl Store {
trx.prepare_cached("INSERT OR REPLACE INTO i (k) VALUES (?)")?
.execute([&key])?;
} else {
- trx.prepare_cached("DELETE FROM v WHERE k = ?")?
+ trx.prepare_cached("DELETE FROM i WHERE k = ?")?
.execute([&key])?;
}
}
@@ -230,6 +230,7 @@ impl Store {
.await
}
+ #[cfg(feature = "test_mode")]
pub async fn destroy(&self) {
use crate::{
SUBSPACE_ACLS, SUBSPACE_BITMAPS, SUBSPACE_BLOBS, SUBSPACE_INDEXES, SUBSPACE_LOGS,
diff --git a/crates/store/src/query/log.rs b/crates/store/src/query/log.rs
index d74d4993..2e117597 100644
--- a/crates/store/src/query/log.rs
+++ b/crates/store/src/query/log.rs
@@ -71,7 +71,7 @@ impl Store {
move |changelog, key, value| {
let change_id =
key.deserialize_be_u64(key.len() - std::mem::size_of::<u64>())?;
- if !is_inclusive || change_id != from_change_id {
+ if is_inclusive || change_id != from_change_id {
if changelog.changes.is_empty() {
changelog.from_change_id = change_id;
}
diff --git a/crates/store/src/query/mod.rs b/crates/store/src/query/mod.rs
index 9a6ff408..1901f841 100644
--- a/crates/store/src/query/mod.rs
+++ b/crates/store/src/query/mod.rs
@@ -8,7 +8,7 @@ use roaring::RoaringBitmap;
use crate::{
fts::{lang::LanguageDetector, Language},
write::BitmapFamily,
- BitmapKey, Serialize, BM_DOCUMENT_IDS,
+ BitmapKey, Serialize, Store, BM_DOCUMENT_IDS,
};
#[derive(Debug, Clone, Copy)]
@@ -235,3 +235,10 @@ impl BitmapKey<&'static [u8]> {
}
}
}
+
+#[cfg(feature = "test_mode")]
+impl Store {
+ pub async fn assert_is_empty(&self) {
+ todo!()
+ }
+}
diff --git a/tests/src/jmap/email_get.rs b/tests/src/jmap/email_get.rs
index 1bfbc288..28623390 100644
--- a/tests/src/jmap/email_get.rs
+++ b/tests/src/jmap/email_get.rs
@@ -21,7 +21,7 @@
* for more details.
*/
-use std::{fs, path::PathBuf, sync::Arc, time::Instant};
+use std::{fs, path::PathBuf, sync::Arc};
use jmap::JMAP;
use jmap_client::{
@@ -41,14 +41,12 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
test_dir.push("resources");
test_dir.push("jmap_mail_get");
- let coco1 = "implement";
- let mailbox_id = "a".to_string();
- /*let mailbox_id = client
- .set_default_account_id(Id::new(1).to_string())
- .mailbox_create("JMAP Get", None::<String>, Role::None)
- .await
- .unwrap()
- .take_id();*/
+ let mailbox_id = client
+ .set_default_account_id(Id::new(1).to_string())
+ .mailbox_create("JMAP Get", None::<String>, Role::None)
+ .await
+ .unwrap()
+ .take_id();
for file_name in fs::read_dir(&test_dir).unwrap() {
let mut file_name = file_name.as_ref().unwrap().path();
@@ -182,10 +180,9 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
}
}
- let coco = "implement";
- //client.mailbox_destroy(&mailbox_id, true).await.unwrap();
+ client.mailbox_destroy(&mailbox_id, true).await.unwrap();
- //server.store.assert_is_empty();
+ server.store.assert_is_empty().await;
}
pub fn all_headers() -> Vec<email::Property> {
diff --git a/tests/src/jmap/email_query.rs b/tests/src/jmap/email_query.rs
index c128f5a2..45380f32 100644
--- a/tests/src/jmap/email_query.rs
+++ b/tests/src/jmap/email_query.rs
@@ -77,8 +77,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client, insert: bool) {
query_options(client).await;
println!("Deleting all messages...");
- let implement = "fds";
- /*let mut request = client.build();
+ let mut request = client.build();
let result_ref = request.query_email().result_reference();
request.set_email().destroy_ref(result_ref);
let response = request.send().await.unwrap();
@@ -89,7 +88,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client, insert: bool) {
.unwrap_set_email()
.unwrap();
- server.store.assert_is_empty();*/
+ server.store.assert_is_empty().await;
}
pub async fn query(client: &mut Client) {
diff --git a/tests/src/jmap/email_set.rs b/tests/src/jmap/email_set.rs
index 87091101..b0826d8c 100644
--- a/tests/src/jmap/email_set.rs
+++ b/tests/src/jmap/email_set.rs
@@ -38,22 +38,19 @@ use super::{find_values, replace_blob_ids, replace_boundaries, replace_values};
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email Set tests...");
- let mailbox_id = "a";
- let coco = "fix";
- /*client
- .set_default_account_id(Id::new(1).to_string())
- .mailbox_create("JMAP Set", None::<String>, Role::None)
- .await
- .unwrap()
- .take_id();*/
+ let mailbox_id = client
+ .set_default_account_id(Id::new(1).to_string())
+ .mailbox_create("JMAP Set", None::<String>, Role::None)
+ .await
+ .unwrap()
+ .take_id();
create(client, &mailbox_id).await;
update(client, &mailbox_id).await;
- let coco = "fd";
- //client.mailbox_destroy(&mailbox_id, true).await.unwrap();
+ client.mailbox_destroy(&mailbox_id, true).await.unwrap();
- //server.store.assert_is_empty();
+ server.store.assert_is_empty().await;
}
async fn create(client: &mut Client, mailbox_id: &str) {
@@ -194,21 +191,18 @@ async fn update(client: &mut Client, root_mailbox_id: &str) {
.unwrap();
// Create two test mailboxes
- let fix = "true";
- let test_mailbox1_id = "c".to_string();
- /*client
- .set_default_account_id(Id::new(1).to_string())
- .mailbox_create("Test 1", None::<String>, Role::None)
- .await
- .unwrap()
- .take_id();*/
- let test_mailbox2_id = "d".to_string();
- /*client
- .set_default_account_id(Id::new(1).to_string())
- .mailbox_create("Test 2", None::<String>, Role::None)
- .await
- .unwrap()
- .take_id();*/
+ let test_mailbox1_id = client
+ .set_default_account_id(Id::new(1).to_string())
+ .mailbox_create("Test 1", None::<String>, Role::None)
+ .await
+ .unwrap()
+ .take_id();
+ let test_mailbox2_id = client
+ .set_default_account_id(Id::new(1).to_string())
+ .mailbox_create("Test 2", None::<String>, Role::None)
+ .await
+ .unwrap()
+ .take_id();
// Set keywords and mailboxes
let mut request = client.build();
@@ -309,15 +303,14 @@ async fn update(client: &mut Client, root_mailbox_id: &str) {
assert_eq!(request.send_get_email().await.unwrap().not_found().len(), 2);
// Destroy test mailboxes
- let fix = "true";
- /*client
+ client
.mailbox_destroy(&test_mailbox1_id, true)
.await
.unwrap();
client
.mailbox_destroy(&test_mailbox2_id, true)
.await
- .unwrap();*/
+ .unwrap();
}
pub async fn assert_email_properties(
diff --git a/tests/src/jmap/mailbox.rs b/tests/src/jmap/mailbox.rs
index 6ddfc463..ad22582f 100644
--- a/tests/src/jmap/mailbox.rs
+++ b/tests/src/jmap/mailbox.rs
@@ -611,8 +611,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
client.mailbox_destroy(&id, true).await.unwrap();
}
- let todo = "yes";
- //server.store.assert_is_empty();
+ server.store.assert_is_empty().await;
}
async fn create_test_mailboxes(client: &mut Client) -> AHashMap<String, String> {
diff --git a/tests/src/jmap/thread_get.rs b/tests/src/jmap/thread_get.rs
index 58c1696d..b20c758f 100644
--- a/tests/src/jmap/thread_get.rs
+++ b/tests/src/jmap/thread_get.rs
@@ -7,14 +7,12 @@ use jmap_proto::types::id::Id;
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email Thread tests...");
- let mailbox_id = "a".to_string();
- let implementer = "fd";
- /*let mailbox_id = client
- .set_default_account_id(Id::new(1).to_string())
- .mailbox_create("JMAP Get", None::<String>, Role::None)
- .await
- .unwrap()
- .take_id();*/
+ let mailbox_id = client
+ .set_default_account_id(Id::new(1).to_string())
+ .mailbox_create("JMAP Get", None::<String>, Role::None)
+ .await
+ .unwrap()
+ .take_id();
let mut expected_result = vec!["".to_string(); 5];
let mut thread_id = "".to_string();
@@ -43,8 +41,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
expected_result
);
- let implement = "fd";
- //client.mailbox_destroy(&mailbox_id, true).await.unwrap();
+ client.mailbox_destroy(&mailbox_id, true).await.unwrap();
- //server.store.assert_is_empty();
+ server.store.assert_is_empty().await;
}
diff --git a/tests/src/jmap/thread_merge.rs b/tests/src/jmap/thread_merge.rs
index cc2206ae..0cccfd70 100644
--- a/tests/src/jmap/thread_merge.rs
+++ b/tests/src/jmap/thread_merge.rs
@@ -7,9 +7,6 @@ use store::ahash::{AHashMap, AHashSet};
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
println!("Running Email Merge Threads tests...");
-
- simple_test(client).await;
-
let mut all_mailboxes = AHashMap::default();
for (base_test_num, test) in [test_1(), test_2(), test_3()].iter().enumerate() {
@@ -23,16 +20,14 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
let mut mailbox_ids = Vec::with_capacity(6);
for test_num in 0..=5 {
- mailbox_ids.push(char::from(b'a' + test_num as u8).to_string());
- let coco = "fd";
- /*mailbox_ids.push(
+ mailbox_ids.push(
client
.set_default_account_id(Id::new((base_test_num + test_num) as u64).to_string())
.mailbox_create("Thread nightmare", None::<String>, Role::None)
.await
.unwrap()
.take_id(),
- );*/
+ );
}
for message in &messages {
@@ -177,8 +172,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
}
// Delete all messages and make sure no keys are left in the store.
- let implement = "fdf";
- /*for (base_test_num, mailbox_ids) in all_mailboxes {
+ for (base_test_num, mailbox_ids) in all_mailboxes {
for (test_num, mailbox_id) in mailbox_ids.into_iter().enumerate() {
client
.set_default_account_id(Id::new((base_test_num + test_num) as u64).to_string())
@@ -188,169 +182,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
}
}
- server.store.assert_is_empty();*/
-}
-
-async fn simple_test(client: &mut Client) {
- let coco = "fdf";
- let mailbox_id = "a".to_string();
- /*
- let mailbox_id = client
- .set_default_account_id(Id::new(1).to_string())
- .mailbox_create("JMAP Get", None::<String>, Role::None)
- .await
- .unwrap()
- .take_id();*/
-
- // A simple thread that uses in-reply-to to link messages together
- let thread_1 = vec![
- client
- .email_import(
- "Message-ID: <t1-msg1>
-From: test1@example.com
-To: test2@example.com
-Subject: my thread
-
-message here!"
- .into(),
- [&mailbox_id],
- None::<Vec<String>>,
- Some(1),
- )
- .await
- .unwrap(),
- client
- .email_import(
- "Message-ID: <t1-msg2>
-From: test2@example.com
-To: test1@example.com
-In-Reply-To: <t1-msg1>
-Subject: Re: my thread
-
-reply here!"
- .into(),
- [&mailbox_id],
- None::<Vec<String>>,
- Some(2),
- )
- .await
- .unwrap(),
- client
- .email_import(
- "Message-ID: <t1-msg3>
-From: test1@example.com
-To: test2@example.com
-In-Reply-To: <t1-msg2>
-Subject: Re: my thread
-
-last reply"
- .into(),
- [&mailbox_id],
- None::<Vec<String>>,
- Some(3),
- )
- .await
- .unwrap(),
- ];
-
- // Another simple thread, but this time with a shared reference header instead
- let thread_2 = vec![
- client
- .email_import(
- "Message-ID: <t2-msg1>
-From: test1@example.com
-To: test2@example.com
-Subject: my thread
-
-message here!"
- .into(),
- [&mailbox_id],
- None::<Vec<String>>,
- Some(1),
- )
- .await
- .unwrap(),
- client
- .email_import(
- "Message-ID: <t2-msg2>
-References: <t2-msg1>
-From: test2@example.com
-To: test1@example.com
-Subject: my thread
-
-reply here!"
- .into(),
- [&mailbox_id],
- None::<Vec<String>>,
- Some(2),
- )
- .await
- .unwrap(),
- client
- .email_import(
- "Message-ID: <t2-msg3>
-References: <t2-msg1>
-From: test1@example.com
-To: test2@example.com
-Subject: my thread
-
-reply here!"
- .into(),
- [&mailbox_id],
- None::<Vec<String>>,
- Some(3),
- )
- .await
- .unwrap(),
- ];
-
- // Make sure none of the separate threads end up with the same thread ID
- assert_ne!(
- thread_1.first().unwrap().thread_id().unwrap(),
- thread_2.first().unwrap().thread_id().unwrap(),
- "Making sure thread 1 and thread 2 have different thread IDs"
- );
-
- // Make sure each message in each thread ends up with the right thread ID
- assert_thread_ids_match(client, &thread_1, "thread chained with In-Reply-To header").await;
- assert_thread_ids_match(client, &thread_2, "thread with References header").await;
-
- //client.mailbox_destroy(&mailbox_id, true).await.unwrap();
-}
-
-async fn assert_thread_ids_match(
- client: &mut Client,
- emails: &Vec<jmap_client::email::Email>,
- description: &str,
-) {
- let thread_id = emails.first().unwrap().thread_id().unwrap();
-
- // First, make sure the thread ID is the same for all messages in the thread
- for email in emails {
- assert_eq!(
- email.thread_id().unwrap(),
- thread_id,
- "Comparing thread IDs of messages in: {}",
- description
- );
- }
-
- // Next, make sure querying the thread yields the same messages
- let full_thread = client.thread_get(thread_id).await.unwrap().unwrap();
- let mut email_ids_in_fetched_thread = full_thread.email_ids().to_vec();
- email_ids_in_fetched_thread.sort();
-
- let mut expected_email_ids = emails
- .iter()
- .map(|email| email.id().unwrap())
- .collect::<Vec<_>>();
- expected_email_ids.sort();
-
- assert_eq!(
- email_ids_in_fetched_thread, expected_email_ids,
- "Comparing email IDs in: {}",
- description
- );
+ server.store.assert_is_empty().await;
}
fn build_message(message: usize, in_reply_to: Option<usize>, thread_num: usize) -> String {