jmap-metadata-types
Serde-annotated Rust types for the JMAP Object Metadata extension (draft-ietf-jmap-metadata-01). Types only — no method handlers, no async, no network I/O.
What it is
| Type | Description |
|---|---|
Metadata |
Tagged union of Annotation, ImapMetadata, WebDavMetadata (@type discriminator) |
Annotation |
General-purpose vendor metadata (§2.1.1) — round-trips arbitrary domain-prefixed properties via extra |
ImapMetadata |
IMAP METADATA extension mapping (§2.1.2) |
WebDavMetadata |
WebDAV dead-property mapping (§2.1.3) |
MetadataCapability |
Account-level capability object (§1.2.1) |
MetadataFilterCondition |
Filter for Metadata/query (§3.4.1) — filter-algebra; not extras-preserving |
MetadataProperty |
Property selector enum used by /get / /set |
Capability URI constant:
| Constant | Value |
|---|---|
JMAP_METADATA_URI |
"urn:ietf:params:jmap:metadata" |
What it's for
draft-ietf-jmap-metadata data types — Metadata, Annotation,
ImapMetadata, WebDavMetadata, MetadataFilterCondition, and
MetadataCapability — consumed by jmap-metadata-server (method
handlers + MetadataBackend trait) and jmap-metadata-client
(client-side method bindings). This crate is the workspace-recommended
IETF-track escape hatch for vendor data that needs to be queryable:
the workspace extras-preservation policy excludes filter algebra
(silent-drop is a query-correctness bug), so vendors who need
filterable metadata route through this object rather than via a
vendor-extended filter condition.
Why this crate exists
jmap-types and the extension-types crates apply an
extras-preservation policy that captures vendor
fields in a flatten extra map on every wire-format Deserialize
struct. That policy explicitly excludes filter and comparator
algebra: silently round-tripping an unknown filter clause through
the client gives the wrong record set back with no error signal.
draft-ietf-jmap-metadata-01 is the IETF-track answer. It defines a
companion Metadata object keyed by (relatedType, relatedId) with
schema discovery via the capability and a Metadata/query filter
that includes textMatch over vendor string properties — coarse
but standardised filterability that the base extras policy cannot
offer.
This crate is the foundation of the workspace's implementation tracker, bd JMAP-06zp.
Spec coverage
draft-ietf-jmap-metadata-01 sections implemented:
- §1.2.1 —
MetadataCapabilityandJMAP_METADATA_URI - §2.1 —
Metadatatagged union; three variants - §2.1.1 / §2.2.1.6 —
Annotationwith vendor-extension support - §2.1.2 / §2.2.2 —
ImapMetadatawith/private/and/shared/namespace mapping - §2.1.3 / §2.2.3 —
WebDavMetadatawith expanded-name property keys - §2.2.1 — Common properties (
id,@type,relatedType,relatedId,isPrivate) - §3.4.1 —
MetadataFilterCondition - IANA §9.1 — capability URI string
Not in this crate (tracked separately under bd JMAP-06zp):
- Method handlers
Metadata/get,Metadata/set,Metadata/changes,Metadata/query,Metadata/queryChanges(live injmap-metadata-server, bd JMAP-06zp.3). - The standard-method extensions (
fetchMetadata,onSuccessCreateMetadata,onSuccessUpdateMetadata,metadata: Metadata[]response field) live in each consumer crate's/getand/setargument structs (bd JMAP-06zp.5).
Filter-algebra exclusion
MetadataFilterCondition does NOT carry a extra flatten field — the
workspace extras-preservation policy explicitly excludes filter
algebra (see workspace AGENTS.md and bd JMAP-lbdy).
Vendor extras that need to be filterable belong in an Annotation
payload and are queried via the spec's own textMatch filter, not
via a vendor-extended filter condition.
How to use
use ;
// Deserialize an Annotation (the §7.5 atomic-create response shape).
let meta: Metadata = from_str.unwrap;
match meta
How it works
The Metadata enum is #[serde(tag = "@type")] — the wire @type
discriminator is consumed by serde automatically. Each variant
struct carries the common properties (id, relatedType, relatedId,
isPrivate) as typed fields plus an extra flatten map for
vendor / future-spec fields.
All structs carry #[serde(rename_all = "camelCase")] to produce
camelCase JSON field names as required by the JMAP wire format.
MetadataFilterCondition.type_names is named for clarity in Rust
but serialises as the literal wire name "@type" via
#[serde(rename = "@type")].
ImapMetadata and WebDavMetadata use BTreeMap<String, String>
for their metadata field so round-trip output is deterministic.
Gotchas
Tests are named with _draft_01_ to pin them to the current
draft-ietf-jmap-metadata revision. When the draft revs, expect to
update or replace these tests alongside the wire-format changes. The
#[non_exhaustive] derive on every struct and enum keeps additive
spec evolution non-breaking, but a renamed or removed wire field
would still be a breaking change in the next 0.x bump.
MetadataFilterCondition does not carry the workspace
extras-preservation extra field — see the
Filter-algebra exclusion section above
and the workspace AGENTS.md for the rationale.
References
- draft-ietf-jmap-metadata-01 — JMAP Object Metadata (normative for all type definitions)
- RFC 8620 — JMAP Core (Id, State, request/response shapes,
/get,/set,/query)