# jmap-cid-types
draft-atwood-jmap-cid-00 wire-format types for the `jmap-*` crate family.
The `urn:ietf:params:jmap:cid` JMAP capability plus the `sha256` typed
string shape used in blob upload responses and FileNode objects.
## What it is
When a server advertises `urn:ietf:params:jmap:cid`, blob upload
responses (RFC 8620 §6.1) carry a `sha256` field with the SHA-256
digest of the uploaded content, encoded as a lowercase hexadecimal
string of exactly 64 characters. When the JMAP FileNode extension is
also advertised, FileNode objects gain the same `sha256` property.
This crate carries the typed `sha256` wire shape ([`Sha256`]) plus
the matching parse-error type ([`Sha256DigestError`]). Wiring the
shape into the Blob upload response surface and Session capability
detection happens in `jmap-base-client`.
## Why a separate crate
CID is not tied to any single consumer extension. The `sha256` field
is referenced by `draft-atwood-jmap-chat-00` (which defers to the CID
document as the normative definition), and the spec is structured to
fold cleanly into a future RFC 8620 bis or
`draft-ietf-jmap-filenode`. Standing CID up as its own crate keeps
the dep graph honest and avoids forcing a `jmap-chat-*` or
`jmap-filenode-*` dependency on consumers that only want content
identifiers.
## What it's for
The typed wire shape here is consumed by `jmap-base-client` for the
`BlobUploadResponse.sha256` field and the `Session::supports_cid()`
helper (both landed in `jmap-base-client`), and by a future
`jmap-filenode-types` revision for the FileNode `sha256` property.
The CID document — capability URI `urn:ietf:params:jmap:cid`, draft
owned by Mark Atwood — is deliberately independent of any single
consumer extension because the same content-hash field feeds Blob
upload responses, FileNode integrity, and the JMAP Chat draft's
attachment references.
## How to use
```toml
[dependencies]
jmap-cid-types = "0.1"
```
Pulls in `serde` and `serde_json` at runtime. No async runtime,
no `jmap-types`, no `jmap-server`, no `jmap-base-client`. Parse
and validate a `sha256` digest from a wire string:
```rust
use jmap_cid_types::Sha256;
// 64 lowercase hex characters per draft-atwood-jmap-cid-00 §2.
let digest: Sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
.parse()?;
assert_eq!(digest.as_ref().len(), 64);
# Ok::<(), jmap_cid_types::Sha256DigestError>(())
```
Use `Sha256::from(&[u8; 32])` (or `Sha256::from([u8; 32])` for an
owned array) to build a digest from the raw 32-byte output of a
SHA-256 hash function (e.g. `sha2::Sha256`) without going through a
hex string. The capability URI `"urn:ietf:params:jmap:cid"` is
detected via the `Session.capabilities` map in `jmap-base-client`.
## How it works
- [`Sha256`] is a `String`-backed newtype with parse-time ABNF
validation per the draft `sha256-value` rule
(`64( %x30-39 / %x61-66 )`): exactly 64 characters, lowercase hex
only. Validation runs on `from_hex`, `TryFrom<&str>`, `FromStr`,
and the `Deserialize` impl.
- `From<[u8; 32]>` / `From<&[u8; 32]>` for `Sha256` build a digest
infallibly by formatting the raw 32-byte SHA-256 output as
lowercase hex; no parse step is needed because the output is
always valid by construction. The input is the output of a hash
function (e.g. `sha2::Sha256::digest(data).into()`) — this crate
carries no hash computation.
- The error type [`Sha256DigestError`] is a single-tier enum
(`WrongLength { got }` or `NonHexLowercase { at, byte }`) with
`#[non_exhaustive]` at the type level and per-variant
`#[non_exhaustive]` so variant additions and per-variant field
additions both remain semver-additive. Diagnostics carry byte
position so callers can point at the bad byte without re-indexing
the input.
- `#[non_exhaustive]` on every public struct and enum, so additive
spec evolution stays a non-breaking change.
- No async. `#[forbid(unsafe_code)]` at the crate root.
- Runtime dependencies limited to `serde` and `serde_json`
(the latter for the `CidCapability.extra` Map<String, Value>
surface per workspace extras-preservation policy).
## Gotchas
- Blob upload-response and Session-capability wiring live in
`jmap-base-client`, not here.
`jmap_base_client::blob::BlobUploadResponse.sha256:
Option<jmap_cid_types::Sha256>` is the typed binding for the
upload-response `sha256` field; `Session::supports_cid()` is the
helper that checks the capability map. This crate is the
wire-shape source of truth; the binding lives one layer up.
- CID and the RFC 9404 BLOBEXT `Blob/get` `digest:sha-256` request
are deliberately separate mechanisms with different encodings
(lowercase hex vs base64) and different access patterns
(unconditional at upload vs on-demand via `Blob/get`). See the
draft §2.3.
## References
- [draft-atwood-jmap-cid-00] — JMAP Blob Content Identifiers (normative
for `Sha256` and the `urn:ietf:params:jmap:cid` capability)
- [RFC 8620] — JMAP Core (Blob upload §6.1 is the binding point)
[draft-atwood-jmap-cid-00]: https://github.com/MarkAtwood/jmap-chat-spec
[RFC 8620]: https://www.rfc-editor.org/rfc/rfc8620