Files
Stalwart/crates/jmap/src/lib.rs
2025-10-21 16:34:23 +02:00

230 lines
6.5 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
#![warn(clippy::large_futures)]
use common::Server;
use jmap_proto::{
method::{
query::{QueryRequest, QueryResponse},
set::{SetRequest, SetResponse},
},
object::JmapObject,
types::state::State,
};
use std::{fmt::Display, future::Future};
use store::{
fts::FtsFilter,
query::{Comparator, Filter, ResultSet, SortedResultSet, sort::Pagination},
roaring::RoaringBitmap,
};
use trc::AddContext;
use types::collection::Collection;
pub mod addressbook;
pub mod api;
pub mod blob;
pub mod calendar;
pub mod calendar_event;
pub mod calendar_event_notification;
pub mod changes;
pub mod contact;
pub mod email;
pub mod file;
pub mod identity;
pub mod mailbox;
pub mod participant_identity;
pub mod principal;
pub mod push;
pub mod quota;
pub mod share_notification;
pub mod sieve;
pub mod submission;
pub mod thread;
pub mod vacation;
pub mod websocket;
impl JmapMethods for Server {
async fn prepare_set_response<T: JmapObject + Sync + Send>(
&self,
request: &SetRequest<'_, T>,
asserted_state: State,
) -> trc::Result<SetResponse<T>> {
Ok(
SetResponse::from_request(request, self.core.jmap.set_max_objects)?
.with_state(asserted_state),
)
}
async fn filter(
&self,
account_id: u32,
collection: Collection,
filters: Vec<Filter>,
) -> trc::Result<ResultSet> {
self.core
.storage
.data
.filter(account_id, collection, filters)
.await
.add_context(|err| {
err.caused_by(trc::location!())
.account_id(account_id)
.collection(collection)
})
}
async fn fts_filter<T: Into<u8> + Display + Clone + std::fmt::Debug + Sync + Send>(
&self,
account_id: u32,
collection: Collection,
filters: Vec<FtsFilter<T>>,
) -> trc::Result<RoaringBitmap> {
self.core
.storage
.fts
.query(account_id, collection, filters)
.await
.add_context(|err| {
err.caused_by(trc::location!())
.account_id(account_id)
.collection(collection)
})
}
async fn build_query_response<T: JmapObject + Sync + Send>(
&'_ self,
result_set: &ResultSet,
query_state: State,
request: &QueryRequest<T>,
) -> trc::Result<(QueryResponse, Option<Pagination<'_>>)> {
let total = result_set.results.len() as usize;
let (limit_total, limit) = if let Some(limit) = request.limit {
if limit > 0 {
let limit = std::cmp::min(limit, self.core.jmap.query_max_results);
(std::cmp::min(limit, total), limit)
} else {
(0, 0)
}
} else {
(
std::cmp::min(self.core.jmap.query_max_results, total),
self.core.jmap.query_max_results,
)
};
Ok((
QueryResponse {
account_id: request.account_id,
query_state,
can_calculate_changes: true,
position: 0,
ids: vec![],
total: if request.calculate_total.unwrap_or(false) {
Some(total)
} else {
None
},
limit: if total > limit { Some(limit) } else { None },
},
if limit_total > 0 {
Pagination::new(
limit_total,
request.position.unwrap_or(0),
request.anchor.map(|a| a.document_id()),
request.anchor_offset.unwrap_or(0),
)
.into()
} else {
None
},
))
}
async fn sort(
&self,
result_set: ResultSet,
comparators: Vec<Comparator>,
paginate: Pagination<'_>,
mut response: QueryResponse,
) -> trc::Result<QueryResponse> {
// Sort results
let collection = result_set.collection;
let account_id = result_set.account_id;
response.update_results(
self.core
.storage
.data
.sort(result_set, comparators, paginate)
.await
.add_context(|err| {
err.caused_by(trc::location!())
.account_id(account_id)
.collection(collection)
})?,
)?;
Ok(response)
}
}
pub trait JmapMethods: Sync + Send {
fn prepare_set_response<T: JmapObject + Sync + Send>(
&self,
request: &SetRequest<T>,
asserted_state: State,
) -> impl Future<Output = trc::Result<SetResponse<T>>> + Send;
fn filter(
&self,
account_id: u32,
collection: Collection,
filters: Vec<Filter>,
) -> impl Future<Output = trc::Result<ResultSet>> + Send;
fn fts_filter<T: Into<u8> + Display + Clone + std::fmt::Debug + Sync + Send>(
&self,
account_id: u32,
collection: Collection,
filters: Vec<FtsFilter<T>>,
) -> impl Future<Output = trc::Result<RoaringBitmap>> + Send;
fn build_query_response<T: JmapObject + Sync + Send>(
&'_ self,
result_set: &ResultSet,
query_state: State,
request: &QueryRequest<T>,
) -> impl Future<Output = trc::Result<(QueryResponse, Option<Pagination<'_>>)>> + Send;
fn sort(
&self,
result_set: ResultSet,
comparators: Vec<Comparator>,
paginate: Pagination,
response: QueryResponse,
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
}
trait UpdateResults: Sized {
fn update_results(&mut self, sorted_results: SortedResultSet) -> trc::Result<()>;
}
impl UpdateResults for QueryResponse {
fn update_results(&mut self, sorted_results: SortedResultSet) -> trc::Result<()> {
// Prepare response
if sorted_results.found_anchor {
self.position = sorted_results.position;
self.ids = sorted_results
.ids
.into_iter()
.map(|id| id.into())
.collect::<Vec<_>>();
Ok(())
} else {
Err(trc::JmapEvent::AnchorNotFound.into_err())
}
}
}