#[non_exhaustive]pub struct SessionClient { /* private fields */ }Expand description
A JmapClient bound to a JMAP session.
Obtain via JmapMetadataExt::with_metadata_session.
All JMAP Metadata methods are available on this type without needing to
pass &Session on every call.
§Session lifecycle
SessionClient captures the Session at construction time. After
re-fetching the session via JmapClient::fetch_session, construct a new
SessionClient with the updated session. Reusing a stale SessionClient
after session expiry will result in unknownAccount or similar errors
from the server.
Clone is derived because JmapClient is itself cheap-to-clone (it
already implements Clone and with_metadata_session clones one
internally), enabling parallel-task fan-out with one bound session.
Debug is implemented manually to redact the inner JmapClient (which
holds an HTTP client and is intentionally not Debug in
jmap-base-client); only the Session is shown. This lets callers
embed a SessionClient in a #[derive(Debug)] struct without manual
impls of their own.
§Thread safety
SessionClient is Send + Sync. Both
jmap_base_client::JmapClient (backed by reqwest::Client) and
jmap_base_client::Session (plain serde-derived data) are
Send + Sync per jmap-base-client’s contract, so this type can be
shared across async tasks via Arc<SessionClient> or cloned for
per-task ownership.
A Send + Sync regression in a future jmap-base-client release
would be a major-version-breaking change for this crate. A
compile-time assertion in methods/mod.rs guards against the
regression landing silently — see
_assert_session_client_send_sync.
Implementations§
Source§impl SessionClient
impl SessionClient
Sourcepub async fn metadata_get(
&self,
ids: Option<&[Id]>,
properties: Option<&[&str]>,
params: Option<MetadataGetParams>,
) -> Result<GetResponse<Metadata>, ClientError>
pub async fn metadata_get( &self, ids: Option<&[Id]>, properties: Option<&[&str]>, params: Option<MetadataGetParams>, ) -> Result<GetResponse<Metadata>, ClientError>
Fetch Metadata objects by IDs (draft-ietf-jmap-metadata-01 §3.2).
If ids is None, the server returns all Metadata objects for
the account, SUBJECT TO the server’s maxObjectsInGet cap (RFC
8620 §5.1). For production use, scope the result set via
Self::metadata_query first and pass explicit ids here to
avoid requestTooLarge errors when the account holds more
objects than the cap.
Pass properties: None to return all fields. Pass
params: Some(MetadataGetParams { extra: ... }) to inject
vendor / site / private method-level extension args; pass
None (or Some(Default::default())) for the standard wire
shape.
§Errors
ClientError::InvalidSessionif the bound session has no primary account forurn:ietf:params:jmap:metadata.- Any transport / protocol variant returned by
JmapClient::call:Http,Parse,AuthFailed,MethodError(wraps RFC 8620 §3.6.2 method-level errors such asaccountNotFound,invalidArguments,serverFail),MethodNotFound,ResponseTooLarge, orUnexpectedResponse.
Sourcepub async fn metadata_changes(
&self,
since_state: &State,
max_changes: Option<u64>,
params: Option<MetadataChangesParams>,
) -> Result<ChangesResponse, ClientError>
pub async fn metadata_changes( &self, since_state: &State, max_changes: Option<u64>, params: Option<MetadataChangesParams>, ) -> Result<ChangesResponse, ClientError>
Fetch changes to Metadata objects since since_state
(draft-ietf-jmap-metadata-01 §3.3).
If has_more_changes is true in the response, call again with
new_state as since_state until the flag is false.
params carries the Metadata-specific optional filters
filterRelatedType and filterMetadataType. Pass None (or
Some(Default::default())) to fetch unfiltered changes. Per §3.3,
clients that use these filters MUST re-use the same filter values
across subsequent calls to ensure consistent synchronisation,
because the returned state token represents the complete account
state and is independent of the filter selection.
§Errors
ClientError::InvalidArgumentifsince_stateis the empty string (defence-in-depth —Stateconstructed viaState::fromaccepts empty strings, but an emptysinceStateis never useful and would otherwise generate a wasted round-trip producing a server-sidecannotCalculateChanges).ClientError::InvalidSessionif the bound session has no primary account forurn:ietf:params:jmap:metadata.- Any transport / protocol variant returned by
JmapClient::call— see the matching error list onSelf::metadata_get.
Sourcepub async fn metadata_set(
&self,
create: Option<Value>,
update: Option<HashMap<Id, PatchObject>>,
destroy: Option<Vec<Id>>,
if_in_state: Option<&State>,
params: Option<MetadataSetParams>,
) -> Result<SetResponse<Metadata>, ClientError>
pub async fn metadata_set( &self, create: Option<Value>, update: Option<HashMap<Id, PatchObject>>, destroy: Option<Vec<Id>>, if_in_state: Option<&State>, params: Option<MetadataSetParams>, ) -> Result<SetResponse<Metadata>, ClientError>
Create, update, or destroy Metadata objects (draft-ietf-jmap-metadata-01 §3.1).
Pass create, update, and/or destroy as needed. All three are
optional; pass None to omit any operation from the request.
create is Option<serde_json::Value> and MUST be a JSON object
whose keys are caller-chosen creation ids (RFC 8620 §5.3 — the
creation id strings may be referenced via the #-prefixed result-
reference syntax in RFC 8620 §3.7) and whose values are partial
Metadata objects per draft-ietf-jmap-metadata-01 §3.1 (one of the
three @type variants: Annotation per §2.1.1, ImapMetadata
per §2.1.2, WebDavMetadata per §2.1.3, each with at minimum
relatedType, relatedId, and an optional isPrivate). The
server assigns the final id and returns it under created in
the response. Wire-shape mismatches (e.g. passing an array or a
primitive) are rejected by the server as invalidArguments after
a network round-trip.
update is Option<HashMap<Id, PatchObject>> (RFC 8620 §5.3). Wire
format is unchanged from a plain JSON object because PatchObject
is #[serde(transparent)]; the typed parameter binds the JSON Pointer
key + null-leaf removal contract to the type system.
The asymmetry between typed update and untyped create is
deliberate: PatchObject is a stable workspace-defined type with one
JSON-Pointer-keyed shape, whereas a Metadata create object is one of
three @type variants and the spec accepts the full JSON wire
shape (including vendor-defined extras at every level). Forcing
create through a single Rust type would fight the spec’s
extensibility model. Mirrors the canonical mail-client email_set
signature.
Pass if_in_state: Some(&state) to use an optimistic-concurrency
guard (RFC 8620 §5.3): the server returns stateMismatch if the
current state does not match. Required for safe read-modify-write
given the (relatedType, relatedId, @type, isPrivate) uniqueness
invariant.
Pass params: Some(MetadataSetParams { extra: ... }) to inject
vendor / site / private method-level extension args; pass None
(or Some(Default::default())) for the standard wire shape.
The server enforces uniqueness on the tuple
(relatedType, relatedId, @type, isPrivate) and may return
alreadyExists, forbidden (if maySetPrivate: false and
isPrivate: true is requested), overQuota, or
invalidProperties SetErrors per §3.1.
§Errors
ClientError::InvalidArgumentif all three ofcreate,update, anddestroyareNone(an all-None/set is a no-op round-trip;if_in_stateandparamsalone are not sufficient to make the request useful, so the client rejects it before sending).ClientError::InvalidArgumentifserde_json::to_valuefails when serializing theupdatemap. In practice this can occur only under pathological conditions (allocation failure on a very largeHashMap; aPatchObjectwhose internal JSON tree exceedsserde_json’s recursion limit). The size ofupdateis otherwise bounded only by available memory; the wire request is buffered by the HTTP client (reqwest’sRequestBuilder::jsonserializes the body upfront viaserde_json::to_vec), so the transient peak holds the sourceHashMap, the intermediateserde_json::Valuetree, and the serializedVec<u8>body simultaneously — roughly 3-4× theHashMap’s in-memory size. Callers dealing with thousands of patches per call may prefer to batch across multiplemetadata_setinvocations to bound that transient peak.ClientError::InvalidSessionif the bound session has no primary account forurn:ietf:params:jmap:metadata.- Any transport / protocol variant returned by
JmapClient::call— see the matching error list onSelf::metadata_get. Per-creation /set failures (e.g.alreadyExists,forbidden,overQuota,invalidProperties) appear inSetResponse::not_created/SetResponse::not_updated/SetResponse::not_destroyedasSetErrorvalues, not asErr. AstateMismatchonif_in_statesurfaces asMethodErrorwitherror_type == "stateMismatch".
Sourcepub async fn metadata_query(
&self,
filter: Option<Value>,
sort: Option<Value>,
position: Option<u64>,
limit: Option<u64>,
params: Option<MetadataQueryParams>,
) -> Result<QueryResponse, ClientError>
pub async fn metadata_query( &self, filter: Option<Value>, sort: Option<Value>, position: Option<u64>, limit: Option<u64>, params: Option<MetadataQueryParams>, ) -> Result<QueryResponse, ClientError>
Query Metadata IDs with optional filter and sort (draft-ietf-jmap-metadata-01 §3.4).
Pass filter: None and sort: None to return all Metadata IDs with
server-default ordering. Use position and limit for pagination.
The filter value is typed by the caller; the canonical type is
jmap_metadata_types::MetadataFilterCondition (§3.4.1) wrapped in
jmap_types::query::Filter. This builder accepts a serde_json::Value
so callers can mix filter conditions with FilterOperator algebra
without forcing a single type through the API.
Construct a filter via serde_json::to_value on the typed shape, or
inline as a JSON literal:
// Match Annotations on a specific Email.
let filter = serde_json::json!({
"relatedType": "Email",
"relatedId": "EM456",
"@type": "Annotation"
});
// Match Annotations on either of two Emails (FilterOperator algebra).
let filter = serde_json::json!({
"operator": "OR",
"conditions": [
{ "relatedType": "Email", "relatedId": "EM456" },
{ "relatedType": "Email", "relatedId": "EM789" }
]
});Sort comparators MUST set property to one of id, @type,
relatedType, relatedId, or isPrivate per §3.4.2; id MUST be
supported by every server, the others SHOULD be supported.
Construct sort as an array of comparator objects:
// Ascending by @type, then by id.
let sort = serde_json::json!([
{ "property": "@type", "isAscending": true },
{ "property": "id", "isAscending": true }
]);Pass params: Some(MetadataQueryParams { extra: ... }) to inject
vendor / site / private method-level extension args; pass None
(or Some(Default::default())) for the standard wire shape.
§Errors
ClientError::InvalidSessionif the bound session has no primary account forurn:ietf:params:jmap:metadata.- Any transport / protocol variant returned by
JmapClient::call— see the matching error list onSelf::metadata_get. RFC 8620 §5.5 defines additional method-level errors specific to /query (anchorNotFound,unsupportedFilter,unsupportedSort,tooManyChanges); they surface here asMethodErrorwith the correspondingerror_typestring.
Sourcepub async fn metadata_query_changes(
&self,
since_query_state: &State,
max_changes: Option<u64>,
filter: Option<Value>,
sort: Option<Value>,
up_to_id: Option<&Id>,
calculate_total: Option<bool>,
params: Option<MetadataQueryChangesParams>,
) -> Result<QueryChangesResponse, ClientError>
pub async fn metadata_query_changes( &self, since_query_state: &State, max_changes: Option<u64>, filter: Option<Value>, sort: Option<Value>, up_to_id: Option<&Id>, calculate_total: Option<bool>, params: Option<MetadataQueryChangesParams>, ) -> Result<QueryChangesResponse, ClientError>
Fetch query-result changes for Metadata since since_query_state
(draft-ietf-jmap-metadata-01 §3.5).
Returns which Metadata IDs were removed from or added to the query
result set since the given state. max_changes may be None.
filter and sort MUST match the filter / sort passed to the
original Metadata/query call that returned since_query_state —
RFC 8620 §5.6 is explicit that the server uses them to compute
which entries entered or left the result set.
up_to_id is the highest-index id the client has cached;
calculate_total requests the new total result count.
Pass params: Some(MetadataQueryChangesParams { extra: ... }) to
inject vendor / site / private method-level extension args; pass
None (or Some(Default::default())) for the standard wire shape.
§Errors
ClientError::InvalidArgumentifsince_query_stateis the empty string (defence-in-depth empty-state guard; seeSelf::metadata_changes).ClientError::InvalidSessionif the bound session has no primary account forurn:ietf:params:jmap:metadata.- Any transport / protocol variant returned by
JmapClient::call— see the matching error list onSelf::metadata_get. RFC 8620 §5.6 also definescannotCalculateChanges(returned when the server cannot honour the request given the supplied filter / sort); it surfaces asMethodError.
Source§impl SessionClient
impl SessionClient
Sourcepub fn client(&self) -> &JmapClient
pub fn client(&self) -> &JmapClient
Borrow the underlying JmapClient.
Useful for ad-hoc operations outside the typed JMAP method surface —
for example, calling JmapClient::upload / JmapClient::download_blob,
or constructing a JmapClient::event_source subscription using the
bound session’s event_source_url.
Sourcepub fn session(&self) -> &Session
pub fn session(&self) -> &Session
Borrow the captured Session.
SessionClient captures the Session at construction time. After
re-fetching the session via JmapClient::fetch_session, callers
should construct a new SessionClient. This accessor lets a caller
compare the captured session’s state field against a freshly
fetched session to detect staleness, or inspect
accountCapabilities / primary_accounts for capability-specific
metadata not exposed via the typed JMAP method surface.
Sourcepub fn metadata_account_id(&self) -> Result<&str, ClientError>
pub fn metadata_account_id(&self) -> Result<&str, ClientError>
Return the primary account id for urn:ietf:params:jmap:metadata,
or Err(InvalidSession) if the session has no primary account for
that capability.