#[non_exhaustive]pub struct SetError {
pub error_type: SetErrorType,
pub description: Option<String>,
pub properties: Option<Vec<String>>,
pub existing_id: Option<Id>,
pub max_recipients: Option<u64>,
pub invalid_recipients: Option<Vec<String>>,
pub not_found: Option<Vec<Id>>,
pub max_size: Option<u64>,
pub extra: Map<String, Value>,
}Expand description
JMAP wire-format SetError; emitted by TasksBackend write methods
wrapped as BackendSetError::SetError and serialised into
notCreated / notUpdated / notDestroyed maps by the /set handlers
(RFC 8620 §5.3).
A per-item error in a /set response (notCreated, notUpdated,
notDestroyed maps) (RFC 8620 §5.3).
Construct with SetError::new and chain the builder methods as needed.
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.error_type: SetErrorTypeThe machine-readable error type.
description: Option<String>Optional human-readable description of the error.
properties: Option<Vec<String>>Property names that caused the error (for invalidProperties).
existing_id: Option<Id>The existing object id (for alreadyExists — RFC 8621 §5.7).
max_recipients: Option<u64>Maximum recipients allowed (for tooManyRecipients — RFC 8621 §7.5).
invalid_recipients: Option<Vec<String>>Invalid recipient addresses (for invalidRecipients — RFC 8621 §7.5).
not_found: Option<Vec<Id>>Missing blob IDs (for blobNotFound — RFC 8621 §5.5).
max_size: Option<u64>Maximum message size in octets (for tooLarge on EmailSubmission — RFC 8621 §7.5).
extra: Map<String, Value>Catch-all for extension-defined SetError fields not covered by the typed members above.
JMAP extensions sometimes ship error variants whose wire shape
includes additional structured fields beyond the RFC 8620 §5.3
base set — e.g. JMAP Chat’s rateLimited SetError carries a
serverRetryAfter UTCDate telling the client when it may
retry, and mdnAlreadySent (RFC 8621 §7.7) is a typed
extension error variant. This map preserves any such field
across serialize / deserialize round-trip, mirroring the
extras-preservation policy on the client-side
jmap_types::SetError type.
Use SetError::with_extra to populate from handler code:
SetError::new(SetErrorType::custom("rateLimited"))
.with_description("Slow mode is active for this chat")
.with_extra("serverRetryAfter", json!(retry_after_str))Per workspace AGENTS.md “Extras-preservation policy” — wire
format is byte-identical to a pre-extras SetError when the
map is empty (the skip_serializing_if collapses it).
§Reserved-name invariant (bd:JMAP-jfia.17)
Keys in RESERVED_SET_ERROR_WIRE_NAMES MUST NOT appear in
this map. The typed fields above serialize to those names, so
a colliding extras key produces a JSON object with two keys at
the same level — RFC 8259 §4 permits duplicate keys but the
behaviour is implementation-defined and the resulting SetError
is malformed in practice.
SetError::with_extra enforces this in debug builds via
debug_assert!; direct field mutation (this field is pub per
the workspace extras-preservation policy) bypasses that guard.
Test and audit code SHOULD call SetError::validate_extras
to detect collisions deterministically across build profiles.
Implementations§
Source§impl SetError
impl SetError
Sourcepub fn new(error_type: SetErrorType) -> SetError
pub fn new(error_type: SetErrorType) -> SetError
Construct a SetError with the given type and all optional fields None.
Sourcepub fn with_description(self, desc: impl Into<String>) -> SetError
pub fn with_description(self, desc: impl Into<String>) -> SetError
Set the human-readable description.
§Security
SetError.description is serialized verbatim into the JMAP wire
response (RFC 8620 §5.3 notCreated / notUpdated /
notDestroyed maps) and is visible to any client that can
dispatch the failing /set call. The MUST-NOT rules that apply
to JmapBackend::Error’s Display output
also apply to this string:
- 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.
- PII shaped like an email address in any code path that an unauthenticated caller can trigger.
Wrap downstream errors with crate::server_fail_from_backend
(which always emits the static “internal error” description)
rather than interpolating them into a SetError description.
SetError paths are MORE leak-prone than serverFail because
adversarial clients can probe for descriptions by sending
crafted /set arguments — the typed-error contract guarantees
the response includes a SetError for every failing target.
Static, caller-meaningful descriptions (“rate limit exceeded —
retry in N seconds”, “patch nesting exceeds server limit”) are
fine; backend-error interpolations are not.
Precedent: the parallel contract on
JmapBackend::Error (bd:JMAP-sc1b.100) and the matching
handler-side leak path closed in bd:JMAP-wlip.2. This warning
added in bd:JMAP-wlip.26.
Sourcepub fn with_properties<I, S>(self, props: I) -> SetError
pub fn with_properties<I, S>(self, props: I) -> SetError
Set the list of property names that caused the error.
Sourcepub fn with_existing_id(self, id: Id) -> SetError
pub fn with_existing_id(self, id: Id) -> SetError
Set the existing object id (used with alreadyExists).
Sourcepub fn with_max_recipients(self, n: u64) -> SetError
pub fn with_max_recipients(self, n: u64) -> SetError
Set the maximum recipients (used with tooManyRecipients — RFC 8621 §7.5).
Sourcepub fn with_invalid_recipients<I, S>(self, addrs: I) -> SetError
pub fn with_invalid_recipients<I, S>(self, addrs: I) -> SetError
Set the invalid recipient addresses (used with invalidRecipients — RFC 8621 §7.5).
Sourcepub fn with_not_found(self, ids: Vec<Id>) -> SetError
pub fn with_not_found(self, ids: Vec<Id>) -> SetError
Set the missing blob IDs (used with blobNotFound — RFC 8621 §5.5).
Sourcepub fn with_max_size(self, n: u64) -> SetError
pub fn with_max_size(self, n: u64) -> SetError
Set the maximum message size in octets (used with tooLarge on EmailSubmission — RFC 8621 §7.5).
Sourcepub fn with_extra(self, key: impl Into<String>, value: Value) -> SetError
pub fn with_extra(self, key: impl Into<String>, value: Value) -> SetError
Insert an extension-defined field into Self::extra.
Used by handlers to attach typed wire fields that no with_*
builder covers — for example JMAP Chat’s rateLimited SetError
must carry a serverRetryAfter UTCDate:
SetError::new(SetErrorType::custom("rateLimited"))
.with_description("Slow mode is active for this chat")
.with_extra("serverRetryAfter", serde_json::json!(retry_after_str))The serialized wire shape merges key/value at the same
level as the typed fields (via #[serde(flatten)] on
Self::extra). Calling with_extra("type", ...),
with_extra("properties", ...), or any other reserved
wire-name will produce a malformed SetError on the wire —
callers are responsible for choosing extension-namespace keys
that do not collide with the typed-field wire names.
In debug builds, a with_extra(key, ...) call where key is in
the reserved set RESERVED_SET_ERROR_WIRE_NAMES panics via
debug_assert! to catch the bug at first test run
(bd:JMAP-wlip.3). Release builds preserve the current
no-validation behaviour to avoid silent runtime cost on a
correctly-written caller.
Sourcepub fn validate_extras(&self) -> Result<(), ReservedExtrasKey>
pub fn validate_extras(&self) -> Result<(), ReservedExtrasKey>
Validate that Self::extra does not contain any key in
RESERVED_SET_ERROR_WIRE_NAMES (bd:JMAP-jfia.17).
Self::with_extra enforces the same invariant in debug builds
via debug_assert!, but direct field mutation (e.g.
err.extra.insert("type", json!("evil"))) bypasses that guard.
This method is the deterministic, build-profile-independent
gate: callers and tests that construct SetError values
programmatically should run it before serializing, to catch
the collision case that would produce a malformed wire shape
with two keys at the same name.
Returns the first colliding key on Err; check validate_extras
in a loop if you need to surface all collisions.
§Errors
Returns ReservedExtrasKey with the first reserved key
encountered in Self::extra.