#[non_exhaustive]pub struct ChangesResponse {
pub account_id: Id,
pub old_state: State,
pub new_state: State,
pub has_more_changes: bool,
pub created: Vec<Id>,
pub updated: Vec<Id>,
pub destroyed: Vec<Id>,
pub updated_properties: Option<Option<Vec<String>>>,
pub extra: Map<String, Value>,
}Expand description
RFC 8620 §5.2 — Foo/changes response shape.
Reports the ids of objects created, updated, or destroyed since
old_state. If has_more_changes is true, the client should call
/changes again with since_state = new_state to retrieve the next
page; otherwise new_state is the current state.
§Extension fields and the foundation-coupling trade-off
Some JMAP data-type extensions add an updatedProperties field to
their /changes response shape:
-
RFC 8621 §2.2 (
Mailbox/changes): set when onlytotalEmails/unreadEmails/totalThreads/unreadThreadschanged. -
RFC 9425 §5 (
Quota/changes): set when only theusedproperty changed. -
draft-ietf-jmap-metadata-04 §3.3: every data type the metadata capability lists, when the request’s
usingincludesurn:ietf:params:jmap:metadata. The field is then always present,nullunless every updated id changed only inmetadataand/orprivateMetadata(plus the Mailbox counts).
For all other /changes methods (RFC 8621 §3.2 Thread/changes,
§4.3 Email/changes, plus every extension /changes method not
listed above) the server omits the field, and clients deserialize
it as None.
Architectural decision (bd:JMAP-6xs8.5). Carrying
updatedProperties on this base type, rather than on per-extension
MailboxChangesResponse / QuotaChangesResponse newtypes, is a
deliberate workspace trade-off:
- Chosen shape: one foundation
ChangesResponsewith anOption<Option<Vec<String>>>field that extensions populate. - Alternative (a):
ChangesResponse<Ext = ()>generic with a per-type extension struct. Rejected: would force every/changeshandler in every extension server to thread theExttype parameter, and the 30-crate canonical-template family would have to settle on a single way to express “no extension” vs “Mailbox extension” vs “Quota extension”. - Alternative (b):
MailboxChangesResponseandQuotaChangesResponseas separate types injmap-mail-typesand a futurejmap-quota-types, delegating to a shared base via composition. Rejected: would duplicate the seven base fields (accountId,oldState,newState,hasMoreChanges, three id vectors) at every extension type, and require parallel handler / parse / dispatch code. - Alternative (c): leave
updatedPropertiesas an extras flatten entry that mail/quota types explicitly read viaresp.extra.get("updatedProperties"). Rejected: loses typed access, and the field is RFC-defined (not vendor / site), so the extras pattern (workspace AGENTS.md “Extras-preservation policy”) is the wrong tool.
Drift risk acknowledged: every future /changes extension
(jmap-sharing, jmap-tasks, etc.) will face pressure to add its
own typed field here “for parity”. Each such field would extend
this foundation type by one more extension-specific column.
Cautionary precedent: bd:JMAP-kt5k removed eight cap-advertising
fields from the chat capability after they accreted there for the
same parity-pressure reason. New /changes extension fields land
here only when the field is RFC-defined and the alternative shapes
above have been re-evaluated against the new use case; a vendor
extension belongs in the extras catch-all, not on the typed
surface.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.account_id: IdThe account the response refers to.
old_state: StateThe state token the client passed in.
new_state: StateThe current (or next-page) state token.
has_more_changes: booltrue if there are more changes the client must page through.
created: Vec<Id>Ids of objects created since old_state.
updated: Vec<Id>Ids of objects updated since old_state.
destroyed: Vec<Id>Ids of objects destroyed since old_state.
updated_properties: Option<Option<Vec<String>>>Property names that changed (RFC 8621 §2.2, RFC 9425 §5,
draft-ietf-jmap-metadata-04 §3.3), in three states: None when
absent, Some(None) for null and Some(Some(names)) for a
list. Each state round-trips.
Mailbox/changes and Quota/changes may list the properties
that changed, and clients can back-reference
/updatedProperties into a follow-up /get. When the request’s
using includes urn:ietf:params:jmap:metadata, -04 §3.3 says
for every listed data type that the argument “is always present,
and is null when updated is empty”, so a server sets Some(None)
rather than None there. Other responses leave it None unless
their type defines it, and it is then omitted.
extra: Map<String, Value>Catch-all for vendor / site / private extension fields not covered by the typed fields above. Preserves unknown fields across deserialize/serialize round-trip per workspace extras-preservation policy (see workspace AGENTS.md).