Fix JMAP: VacationResponse singleton handling

This commit is contained in:
Maurus Decimus
2026-06-18 16:27:27 +02:00
parent aaee070a2e
commit 4e9b4c1651
2 changed files with 11 additions and 65 deletions

View File

@@ -21,6 +21,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If
- `filter: null` rejected as `notRequest` on `/query` and `/queryChanges`. - `filter: null` rejected as `notRequest` on `/query` and `/queryChanges`.
- `Email/query` total miscount when `collapseThreads` is enabled. - `Email/query` total miscount when `collapseThreads` is enabled.
- `SearchSnippet/get` response structure. - `SearchSnippet/get` response structure.
- `VacationResponse` singleton handling.
- OIDC: Add default domain name to groups that are not email addresses. - OIDC: Add default domain name to groups that are not email addresses.
- RocksDB: Enable blob garbage collection to reclaim disk space from deleted blobs. - RocksDB: Enable blob garbage collection to reclaim disk space from deleted blobs.

View File

@@ -7,9 +7,7 @@
use super::get::VacationResponseGet; use super::get::VacationResponseGet;
use crate::changes::state::StateManager; use crate::changes::state::StateManager;
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
use email::sieve::{ use email::sieve::{SieveScript, VacationResponse, ingest::SieveScriptIngest};
SieveScript, VacationResponse, delete::SieveScriptDelete, ingest::SieveScriptIngest,
};
use jmap_proto::{ use jmap_proto::{
error::set::{SetError, SetErrorType}, error::set::{SetError, SetErrorType},
method::set::{SetRequest, SetResponse}, method::set::{SetRequest, SetResponse},
@@ -60,10 +58,13 @@ impl VacationResponseSet for Server {
) )
.await?, .await?,
); );
let will_destroy = request.unwrap_destroy().into_valid().collect::<Vec<_>>();
for id in request.unwrap_destroy().into_valid() {
response.not_destroyed.append(id, SetError::singleton());
}
// Process set or update requests // Process set or update requests
let mut create_id = None; let create_id = None;
let mut changes = None; let mut changes = None;
match (request.create, request.update) { match (request.create, request.update) {
(Some(create), Some(update)) if !create.is_empty() && !update.is_empty() => { (Some(create), Some(update)) if !create.is_empty() && !update.is_empty() => {
@@ -72,37 +73,14 @@ impl VacationResponseSet for Server {
.details("Creating and updating on the same request is not allowed.")); .details("Creating and updating on the same request is not allowed."));
} }
(Some(create), _) if !create.is_empty() => { (Some(create), _) if !create.is_empty() => {
for (id, obj) in create { for (id, _obj) in create {
if will_destroy.contains(&Id::singleton()) { response.not_created.append(id, SetError::singleton());
response.not_created.append(
id,
SetError::new(SetErrorType::WillDestroy)
.with_description("ID will be destroyed."),
);
} else if create_id.is_some() {
response.not_created.append(
id,
SetError::forbidden()
.with_description("Only one object can be created."),
);
} else {
create_id = Some(id);
changes = Some(obj);
}
} }
} }
(_, Some(update)) if !update.is_empty() => { (_, Some(update)) if !update.is_empty() => {
for (id, obj) in update.into_valid() { for (id, obj) in update.into_valid() {
if id.is_singleton() { if id.is_singleton() {
if !will_destroy.contains(&id) { changes = Some(obj);
changes = Some(obj);
} else {
response.not_updated.append(
id,
SetError::new(SetErrorType::WillDestroy)
.with_description("ID will be destroyed."),
);
}
} else { } else {
response.not_updated.append( response.not_updated.append(
id, id,
@@ -112,9 +90,7 @@ impl VacationResponseSet for Server {
} }
} }
_ => { _ => {
if will_destroy.is_empty() { return Ok(response);
return Ok(response);
}
} }
} }
@@ -340,37 +316,6 @@ impl VacationResponseSet for Server {
} else { } else {
response.updated.append(Id::singleton(), None); response.updated.append(Id::singleton(), None);
} }
} else if !will_destroy.is_empty() {
for id in will_destroy {
if id.is_singleton()
&& let Some(document_id) = self.get_vacation_sieve_script_id(account_id).await?
{
self.sieve_script_delete(account_id, document_id, access_token, &mut batch)
.await?;
if active_script_id == Some(document_id) {
batch
.with_collection(Collection::Principal)
.with_document(0)
.clear(PrincipalField::ActiveScriptId);
}
response.destroyed.push(id);
break;
}
response.not_destroyed.append(id, SetError::not_found());
}
// Write changes
if !batch.is_empty() {
response.new_state = Some(
self.commit_batch(batch)
.await
.and_then(|ids| ids.last_change_id(account_id))
.caused_by(trc::location!())?
.into(),
);
}
} }
Ok(response) Ok(response)