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
- If the parent already has a flat 'affine', treat the file as migrated and make the migration idempotent (skip when 'affine' already exists).
- Re-insert the legacy 'affine' 9-number array into the transform object in the file JSON.
- Restore the note from a backup and migrate it again cleanly.
- 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
- Validate every transform has a 9-element affine array before migration.
- Check for an already-converted flat 'affine' to keep migration idempotent.
- Keep pre-migration backups so partial migrations can be redone cleanly.
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
- bitmapimage does not contain 'image'.
- image does not contain 'rectangle'.
- bitmapimage does not contain 'rectangle'.
- rect does not contain 'transform'.
- no value `stroke_components` in `store_snapshot`
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)