flxzt/rnote · error · anyhow::Error

transform does not contain 'affine'.

Error message

transform does not contain 'affine'.

What it means

Thrown in convert_transform_to_affine_in_obj when the 'transform' object has no 'affine' key. The migration extracts the 9-element nalgebra affine matrix from transform.affine and rewrites it as a flat 6-element 'affine' array on the parent object; without the key the maj0min13 -> maj0min15 upgrade cannot proceed.

Solutions

  1. If the parent already has a flat 'affine', treat the file as migrated and make the migration idempotent (skip when 'affine' already exists).
  2. Re-insert the legacy 'affine' 9-number array into the transform object in the file JSON.
  3. Restore the note from a backup and migrate it again cleanly.
  4. Change the helper to warn-and-skip on missing 'affine' rather than failing the whole file load.

Example fix

// before
.get("affine").ok_or(anyhow!("transform does not contain 'affine'."))?
// after: idempotent + tolerant
if obj.get("affine").is_some() { return Ok(()); }
let Some(affine) = transform.get("affine") else {
    log::warn!("transform missing 'affine'; skipping conversion");
    return Ok(());
};
Defensive patterns

Strategy: validation

Validate before calling

let t = &node["transform"];
if t.is_object() && t.get("affine").is_none() {
    return Err("transform missing 'affine'".into());
}

Type guard

fn has_affine(v: &IValue) -> bool {
    v.get("transform")
        .and_then(|t| t.get("affine"))
        .map_or(false, |a| a.as_array().map_or(false, |a| a.len() == 9))
}

Try / catch

if let Err(e) = convert_transform_to_affine_in_obj(rect) {
    if e.to_string().contains("does not contain 'affine'") {
        // check for already-flat 'affine' and skip, else restore backup
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Migrating a file where a node has {"transform": {...}} but the transform object lacks 'affine' (e.g. it holds only a rotation/scale sub-object or was truncated).

Common situations: Hand-edited legacy files; older/buggy exporters writing transforms without the affine matrix; partially migrated files where affine was already flattened to the parent but 'transform' remained.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs:129

            engine_snapshot: value.engine_snapshot,
        })
    }
}

/// Converts a "object->transform->nalgebra-affine" to "object->(glamx-)affine"
fn convert_transform_to_affine_in_obj(obj: &mut IValue) -> anyhow::Result<()> {
    let obj = obj
        .as_object_mut()
        .ok_or(anyhow!("supplied value is not a JSON object."))?;
    let transform = obj
        .remove("transform")
        .ok_or(anyhow!("rect does not contain 'transform'."))?;
    let transform = transform
        .as_object()
        .ok_or(anyhow!("transform is not a JSON object."))?;
    let affine = transform
        .get("affine")
        .ok_or(anyhow!("transform does not contain 'affine'."))?
        .as_array()
        .ok_or(anyhow!("affine not an array."))?;

    obj.insert(
        "affine",
        vec![
            #[allow(clippy::get_first)]
            affine
                .get(0)
                .cloned()
                .ok_or(anyhow!("affine does not have value at index 0"))?,
            affine
                .get(1)
                .cloned()
                .ok_or(anyhow!("affine does not have value at index 1"))?,
            affine
                .get(3)
                .cloned()

View on GitHub (pinned to bbc5354502)