pub use jmap_types::{GetObject, JmapObject, QueryObject, SetObject};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetError {
#[serde(rename = "type")]
pub error_type: SetErrorType,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Vec<String>>,
#[serde(rename = "existingId", skip_serializing_if = "Option::is_none")]
pub existing_id: Option<jmap_types::Id>,
#[serde(rename = "maxRecipients", skip_serializing_if = "Option::is_none")]
pub max_recipients: Option<u64>,
#[serde(rename = "invalidRecipients", skip_serializing_if = "Option::is_none")]
pub invalid_recipients: Option<Vec<String>>,
#[serde(rename = "notFound", skip_serializing_if = "Option::is_none")]
pub not_found: Option<Vec<jmap_types::Id>>,
#[serde(rename = "maxSize", skip_serializing_if = "Option::is_none")]
pub max_size: Option<u64>,
}
impl SetError {
pub fn new(error_type: SetErrorType) -> Self {
Self {
error_type,
description: None,
properties: None,
existing_id: None,
max_recipients: None,
invalid_recipients: None,
not_found: None,
max_size: None,
}
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn with_properties<I, S>(mut self, props: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.properties = Some(props.into_iter().map(|s| s.into()).collect());
self
}
pub fn with_existing_id(mut self, id: jmap_types::Id) -> Self {
self.existing_id = Some(id);
self
}
pub fn with_max_recipients(mut self, n: u64) -> Self {
self.max_recipients = Some(n);
self
}
pub fn with_invalid_recipients<I, S>(mut self, addrs: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.invalid_recipients = Some(addrs.into_iter().map(|s| s.into()).collect());
self
}
pub fn with_not_found(mut self, ids: Vec<jmap_types::Id>) -> Self {
self.not_found = Some(ids);
self
}
pub fn with_max_size(mut self, n: u64) -> Self {
self.max_size = Some(n);
self
}
}
impl std::fmt::Display for SetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.error_type)?;
if let Some(ref desc) = self.description {
write!(f, ": {desc}")?;
}
Ok(())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SetErrorType {
Forbidden,
OverQuota,
TooLarge,
RateLimit,
NotFound,
InvalidPatch,
WillDestroy,
InvalidProperties,
Singleton,
AlreadyExists,
MailboxHasChild,
MailboxHasEmail,
TooManyKeywords,
TooManyMailboxes,
BlobNotFound,
ForbiddenFrom,
InvalidEmail,
TooManyRecipients,
NoRecipients,
InvalidRecipients,
ForbiddenMailFrom,
ForbiddenToSend,
CannotUnsend,
}
impl std::fmt::Display for SetErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Forbidden => "forbidden",
Self::OverQuota => "overQuota",
Self::TooLarge => "tooLarge",
Self::RateLimit => "rateLimit",
Self::NotFound => "notFound",
Self::InvalidPatch => "invalidPatch",
Self::WillDestroy => "willDestroy",
Self::InvalidProperties => "invalidProperties",
Self::Singleton => "singleton",
Self::AlreadyExists => "alreadyExists",
Self::MailboxHasChild => "mailboxHasChild",
Self::MailboxHasEmail => "mailboxHasEmail",
Self::TooManyKeywords => "tooManyKeywords",
Self::TooManyMailboxes => "tooManyMailboxes",
Self::BlobNotFound => "blobNotFound",
Self::ForbiddenFrom => "forbiddenFrom",
Self::InvalidEmail => "invalidEmail",
Self::TooManyRecipients => "tooManyRecipients",
Self::NoRecipients => "noRecipients",
Self::InvalidRecipients => "invalidRecipients",
Self::ForbiddenMailFrom => "forbiddenMailFrom",
Self::ForbiddenToSend => "forbiddenToSend",
Self::CannotUnsend => "cannotUnsend",
};
f.write_str(s)
}
}
#[derive(Debug)]
pub enum BackendSetError<E> {
SetError(SetError),
Other(E),
}
impl<E: std::fmt::Display> std::fmt::Display for BackendSetError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SetError(se) => write!(f, "set error: {se}"),
Self::Other(e) => write!(f, "{e}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for BackendSetError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Other(e) => Some(e),
_ => None,
}
}
}
impl<E> From<SetError> for BackendSetError<E> {
fn from(e: SetError) -> Self {
Self::SetError(e)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub enum BackendChangesError<E> {
TooManyChanges { limit: u64 },
Other(E),
}
impl<E: std::fmt::Display> std::fmt::Display for BackendChangesError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TooManyChanges { limit: 0 } => write!(f, "cannot calculate changes"),
Self::TooManyChanges { limit } => write!(f, "too many changes (limit: {limit})"),
Self::Other(e) => write!(f, "{e}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for BackendChangesError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Other(e) => Some(e),
_ => None,
}
}
}
impl<E> From<E> for BackendChangesError<E> {
fn from(e: E) -> Self {
Self::Other(e)
}
}
impl<E: std::error::Error> From<BackendChangesError<E>> for jmap_types::JmapError {
fn from(e: BackendChangesError<E>) -> Self {
match e {
BackendChangesError::TooManyChanges { limit: 0 } => {
jmap_types::JmapError::cannot_calculate_changes()
}
BackendChangesError::TooManyChanges { limit } => {
jmap_types::JmapError::too_many_changes_with_limit(limit)
}
BackendChangesError::Other(inner) => {
jmap_types::JmapError::server_fail(inner.to_string())
}
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct ChangesResult {
pub created: Vec<jmap_types::Id>,
pub updated: Vec<jmap_types::Id>,
pub destroyed: Vec<jmap_types::Id>,
pub has_more_changes: bool,
pub new_state: jmap_types::State,
}
impl ChangesResult {
pub fn new(
created: Vec<jmap_types::Id>,
updated: Vec<jmap_types::Id>,
destroyed: Vec<jmap_types::Id>,
has_more_changes: bool,
new_state: jmap_types::State,
) -> Self {
Self {
created,
updated,
destroyed,
has_more_changes,
new_state,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct QueryResult {
pub ids: Vec<jmap_types::Id>,
pub position: i64,
pub total: Option<u64>,
pub query_state: jmap_types::State,
pub can_calculate_changes: bool,
}
impl QueryResult {
pub fn new(
ids: Vec<jmap_types::Id>,
position: i64,
total: Option<u64>,
query_state: jmap_types::State,
can_calculate_changes: bool,
) -> Self {
Self {
ids,
position,
total,
query_state,
can_calculate_changes,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct AddedItem {
pub id: jmap_types::Id,
pub index: u64,
}
impl AddedItem {
pub fn new(id: jmap_types::Id, index: u64) -> Self {
Self { id, index }
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct QueryChangesResult {
pub old_query_state: jmap_types::State,
pub new_query_state: jmap_types::State,
pub total: Option<u64>,
pub removed: Vec<jmap_types::Id>,
pub added: Vec<AddedItem>,
}
impl QueryChangesResult {
pub fn new(
old_query_state: jmap_types::State,
new_query_state: jmap_types::State,
total: Option<u64>,
removed: Vec<jmap_types::Id>,
added: Vec<AddedItem>,
) -> Self {
Self {
old_query_state,
new_query_state,
total,
removed,
added,
}
}
}
pub trait JmapBackend: Send + Sync + 'static {
type Error: std::error::Error + Send + Sync + 'static;
fn get_objects<O: GetObject + Send + Sync>(
&self,
account_id: &jmap_types::Id,
ids: Option<&[jmap_types::Id]>,
properties: Option<&[String]>,
) -> impl std::future::Future<Output = Result<(Vec<O>, Vec<jmap_types::Id>), Self::Error>> + Send;
fn get_state<O: JmapObject + Send + Sync>(
&self,
account_id: &jmap_types::Id,
) -> impl std::future::Future<Output = Result<jmap_types::State, Self::Error>> + Send;
fn get_changes<O: JmapObject + Send + Sync>(
&self,
account_id: &jmap_types::Id,
since_state: &jmap_types::State,
max_changes: Option<u64>,
) -> impl std::future::Future<Output = Result<ChangesResult, BackendChangesError<Self::Error>>> + Send;
fn query_objects<O: QueryObject + Send + Sync>(
&self,
account_id: &jmap_types::Id,
filter: Option<&O::Filter>,
sort: Option<&[O::Comparator]>,
limit: Option<u64>,
position: i64,
) -> impl std::future::Future<Output = Result<QueryResult, Self::Error>> + Send;
#[allow(clippy::too_many_arguments)]
fn query_changes<O: QueryObject + Send + Sync>(
&self,
account_id: &jmap_types::Id,
since_query_state: &jmap_types::State,
filter: Option<&O::Filter>,
sort: Option<&[O::Comparator]>,
max_changes: Option<u64>,
up_to_id: Option<&jmap_types::Id>,
collapse_threads: bool,
) -> impl std::future::Future<
Output = Result<QueryChangesResult, BackendChangesError<Self::Error>>,
> + Send;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backend_changes_error_limit_zero_maps_to_cannot_calculate() {
let err = jmap_types::JmapError::from(
BackendChangesError::<std::convert::Infallible>::TooManyChanges { limit: 0 },
);
assert_eq!(
err.error_type.as_str(),
"cannotCalculateChanges",
"limit=0 must produce cannotCalculateChanges; got: {:?}",
err.error_type
);
}
#[test]
fn backend_changes_error_nonzero_limit_maps_to_too_many_changes() {
let err = jmap_types::JmapError::from(
BackendChangesError::<std::convert::Infallible>::TooManyChanges { limit: 50 },
);
assert_eq!(
err.error_type.as_str(),
"tooManyChanges",
"limit=50 must produce tooManyChanges; got: {:?}",
err.error_type
);
}
}