Skip to main content

Card

Struct Card 

Source
pub struct Card { /* private fields */ }
Expand description

A single card-yaml block (root or composable). body is the content (Content) form of the prose after the closing fence — the empty content when none follows; check card.body().is_blank(). Markdown is a projection: Card::body_markdown re-emits it.

Implementations§

Source§

impl Card

Source

pub fn new(kind: impl Into<String>) -> Result<Self, EditError>

Create a composable card with the given kind, no fields, and an empty body.

Source

pub fn store_field( &mut self, name: &str, value: impl Into<QuillValue>, ) -> Result<(), EditError>

Store a payload field verbatim, clearing any !must_fill marker on that key — the opaque store (store = verbatim, coercion deferred to render; contrast the typed TypedWriter::set). Scalars convert in place (store_field("qty", 3)); see the From impls on QuillValue.

Returns EditError::InvalidFieldName when name does not match [A-Za-z_][A-Za-z0-9_]*.

Source

pub fn store_fill( &mut self, name: &str, value: impl Into<QuillValue>, ) -> Result<(), EditError>

Store a payload field verbatim and mark it as a !must_fill placeholder. Null emits as key: !must_fill; scalars/sequences as key: !must_fill <value>. The opaque store’s fill variant (quill-free, verbatim); same validation as Card::store_field.

Source

pub fn store_fields<K, V, I>( &mut self, fields: I, ) -> Result<(), Vec<(String, EditError)>>
where K: Into<String>, V: Into<QuillValue>, I: IntoIterator<Item = (K, V)>,

Store several payload fields verbatim and atomically, clearing any !must_fill marker on each key — the opaque store’s batch (contrast the typed TypedWriter::set_all). The whole batch is validated first — on any violation nothing is applied and every offending field is reported as a (name, error) pair, so a caller feeding externally-sourced names (database columns, form keys) sees all violations in one pass instead of fix-rerun-repeat. Per-field rules are those of Card::store_field; insertion order follows the iterator, and a repeated name behaves like repeated store_field calls (last value wins, first position kept).

Source

pub fn remove_field( &mut self, name: &str, ) -> Result<Option<QuillValue>, EditError>

Remove a payload field; returns Ok(None) if the name is absent. Removal has no lane — the one verb serves every write path. Same validation as Card::store_field.

Source

pub fn store_ext(&mut self, value: Map<String, Value>) -> Result<(), EditError>

Replace the card’s opaque $ext map wholesale, inserting it at the canonical position (after $quill/$kind/$id, before user fields) when none existed. Passing an empty map records an explicit $ext: {}.

$ext carries out-of-band consumer state (editor renames, agent annotations, …) and is stripped from Document::to_plate_json, so a write here can never affect a render. Any nested comments attached to a replaced $ext are dropped. Returns EditError::ValueTooDeep when the map nests past the §8 depth limit — $ext never reaches the plate JSON, but it does flow through the recursive emit and DTO paths, so it carries the same depth bound as user fields.

Quill-free and never coerced — an opaque store_* verb by the vocabulary rule, not a typed set.

Source

pub fn remove_ext(&mut self) -> Option<Map<String, Value>>

Remove the card’s $ext map entirely, returning the previous map if present. This is a blunt escape hatch — it discards every namespace ($ext.editor, $ext.agent, …) at once. To clear consumer state, prefer Card::remove_ext_namespace, which drops only your own slot and leaves sibling consumers’ state intact.

Source

pub fn store_ext_namespace( &mut self, namespace: impl Into<String>, value: Value, ) -> Result<(), EditError>

Merge value into the card’s $ext map under namespace, creating the map when absent and replacing any existing value at that key.

This is the recommended way to write $ext: it preserves sibling namespaces, so independent consumers keying on their own slot ($ext.editor, $ext.agent, …) don’t clobber each other. Returns EditError::ValueTooDeep when the merged map nests past the §8 depth limit (see Card::store_ext); the card’s $ext is unchanged on error. Quill-free and never coerced — an opaque store_* verb.

Source

pub fn remove_ext_namespace(&mut self, namespace: &str) -> Option<Value>

Remove namespace from the card’s $ext map, returning the value that was stored there (or None when the map or the key was absent).

This is the recommended way to clear $ext state: it is the namespace-scoped inverse of Card::store_ext_namespace and preserves sibling namespaces, where Card::remove_ext would wipe them all. When removing the last namespace empties the map, the $ext entry is dropped entirely (not left as $ext: {}), so store_ext_namespace(ns, v) followed by remove_ext_namespace(ns) restores a card that had no $ext to its original state.

Source

pub fn seed(&self) -> Option<&Map<String, Value>>

The raw $seed map (keyed by card-kind), or None. For a parsed, per-kind overlay, index this map by kind and pass the entry to crate::SeedOverlay::from_json. Only the main card carries $seed.

Source

pub fn store_seed_namespace( &mut self, card_kind: impl Into<String>, value: Value, ) -> Result<(), EditError>

Merge a card-kind’s seed overlay value into the card’s $seed map under card_kind, creating the map when absent and replacing any existing overlay for that kind. Sibling kinds are preserved — this is the per-kind-safe writer, the seed analogue of Card::store_ext_namespace. card_kind must be a valid, non-reserved composable kind (EditError::InvalidKindName / EditError::ReservedKind otherwise) — $seed is keyed by composable card-kind, unlike the free-form namespaces of $ext. Returns EditError::ValueTooDeep when the merged map nests past the §8 depth limit; the card is unchanged on error. Quill-free and never coerced — an opaque store_* verb.

Source

pub fn remove_seed_namespace(&mut self, card_kind: &str) -> Option<Value>

Remove card_kind from the card’s $seed map, returning the overlay stored there (or None). When removing the last kind empties the map, the $seed entry is dropped entirely (not left as $seed: {}). The seed analogue of Card::remove_ext_namespace.

Source

pub fn install_body(&mut self, content: Content)

Install the body content directly from a pre-built Contentvalue semantics, the native richtext writer. A content is valid by construction, so this is infallible: no markdown import, no diff, no schema check; the identity anchors of the previous body are gone (install-this-exact-value, so a to_markdown → install round-trip cannot resurrect them). Use it when the caller already holds a content (a decoded canonical-JSON body, another field’s value, an editor’s serialized state). For “here’s new authored markdown,” use revise_body, which rebases surviving anchors; the cold-import path is spelled at the call site as install_body(import_body(md)?).

Source

pub fn install_field( &mut self, name: &str, content: Content, ) -> Result<(), EditError>

Install a richtext field’s content directly from a pre-built Content — the field-level twin of install_body. Value semantics: stores the canonical content JSON verbatim (identity marks and content-only marks such as underline intact), no diff, no schema check (schema-blind, like apply_field_richtext_changecommit_field is the typed door). Returns EditError::InvalidFieldName for a malformed name.

Source

pub fn commit_field( &mut self, name: &str, value: impl Into<QuillValue>, schema: &FieldSchema, ) -> Result<(), EditError>

Write-time commit: validate and normalize value per the field’s schema type and store the canonical form. The typed sibling of the opaque store_field — the one write verb for every field type (richtext today, any future content model tomorrow), dispatching on the FieldSchema rather than growing a per-type method.

The two write disciplines: store_field stores the value opaquely and defers coercion to render (keystroke-level state, data-in-flight); commit_field canonicalizes now and fails now (an editor blur/save, an agent write). Neither is forced on the other.

Behavior by type:

  • richtext — imports a markdown string / adopts a content object and stores canonical content JSON, so identity marks (anchors, island ids) live on the stored value from the write; a richtext(inline) schema rejects a multi-block value with EditError::FieldRichtextNotInline.
  • scalars (string/integer/number/boolean/datetime) — stores the coerced canonical ("3"3), applying only value-parsing normalizations; a cross-type value that the render floor would coerce (e.g. 1true) or a shape mismatch fails here instead.
  • array / object — coerces each element/property against the element/property schema.
  • null — passes through unchanged (the null ≡ absent rule); nothing is coerced (a richtext null reads back as the empty content via field_richtext).

The caller supplies the schema because a Document holds only a $quill reference, not the resolved schema; an editor holds it (see crate::TypedWriter, which resolves the schema per field and calls this).

Returns EditError::InvalidFieldName for a malformed name, EditError::FieldRichtextDecode / EditError::FieldRichtextNotInline for a richtext field, EditError::FieldConform for any other type mismatch, and EditError::ValueTooDeep when the stored value nests past the §8 depth limit.

Source

pub fn revise_body( &mut self, body: impl Into<String>, ) -> Result<Delta, EditError>

Revise the body from an authored markdown string — edit semantics, the whole-document (stale-text / LLM / MCP) writer, and the receipt- returning default write path. Imports the markdown, diffs it against the current body, and rebases surviving identity anchors onto the new text (cold import + diff_import), then returns the text Delta from the old body to the new one — the change an editor bridge maps its own positions through across a whole-document replace (Delta::map_pos). Surviving identity anchors rebase; formatting marks are re-derived by the fresh import. A pathologically over-nested input (> MAX_NESTING_DEPTH) returns EditError::Import rather than silently degrading to the empty content. Discard the receipt with let _ = card.revise_body(md)?; when caret stability is not needed.

Source

pub fn revise_field( &mut self, name: &str, body: impl Into<String>, ) -> Result<Delta, EditError>

Revise a richtext field from an authored markdown string — the field-level twin of revise_body, and the field-level diff_import the write surface previously lacked (the only field-content writers were the cold commit_field and the splice apply_field_richtext_change, so an LLM rewriting a richtext field’s markdown had no anchor-preserving path). Decodes the field’s current content as the diff base (an absent field cold-imports from empty), rebases surviving anchors onto the new text, re-stores the canonical content, and returns the text Delta.

Schema-blind by design — the content-writer stratum splices without the quill (like apply_field_richtext_change); commit_field is the typed door that enforces richtext(inline), and a violation otherwise surfaces at validate/render.

Returns EditError::InvalidFieldName for a malformed name, EditError::FieldRichtextDecode when the field is present but is not a richtext content (a scalar a store_field wrote), and EditError::Import on an over-nested markdown input.

Source

pub fn revise_field_checked( &mut self, name: &str, body: impl Into<String>, schema: &FieldSchema, ) -> Result<Delta, EditError>

Revise a richtext field from markdown with schema enforcement — the typed and anchor-preserving field write that neither revise_field nor commit_field provides alone. revise_field rebases anchors but is schema-blind; commit_field enforces the schema but cold-imports (the previous value’s anchors are gone). This does both: diff the markdown against the field’s current content so surviving anchors rebase (as revise_field), then enforce schema on the diffed result through the same typed-conform path commit_field runs — so a richtext(inline) schema rejects a multi-block result with EditError::FieldRichtextNotInline, the error surface unchanged, while the anchors survive. Returns the text Delta receipt.

The primitive that TypedWriter::revise_field and CardWriter::revise_field wrap: they resolve schema from the bound quill and call here. The schema runs on the content the diff produced, so a non-richtext schema (nothing to preserve) fails with the same EditError::FieldConform commit_field would raise.

Errors: EditError::InvalidFieldName, EditError::FieldRichtextDecode when the field is present but not a richtext content, EditError::Import on an over-nested markdown input, and the conform errors of commit_field on the diffed result. On any error the field is unchanged.

Source

pub fn apply_body_change( &mut self, text_delta: &Delta, line_ops: &[LineOp], mark_ops: &[MarkOp], ) -> Result<(), EditError>

Apply a committed field-change bundle to the body content — the native form-editor writer. Order is text delta → line ops → mark ops, then one terminal normalization (Content::apply_field_change); mark ranges are in final-text coordinates. Returns EditError::ContentApply when an op is out of bounds; the apply is all-or-nothing (Content::apply_field_change), so the body is unchanged on error — apply the bundle against the body the delta was computed from.

Source

pub fn apply_field_richtext_change( &mut self, name: &str, text_delta: &Delta, line_ops: &[LineOp], mark_ops: &[MarkOp], ) -> Result<(), EditError>

Splice a content field-change bundle into a richtext-valued field’s stored content — the field-path twin of apply_body_change, and what lets identity marks (anchors, island ids) persist on field content across incremental edits. Decodes the field’s canonical content, applies the text delta plus any line/mark ops in the same all-or-nothing bundle, and re-stores the canonical result.

Returns EditError::FieldRichtextDecode when the field is absent or its stored value is not a richtext content (the caller addresses a field it knows is richtext, exactly as when writing it), and EditError::ContentApply when the bundle applies out of bounds.

Source§

impl Card

Source

pub fn from_parts(payload: Payload, body: Content) -> Self

Create a Card from its parts without validation. body is the content form; to build from an authored markdown string, import it first via the crate-internal import_body boundary. For user-facing construction of composable cards use Card::new.

Source

pub fn quill(&self) -> Option<&QuillReference>

Source

pub fn kind(&self) -> Option<&str>

Source

pub fn id(&self) -> Option<&str>

Source

pub fn ext(&self) -> Option<&Map<String, Value>>

Opaque $ext map for out-of-band extension data (UI editor state, agent annotations, …). Carried through Markdown and storage DTO round-trips; never emitted into the plate JSON consumed by backends.

Source

pub fn payload(&self) -> &Payload

Source

pub fn payload_mut(&mut self) -> &mut Payload

Source

pub fn body(&self) -> &Content

The card body as a Content content — the canonical content model. For the markdown projection use Card::body_markdown.

Source

pub fn body_markdown(&self) -> String

The card body rendered back to its markdown projection. This is a derived view (export ∘ body), not stored state; a Document round-trip therefore canonicalizes the body (e.g. __b__**b**).

Source

pub fn field_richtext( &self, name: &str, ) -> Option<Result<Content, RichtextDecodeError>>

Read a richtext-valued user field back as a Content content — the field-level twin of Card::body. Decodes the stored value through the same object-or-markdown dispatch the writer (commit_field) commits, so a field stored as a canonical content reads back losslessly (identity marks intact) and a still-authored markdown string imports.

  • None — the field is absent.
  • Some(Ok(rt)) — decoded content.
  • Some(Err(_)) — the field is present but neither a content object nor an importable markdown string (e.g. a bare number a store_field wrote).

A Document carries no schema, so this cannot itself tell a richtext field from a plain string field; the caller names a field it knows is richtext, exactly as it does when writing.

Source

pub fn field_markdown( &self, name: &str, ) -> Option<Result<String, RichtextDecodeError>>

The markdown projection of a richtext-valued field (export ∘ decode) — the field-level twin of Card::body_markdown, and the projection an emit or a markdown save writes for a content-valued field. The projection twin of field_richtext, carrying its Ok/Err decode outcome:

  • None — the field is absent.
  • Some(Ok(md)) — the projected markdown.
  • Some(Err(_)) — the field is present but does not decode as richtext (a scalar/array/object a store_field wrote, or a non-content object).

Absence returns None; a present non-richtext value returns Some(Err), so the projection surfaces the type mismatch instead of blanking on it.

Source

pub fn field_plaintext( &self, name: &str, ) -> Option<Result<String, RichtextDecodeError>>

The plaintext projection of a content-valued field (to_plaintext ∘ decode) — the literal-codec twin of field_markdown, for a plaintext-typed field: marks are never interpreted, so the text is verbatim both ways. Carries field_richtext’s Ok/Err decode outcome:

  • None — the field is absent.
  • Some(Ok(text)) — the projected literal text.
  • Some(Err(_)) — the field is present but does not decode as content.

Trait Implementations§

Source§

impl Clone for Card

Source§

fn clone(&self) -> Card

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Card

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<&Card> for CardV0_93_0

Source§

fn from(card: &Card) -> Self

Converts to this type from the input type.
Source§

impl From<&Card> for CardWire

Source§

fn from(card: &Card) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Card

Source§

fn eq(&self, other: &Card) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Card

Source§

impl TryFrom<CardV0_93_0> for Card

Source§

type Error = StorageError

The type returned in the event of a conversion error.
Source§

fn try_from(card: CardV0_93_0) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl TryFrom<CardWire> for Card

Source§

type Error = WireError

The type returned in the event of a conversion error.
Source§

fn try_from(wire: CardWire) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

§

impl Freeze for Card

§

impl RefUnwindSafe for Card

§

impl Send for Card

§

impl Sync for Card

§

impl Unpin for Card

§

impl UnsafeUnpin for Card

§

impl UnwindSafe for Card

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.