Fix smart album gte/lte rules missing equal f-number and focal length

Found while testing crates/catalog/src/rules.rs:673 (num_op), reached via Rule::matches at :956-957

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-23 21:24:32 +02:00
co-authored by Claude Haiku 5.5
parent 069e32f909
commit c7cff2d9a7
+26 -2
View File
@@ -670,6 +670,11 @@ fn number(v: &Value) -> Option<f64> {
}
}
/// Widens an `f32` to the `f64` nearest its shortest decimal form (1.4f32 -> 1.4), so it compares equal to the value a user types.
fn f32_as_decimal(v: f32) -> f64 {
v.to_string().parse().unwrap_or(v as f64)
}
fn num_op(op: &str, have: Option<f64>, value: &Value) -> bool {
if op == "between" {
let (a, b) = match value {
@@ -953,8 +958,8 @@ impl Rule {
"editDate" => date_op(op, p.edited.as_deref(), value),
"shutterSpeed" => num_op(op, crate::parse_shutter_seconds(&m.shutter), &shutter_value(value)),
"iso" => num_op(op, m.iso.map(|v| v as f64), value),
"aperture" => num_op(op, m.aperture.map(|v| v as f64), value),
"focalLength" => num_op(op, m.focal_mm.map(|v| v as f64), value),
"aperture" => num_op(op, m.aperture.map(f32_as_decimal), value),
"focalLength" => num_op(op, m.focal_mm.map(f32_as_decimal), value),
"longEdge" | "shortEdge" => {
let (w, h) = p.shown_size();
num_op(op, Some(if field == "longEdge" { w.max(h) } else { w.min(h) }.round()), value)
@@ -1129,6 +1134,25 @@ mod tests {
serde_json::from_value(v).unwrap()
}
#[test]
fn f32_boundary_values_match_gte_lte() {
let cat = Catalog::new();
let matches =
|p: &Photo, field: &str, op: &str, value: f64| rs(json!({"rules": [{"field": field, "op": op, "value": value}]})).matches(p, &cat);
for v in [1.4f32, 1.8, 2.8, 5.6, 0.95] {
let mut p = photo(1);
p.meta.aperture = Some(v);
p.meta.focal_mm = Some(v);
let w = v.to_string().parse::<f64>().unwrap();
for field in ["aperture", "focalLength"] {
assert!(matches(&p, field, "gte", w), "{field} gte {w}");
assert!(matches(&p, field, "lte", w), "{field} lte {w}");
assert!(!matches(&p, field, "gt", w), "{field} gt {w}");
assert!(!matches(&p, field, "lt", w), "{field} lt {w}");
}
}
}
#[test]
fn fields_ops_and_groups() {
let cat = Catalog::new();