Skip to main content

JmapBackend

Trait JmapBackend 

Source
pub trait JmapBackend:
    Send
    + Sync
    + 'static {
    type Error: Error + Send + Sync + 'static;
    type CallerCtx: Clone + Send + Sync + 'static;

    // Required methods
    fn account_exists(
        &self,
        caller: &Self::CallerCtx,
        account_id: &Id,
    ) -> impl Future<Output = Result<bool, Self::Error>> + Send;
    fn get_objects<O: GetObject + Send + Sync>(
        &self,
        caller: &Self::CallerCtx,
        account_id: &Id,
        ids: Option<&[Id]>,
        properties: Option<&[String]>,
    ) -> impl Future<Output = Result<(Vec<O>, Vec<Id>), Self::Error>> + Send;
    fn get_state<O: JmapObject + Send + Sync>(
        &self,
        caller: &Self::CallerCtx,
        account_id: &Id,
    ) -> impl Future<Output = Result<State, Self::Error>> + Send;
    fn get_changes<O: JmapObject + Send + Sync>(
        &self,
        caller: &Self::CallerCtx,
        account_id: &Id,
        since_state: &State,
        max_changes: Option<u64>,
    ) -> impl Future<Output = Result<ChangesResult, BackendChangesError<Self::Error>>> + Send;
    fn query_objects<O: QueryObject + Send + Sync>(
        &self,
        caller: &Self::CallerCtx,
        account_id: &Id,
        filter: Option<&O::Filter>,
        sort: Option<&[O::Comparator]>,
        limit: Option<u64>,
        position: i64,
    ) -> impl Future<Output = Result<QueryResult, Self::Error>> + Send;
    fn query_changes<O: QueryObject + Send + Sync>(
        &self,
        caller: &Self::CallerCtx,
        account_id: &Id,
        since_query_state: &State,
        filter: Option<&O::Filter>,
        sort: Option<&[O::Comparator]>,
        max_changes: Option<u64>,
        up_to_id: Option<&Id>,
        collapse_threads: bool,
    ) -> impl Future<Output = Result<QueryChangesResult, BackendChangesError<Self::Error>>> + Send;

    // Provided method
    fn principal_id(caller: &Self::CallerCtx) -> Option<&Id> { ... }
}
Expand description

Read-side backend supertrait shared by all JMAP server crates.

Domain-specific backend traits (MailBackend, ChatBackend, etc.) require this trait as a supertrait and add write-side methods on top.

Only the read operations that have an identical signature across all JMAP object types belong here. Write operations (create_object, update_object, destroy_object) and domain-specific operations remain in the domain crate.

The collapse_threads parameter on query_changes is included for Email/queryChanges (RFC 8621 §4.5). Non-mail backends should pass false and may ignore the parameter.

This trait is not object-safe by design (generic methods). Use Arc<impl JmapBackend> when sharing across tasks.

§CallerCtx

Every backend method takes a caller: &Self::CallerCtx parameter as the first argument after &self. This is the per-request authentication / authorisation context produced by the caller’s auth layer and forwarded unchanged through crate::Dispatcher::dispatchcrate::JmapHandler → the registered closure → the backend.

Implementations that do not need an auth identity can use the unit type:

impl JmapBackend for MyBackend {
    type Error = MyError;
    type CallerCtx = ();
    // ...
}

Implementations that do need to differentiate behaviour per caller (e.g. applying per-user visibility rules, or rejecting reads with forbidden when the caller is not the owner of the account) read the caller parameter to decide.

The trait bound Clone + Send + 'static is what crate::Dispatcher requires; the bound is repeated here so the supertrait can stand on its own without depending on the dispatcher.

Required Associated Types§

Source

type Error: Error + Send + Sync + 'static

The error type returned by storage operations.

§Security

The Display impl of this type is surfaced through BackendSetError::Other’s and BackendChangesError::Other’s own Display impls, which in turn flow into crate::request_error’s RequestError::Display output. When a downstream consumer wires tracing-style logging on top, the formatted error text lands in operator logs verbatim.

Implementations MUST NOT include any of the following in this type’s Display output:

  • Credential material — auth tokens, passwords, push verification codes, invite codes, session cookies, or anything derived byte-for-byte from an Authorization-header value.
  • Blob content — email bodies, sieve scripts, file contents, or any user-supplied opaque payload. An error like "sieve parse error at line 42: <script excerpt>" violates this — emit the line number and a short type-only summary (“sieve parse error at line 42: unexpected token”) and let the server log the full script body separately under a redacted path.
  • PII shaped like an email address in any code path that an unauthenticated caller can trigger. Wrapping a downstream service error that interpolates the caller’s email is the common foot-gun.

Errors that wrap a downstream-service failure should sanitize the downstream error text — or strip it entirely and replace it with a static summary — before constructing the Display string. The same rule applies to every extension *Backend trait that inherits this associated type by transitivity: MailBackend::Error, ChatBackend::Error, CalendarsBackend::Error, TasksBackend::Error, ContactsBackend::Error, FileNodeBackend::Error, and SharingBackend::Error are all the same JmapBackend::Error associated type — the contract here governs all of them.

Precedent: bd:JMAP-sc1b.79 redacted BearerAuth and BasicAuth at the type-derive level; bd:JMAP-sc1b.100 documents the equivalent contract at the trait-associated-type level.

Source

type CallerCtx: Clone + Send + Sync + 'static

The per-request caller context type produced by the auth layer and forwarded by crate::Dispatcher::dispatch into every method call.

Use () when no auth context is needed.

The bound is Clone + Send + Sync + 'static:

  • Clone because crate::Dispatcher clones the value once per method call in the batch.
  • Send + 'static because each method call is spawned on a tokio::task.
  • Sync because handler method bodies take &Self::CallerCtx and hold that reference across .await boundaries inside a Send future (a &T is Send iff T: Sync).

Required Methods§

Source

fn account_exists( &self, caller: &Self::CallerCtx, account_id: &Id, ) -> impl Future<Output = Result<bool, Self::Error>> + Send

Return true if the given account exists in this backend.

Handlers call this at the start of each method to return accountNotFound (RFC 8620 §3.6.2) rather than surfacing the wrong error when accountId is unknown.

Source

fn get_objects<O: GetObject + Send + Sync>( &self, caller: &Self::CallerCtx, account_id: &Id, ids: Option<&[Id]>, properties: Option<&[String]>, ) -> impl Future<Output = Result<(Vec<O>, Vec<Id>), Self::Error>> + Send

Fetch objects by id (or all objects when ids is None).

properties is the list of property names requested by the client (RFC 8620 §5.1). None means the client did not send a properties field; the backend should return all properties. When Some, the backend MAY filter the response to only the named properties, but is not required to — implementations that always return all properties are correct.

Returns (found, not_found) — objects that exist and ids that do not.

Source

fn get_state<O: JmapObject + Send + Sync>( &self, caller: &Self::CallerCtx, account_id: &Id, ) -> impl Future<Output = Result<State, Self::Error>> + Send

Return the current state token for an object type in the given account.

Source

fn get_changes<O: JmapObject + Send + Sync>( &self, caller: &Self::CallerCtx, account_id: &Id, since_state: &State, max_changes: Option<u64>, ) -> impl Future<Output = Result<ChangesResult, BackendChangesError<Self::Error>>> + Send

Return changes since since_state, up to max_changes entries.

Source

fn query_objects<O: QueryObject + Send + Sync>( &self, caller: &Self::CallerCtx, account_id: &Id, filter: Option<&O::Filter>, sort: Option<&[O::Comparator]>, limit: Option<u64>, position: i64, ) -> impl Future<Output = Result<QueryResult, Self::Error>> + Send

Execute a /query and return a page of matching ids.

position may be negative — negative values are relative to the end of the result set per RFC 8620 §5.5 (e.g. -1 means the last result).

§Filter and sort handling

Implementations MUST honour the supplied filter and sort arguments efficiently — typically by pushing both into the indexed storage layer (database WHERE / ORDER BY, search index, etc.). Returning every matching id and relying on the caller to paginate after the fact degenerates to O(n) per page for IMAP-migration accounts.

Handler implementations in jmap-*-server crates SHOULD NOT post-filter or post-sort the backend’s result; doing so re-introduces the O(n) cost this method exists to avoid. The Mailbox handler in jmap-mail-server is the canonical example of pushing filter/sort fully into the backend.

Source

fn query_changes<O: QueryObject + Send + Sync>( &self, caller: &Self::CallerCtx, account_id: &Id, since_query_state: &State, filter: Option<&O::Filter>, sort: Option<&[O::Comparator]>, max_changes: Option<u64>, up_to_id: Option<&Id>, collapse_threads: bool, ) -> impl Future<Output = Result<QueryChangesResult, BackendChangesError<Self::Error>>> + Send

Execute a /queryChanges and return deltas since since_query_state.

collapse_threads is only meaningful for Email/queryChanges (RFC 8621 §4.5). Pass false for all other object types.

Provided Methods§

Source

fn principal_id(caller: &Self::CallerCtx) -> Option<&Id>

The caller’s stable identity within this account namespace.

Returns None for deployments that have not wired identity (test fixtures, single-user dev servers). A None-returning backend CANNOT honor JMAP semantics that depend on caller identity — chat role-hierarchy, calendar ACLs, sharing/myRights, per-user $seen on shared mailboxes, metadata isPrivate visibility scoping, etc. Authentication is still the HTTP layer’s job; this method exposes the result of that authentication to the JMAP layer for in-method semantics.

Implementations MUST NOT mint identity — they MUST read it from the CallerCtx populated by the HTTP/auth middleware before dispatch() was called.

Backends that honor identity-dependent semantics MUST override this method. Handlers and downstream backend traits MAY rely on it being correct when it returns Some.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§