{"record":{"id":"192d878a598d4e7d","repo":"flxzt/rnote","slug":"rect-does-not-contain-transform","errorCode":null,"errorMessage":"rect does not contain 'transform'.","messagePattern":"rect does not contain 'transform'\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs","lineNumber":123,"sourceCode":"            }\n        }\n\n        //dbg!(&value);\n\n        Ok(Self {\n            engine_snapshot: value.engine_snapshot,\n        })\n    }\n}\n\n/// Converts a \"object->transform->nalgebra-affine\" to \"object->(glamx-)affine\"\nfn convert_transform_to_affine_in_obj(obj: &mut IValue) -> anyhow::Result<()> {\n    let obj = obj\n        .as_object_mut()\n        .ok_or(anyhow!(\"supplied value is not a JSON object.\"))?;\n    let transform = obj\n        .remove(\"transform\")\n        .ok_or(anyhow!(\"rect does not contain 'transform'.\"))?;\n    let transform = transform\n        .as_object()\n        .ok_or(anyhow!(\"transform is not a JSON object.\"))?;\n    let affine = transform\n        .get(\"affine\")\n        .ok_or(anyhow!(\"transform does not contain 'affine'.\"))?\n        .as_array()\n        .ok_or(anyhow!(\"affine not an array.\"))?;\n\n    obj.insert(\n        \"affine\",\n        vec![\n            #[allow(clippy::get_first)]\n            affine\n                .get(0)\n                .cloned()\n                .ok_or(anyhow!(\"affine does not have value at index 0\"))?,\n            affine","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs#L105-L141","documentation":"Thrown in convert_transform_to_affine_in_obj when the supplied object has no 'transform' key. The migration's purpose is to replace the legacy nested transform.affine array with a flat 'affine' array; without 'transform' there is nothing to convert, so the maj0min13 -> maj0min15 upgrade fails. (The message says 'rect' but applies to any object passed to the helper.)","triggerScenarios":"Migrating a file where a rectangle/textstroke/shape node exists but lacks the legacy 'transform' object (e.g. already migrated, partially migrated, or never had one).","commonSituations":"Files that were partially migrated (transform already removed but 'affine' not inserted); hand-edited JSON; nodes created by tools targeting a newer format being run through the old-format migration.","solutions":["Check whether the node already has a flat 'affine' key (already migrated) and make the migration idempotent: skip conversion if 'transform' is absent but 'affine' exists.","Restore the original maj0min13 file from a backup and migrate it once, cleanly.","Re-insert the legacy 'transform' object in the file JSON if the source of truth is known.","Soften the helper to treat a missing 'transform' as a no-op with a warning instead of an error."],"exampleFix":"// before: hard failure\nlet transform = obj.remove(\"transform\").ok_or(anyhow!(\"rect does not contain 'transform'.\"))?;\n// after: idempotent migration\nif obj.get(\"affine\").is_some() { return Ok(()); } // already converted\nlet Some(transform) = obj.remove(\"transform\") else {\n    log::warn!(\"no 'transform' to convert; skipping\");\n    return Ok(());\n};","handlingStrategy":"fallback","validationCode":"if node.get(\"transform\").is_none() && node.get(\"affine\").is_some() {\n    // already migrated; skip\n}","typeGuard":"fn needs_transform_conversion(obj: &IValue) -> bool {\n    obj.get(\"transform\").is_some() && obj.get(\"affine\").is_none()\n}","tryCatchPattern":"if let Err(e) = convert_transform_to_affine_in_obj(rect) {\n    if e.to_string().contains(\"does not contain 'transform'\") {\n        // likely already migrated; continue instead of failing\n    } else { return Err(e); }\n}","preventionTips":["Make the migration idempotent: skip nodes that already have a flat 'affine'.","Migrate each file exactly once; mark files with their format version.","Never run an old-format migration on files already in a newer format."],"tags":["json","file-migration","missing-key","idempotency"],"backgroundTag":"missing-required-argument","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}