flxzt/rnote · error · anyhow::Error

no value `stroke_components` in `store_snapshot`

Error message

no value `stroke_components` in `store_snapshot`

What it means

Thrown by the TryFrom<RnoteFileMaj0Min5Patch8> migration into RnoteFileMaj0Min5Patch9 when the store_snapshot JSON object has no "stroke_components" key. The migration must rewrite each stroke component's inner representation, so the key is mandatory in this version's snapshot schema.

Solutions

  1. Add a "stroke_components": [] JSON array to store_snapshot in the file (empty array is accepted).
  2. Open/save the file once in the older app version that writes this key, then retry migration.
  3. Restore the document from a backup that contains the complete store_snapshot.

Example fix

// before (store_snapshot)
{ "image_snapshots": [] }
// after
{ "image_snapshots": [], "stroke_components": [] }
Defensive patterns

Strategy: validation

Validate before calling

let snap = file_json.get("store_snapshot").and_then(|v| v.as_object())?;
if !snap.contains_key("stroke_components") {
    return Err("file predates maj0-min5-patch9 schema: missing stroke_components".into());
}

Type guard

fn has_stroke_components(v: &serde_json::Value) -> bool {
    v.get("store_snapshot")
        .and_then(|s| s.get("stroke_components"))
        .map(|c| c.is_array())
        .unwrap_or(false)
}

Try / catch

// Rust caller migrating files
match RnoteFileMaj0Min5Patch9::try_from(file_maj0min5patch8) {
    Ok(f) => /* proceed */,
    Err(e) if e.to_string().contains("stroke_components") => {
        // re-save with the older app version or restore backup
    }
}

Prevention

When it happens

Trigger: Opening an older .rnote file whose store_snapshot object lacks "stroke_components" entirely when the loader runs the maj0-min5-patch9 upgrade step.

Common situations: Very old or pre-release .rnote files with a different snapshot schema, files stripped by external tools, or hand-edited documents.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of flxzt/rnote@bbc5354502 (2026-09-08). Data as JSON: /api/errors/39505b0ea6252e76. Report an issue: GitHub.

Appendix: source

Thrown at crates/rnote-engine/src/fileformats/rnoteformat/maj0min5patch9.rs:26

/// Rnote file in version: maj 0 min 5 patch 9.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct RnoteFileMaj0Min5Patch9 {
    /// The document.
    #[serde(rename = "document", alias = "sheet")]
    pub(crate) document: ijson::IValue,
    /// The snapshot of the store.
    #[serde(rename = "store_snapshot")]
    pub(crate) store_snapshot: ijson::IValue,
}

impl TryFrom<RnoteFileMaj0Min5Patch8> for RnoteFileMaj0Min5Patch9 {
    type Error = anyhow::Error;

    fn try_from(mut file: RnoteFileMaj0Min5Patch8) -> Result<RnoteFileMaj0Min5Patch9, Self::Error> {
        let stroke_components = file
            .store_snapshot
            .get_mut("stroke_components")
            .ok_or_else(|| anyhow!("no value `stroke_components` in `store_snapshot`"))?
            .as_array_mut()
            .ok_or_else(|| anyhow!("value `stroke_components` is not a JSON array."))?;

        for value in stroke_components {
            let stroke = value
                .as_object_mut()
                .ok_or_else(|| anyhow!("value in `stroke_components` array is not a JSON Object."))?
                .get_mut("value")
                .ok_or_else(|| {
                    anyhow!("no value `value` in JSON object of `stroke_components` array.")
                })?;

            if stroke.is_null() {
                continue;
            }

            if let Some(brushstroke) = stroke
                .as_object_mut()

View on GitHub (pinned to bbc5354502)