jmap-server 0.1.0

Backend-agnostic JMAP server framework (RFC 8620): parsing, ResultReference resolution, and Dispatcher
Documentation

jmap-server

Backend-agnostic JMAP server framework for Rust. Implements the RFC 8620 wire protocol: request parsing, ResultReference resolution, and the Dispatcher machinery. No opinion on authentication, method sets, capability URIs, or storage.

Usage

use std::sync::Arc;
use jmap_server::{Dispatcher, JmapHandler, HandlerFuture, JmapError};
use serde_json::Value;

// 1. Implement JmapHandler for each method.
struct FooGetHandler;

impl JmapHandler<String> for FooGetHandler {        // String is the CallerCtx type
    fn call(
        &self,
        _method: String,
        _call_id: String,
        _args: Value,
        _caller: String,                             // auth identity passed through
    ) -> HandlerFuture {
        Box::pin(async move { Ok(serde_json::json!({"list": [], "state": "s0"})) })
    }
}

// 2. Register handlers and dispatch requests.
let mut dispatcher: Dispatcher<String> = Dispatcher::new();
dispatcher.register("Foo/get", Arc::new(FooGetHandler));

// In your handler:
// let request = jmap_server::parse_request(body_json, 16)?;
// let response = dispatcher.dispatch(request, caller_identity, session_state).await;

CallerCtx

CallerCtx is whatever your authentication layer produces — an identity struct, a session token, (), etc. The dispatcher clones it for each method call and passes it through to the handler unchanged. It must be Clone + Send + 'static; use Arc<T> to share non-static data (e.g. a database connection pool).

Request parsing

use jmap_server::{parse_request, request_error};

// Parse and validate a JMAP request body.
let req = parse_request(body_json, /* max_calls */ 16)
    .map_err(request_error)?;   // returns RequestError on failure

parse_request validates:

  • The body is a valid JMAP Request object.
  • using is non-empty.
  • The number of method calls does not exceed max_calls.

Capability URI checking is NOT performed here — that is the caller's responsibility.

Error responses

use jmap_server::{request_error, JmapError};

// Request-level errors (HTTP 400/403/500 with RFC 7807 body):
let resp = request_error(JmapError::not_request());          // → HTTP 400
let resp = request_error(JmapError::limit("maxCallsInRequest"));  // → HTTP 400

// Method-level errors (HTTP 200, inside methodResponses):
// Return Err(JmapError::...) from your JmapHandler::call implementation.

Spec references

  • RFC 8620 — JMAP base protocol (request format, ResultReference, error types)
  • RFC 6901 — JSON Pointer (used by ResultReference path resolution)