fix(views): validate saved-view coordinates and rotation

This commit is contained in:
Nigel Ihza Firdausy 2026-10-11 04:52:22 +07:00 committed by bflatastic
parent 39099dcc57
commit fffec4a714
1 changed files with 35 additions and 3 deletions

View File

@ -55,9 +55,18 @@ fn list_views(s: &mut Session, _: &Value) -> Result<Value> {
fn new_view(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "view.saved.new";
let center = p.get("center").and_then(Value::as_array).and_then(|a| Some(Point::new(a.first()?.as_f64()?, a.get(1)?.as_f64()?)));
let center = center.ok_or_else(|| bad(C, "missing center [x, y]"))?;
let center = match p.get("center").and_then(Value::as_array).map(Vec::as_slice) {
Some([x, y]) => match (x.as_f64(), y.as_f64()) {
(Some(x), Some(y)) if x.is_finite() && y.is_finite() => Point::new(x, y),
_ => return Err(bad(C, "center must contain two finite numbers")),
},
_ => return Err(bad(C, "center must be [x, y]")),
};
let zoom = f64_req(p, "zoom", C)?;
let rotation = match p.get("rotation") {
Some(value) => value.as_f64().filter(|n| n.is_finite()).ok_or_else(|| bad(C, "rotation must be a finite number"))?,
None => 0.0,
};
if !(zoom.is_finite() && zoom > 0.0) {
return Err(bad(C, "zoom must be positive"));
}
@ -72,7 +81,7 @@ fn new_view(s: &mut Session, p: &Value) -> Result<Value> {
if st.doc.views.iter().any(|v| v.name == name) {
return Err(bad(C, format!("a view named `{name}` exists")));
}
let view = SavedView { name: name.clone(), center, zoom, rotation: f64_or(p, "rotation", 0.0) };
let view = SavedView { name: name.clone(), center, zoom, rotation };
let index = s.edit("New View", |d, _| {
d.views.push(view);
Ok(d.views.len() - 1)
@ -108,6 +117,29 @@ mod tests {
use crate::Session;
/// Malformed dimensions are rejected rather than saving an arbitrary or partial viewport.
#[test]
fn saved_views_require_exact_finite_coordinates_and_rotation() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let before = s.doc().unwrap().history.undo.len();
for params in [
json!({"center": [10], "zoom": 2}),
json!({"center": [10, 20, 30], "zoom": 2}),
json!({"center": [10, "bad"], "zoom": 2}),
json!({"center": [10, 20], "zoom": 2, "rotation": "bad"}),
json!({"center": [10, 20], "zoom": 2, "rotation": null}),
] {
assert!(s.execute("view.saved.new", &params).is_err(), "accepted {params}");
assert!(s.doc().unwrap().doc.views.is_empty());
assert_eq!(s.doc().unwrap().history.undo.len(), before);
}
s.execute("view.saved.new", &json!({"center": [10, 20], "zoom": 2, "rotation": -15})).unwrap();
let views = &s.doc().unwrap().doc.views;
assert_eq!(views.len(), 1);
assert_eq!(views[0].rotation, -15.0);
}
#[test]
fn save_rename_delete_views() {
let mut s = Session::new();