jmap-filenode-types 0.1.2

JMAP FileNode extension data types (draft-ietf-jmap-filenode-*)
Documentation
# jmap-filenode-types

Serde-annotated Rust types for the JMAP FileNode extension ([draft-ietf-jmap-filenode-13]).
Types only — no method handlers, no async, no network I/O.

## What it is

| Type | Description |
|---|---|
| `FileNode` | The core file-system node object (§3.1) — represents a file, directory, or symlink |
| `NodeType` | Enum: `File`, `Directory`, `Symlink`, `Other(String)` |
| `NodeRole` | Well-known directory roles: `Root`, `Home`, `Temp`, `Trash`, `Documents`, `Downloads`, `Music`, `Pictures`, `Videos`, `Other(String)` |
| `FilesRights` | Per-user access rights on a FileNode (`mayRead`, `mayWrite`, `mayAdmin`, etc.) |
| `FileNodeFilterCondition` | Filter arguments for `FileNode/query` |
| `FileNodeCapability` | Account-level capability object for the FileNode extension |
| `FileNodeProperty` | Enum of legal `FileNode` property names for `properties` arrays |

Capability URI constant:

| Constant | Value |
|---|---|
| `JMAP_FILENODE_URI` | `"urn:ietf:params:jmap:filenode"` |

## What it's for

draft-ietf-jmap-filenode data types, consumed by `jmap-filenode-server`
(method handlers + the `FileNodeBackend` trait) and `jmap-filenode-client`
(typed method bindings). Sibling to `jmap-mail-types` and
`jmap-sharing-types` in the workspace's extension-types family.

## Filter extensibility

Filter types in this crate — `FileNodeFilterCondition` and the generic
`Filter<T>` / `Operator` re-exported from `jmap-types` — are **intentionally
not extensible** via vendor "extras" fields. A filter clause the server does
not understand silently breaks query correctness: the client gets the wrong
set of records back with no error signal. So these types deliberately have no
`extra` catch-all field.

Vendors who need to filter on custom fields have two options:

- **IETF-track (recommended).** Use the JMAP Object Metadata extension
  (`draft-ietf-jmap-metadata`, capability URI `urn:ietf:params:jmap:metadata`),
  which defines a `Metadata` / `Annotation` companion object keyed by
  `(relatedType, relatedId)` with capability-declared schema (`metadataTypes`
  / `maxDepth`) and a `Metadata/query` `textMatch` filter. This is the
  workspace's recommended path for vendor data that needs to be queryable.
  Implemented in [`jmap-metadata-types`]../crate-jmap-metadata-types,
  [`jmap-metadata-server`]../crate-jmap-metadata-server, and
  [`jmap-metadata-client`]../crate-jmap-metadata-client (bd JMAP-06zp).
- **Pre-IETF escape.** If you cannot wait for the metadata draft, escape the
  filter tree to `serde_json::Value` or fork the `FileNodeFilterCondition`
  type. See
  [`crate-jmap-calendars-types/PLAN.md`]../crate-jmap-calendars-types/PLAN.md
  for the hybrid sloppy-value pattern.

This policy is part of the workspace extras-preservation policy documented in
the workspace [`AGENTS.md`](../AGENTS.md); the filter-algebra exclusion
decision is bd JMAP-lbdy.

## Spec coverage

**draft-ietf-jmap-filenode-13 sections implemented:**

- §3.1 — `FileNode` object: `id`, `parentId`, `blobId`, `target`, `size`, `name`,
  `type` (serialized as `"type"`), `mediaType`, `created`, `modified`, `shareWith`,
  `myRights`, `role`
- §3.1 (IANA registries §10.4, §10.5) — `NodeType` and `NodeRole` registered values
- §3.2.3 — `FileNodeFilterCondition` query filter
- §1.6 — `FileNodeCapability` and `JMAP_FILENODE_URI`

## How to use

```rust
use jmap_filenode_types::{FileNode, NodeType};

// Deserialize a file node.
let file_node: FileNode = serde_json::from_str(r#"{
    "id": "node1",
    "parentId": "dir1",
    "blobId": "blob-abc123",
    "target": null,
    "size": 8192,
    "name": "report.pdf",
    "type": "file",
    "mediaType": "application/pdf",
    "created": "2025-01-15T10:00:00Z",
    "modified": "2025-01-20T14:30:00Z",
    "shareWith": null
}"#)?;
assert_eq!(file_node.node_type, Some(NodeType::File));

// Deserialize a directory node.
let dir_node: FileNode = serde_json::from_str(r#"{
    "id": "dir1",
    "parentId": null,
    "blobId": null,
    "target": null,
    "size": null,
    "name": "Documents",
    "type": "directory",
    "mediaType": null,
    "created": "2024-06-01T00:00:00Z",
    "modified": "2025-01-20T14:30:00Z",
    "shareWith": null
}"#)?;
assert_eq!(dir_node.node_type, Some(NodeType::Directory));
# Ok::<(), serde_json::Error>(())
```

## How it works

All structs carry `#[serde(rename_all = "camelCase")]` to produce camelCase JSON field names
as required by the JMAP wire format. Two fields require explicit `#[serde(rename)]`
annotations because their wire names clash with Rust keywords or conventions:

- `node_type` is serialized as `"type"``type` is a reserved keyword in Rust, so the
  struct field is named `node_type` with `#[serde(rename = "type")]`.
- `media_type` in `FileNodeFilterCondition` is serialized as `"mediaType"` — covered by the
  top-level `rename_all = "camelCase"`.

`NodeType` and `NodeRole` are string-backed enums with an `Other(String)` fallback variant
so that unrecognised values from the IANA registry (added after this crate was written)
round-trip without data loss.

## Gotchas

- `ArchiveEntry` integration types for the JMAP Blob Extensions draft
  (`RFC 9404) are not implemented.

## References

- **[draft-ietf-jmap-filenode-13]** — JMAP FileNode (normative for all type definitions)
- **[RFC 8620]** — JMAP Core (Id, State, SetError, request/response shape)

[draft-ietf-jmap-filenode-13]: https://datatracker.ietf.org/doc/draft-ietf-jmap-filenode/
[RFC 8620]: https://www.rfc-editor.org/rfc/rfc8620