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`.
- `Email/query` total miscount when `collapseThreads` is enabled.
- `SearchSnippet/get` response structure.
- `VacationResponse` singleton handling.
- OIDC: Add default domain name to groups that are not email addresses.
- RocksDB: Enable blob garbage collection to reclaim disk space from deleted blobs.

View File

@@ -7,9 +7,7 @@
use super::get::VacationResponseGet;
use crate::changes::state::StateManager;
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
use email::sieve::{
SieveScript, VacationResponse, delete::SieveScriptDelete, ingest::SieveScriptIngest,
};
use email::sieve::{SieveScript, VacationResponse, ingest::SieveScriptIngest};
use jmap_proto::{
error::set::{SetError, SetErrorType},
method::set::{SetRequest, SetResponse},
@@ -60,10 +58,13 @@ impl VacationResponseSet for Server {
)
.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
let mut create_id = None;
let create_id = None;
let mut changes = None;
match (request.create, request.update) {
(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."));
}
(Some(create), _) if !create.is_empty() => {
for (id, obj) in create {
if will_destroy.contains(&Id::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);
}
for (id, _obj) in create {
response.not_created.append(id, SetError::singleton());
}
}
(_, Some(update)) if !update.is_empty() => {
for (id, obj) in update.into_valid() {
if id.is_singleton() {
if !will_destroy.contains(&id) {
changes = Some(obj);
} else {
response.not_updated.append(
id,
SetError::new(SetErrorType::WillDestroy)
.with_description("ID will be destroyed."),
);
}
changes = Some(obj);
} else {
response.not_updated.append(
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 {
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)