Reject deleting a dimension that a remaining dimension refers to (#94) (#145)

This commit is contained in:
MarekSpodBiedry
2026-10-10 12:12:58 -04:00
committed by GitHub
parent fabc5bacd9
commit 24f074a9ad
2 changed files with 34 additions and 1 deletions
+14 -1
View File
@@ -806,7 +806,20 @@ fn run_delconstraint(s: &mut Session, p: &Value) -> Result<Value> {
};
let n = s.doc()?.constraints.iter().filter(|c| doomed(c)).count();
if n > 0 {
s.doc_mut()?.constraints.retain(|c| !doomed(c));
let mut d = s.doc()?.clone();
let broken_before: Vec<String> = cs::parameter_table(&d).into_iter().filter(|r| r.error.is_some()).map(|r| r.name).collect();
let removed: Vec<String> = d.constraints.iter().filter(|c| doomed(c)).map(|c| c.name.clone()).collect();
d.constraints.retain(|c| !doomed(c));
// Reject when a surviving expression would lose a dimension it refers to (same guard as `parameters delete`).
if let Some(row) = cs::parameter_table(&d).into_iter().find(|r| r.error.is_some() && !broken_before.contains(&r.name)) {
let mut refs = Vec::new();
if let Ok(e) = cs::expr::parse(&row.expr) {
cs::expr::names(&e, &mut refs);
}
let used = removed.iter().find(|n| refs.contains(*n)).or(removed.first()).cloned().unwrap_or_default();
return Err(EngineError::Other(format!("`{used}` is used by `{}`", row.name)));
}
*s.doc_mut()? = d;
}
Ok(json!({ "removed": n, "message": format!("{n} constraint(s) removed") }))
}
@@ -190,3 +190,23 @@ fn hostile_constraint_params_never_panic() {
assert!(s.running.is_none());
}
}
#[test]
fn delconstraint_rejects_referenced_dimension() {
let mut s = Session::new();
let a = line(&mut s, [0.0, 0.0], [10.0, 0.0]);
let b = line(&mut s, [0.0, 5.0], [4.0, 5.0]);
s.execute("dimconstraint", &json!({ "type": "horizontal", "h1": a, "expr": "8" })).unwrap();
s.execute("dimconstraint", &json!({ "type": "horizontal", "h1": b, "expr": "d1/2" })).unwrap();
let before = s.doc().unwrap().clone();
let first = s.doc().unwrap().constraints[0].id;
let e = s.execute("delconstraint", &json!({ "ids": [first] })).unwrap_err().to_string();
assert!(e.contains("`d1` is used by `d2`"), "{e}");
assert_eq!(*s.doc().unwrap(), before);
assert!(s.execute("delconstraint", &json!({ "handles": [a] })).is_err());
assert_eq!(*s.doc().unwrap(), before);
// Selecting both dimensions removes both; the dependent one may go with its source.
let ids: Vec<u32> = s.doc().unwrap().constraints.iter().map(|c| c.id).collect();
s.execute("delconstraint", &json!({ "ids": ids })).unwrap();
assert!(s.doc().unwrap().constraints.is_empty());
}