Refactor model for strict Clippy checks

This commit is contained in:
Dylan M. Taylor
2026-10-28 10:52:53 +00:00
parent 7898cc8cfd
commit 95fcdcc2ca
8 changed files with 55 additions and 41 deletions
+3 -2
View File
@@ -90,10 +90,11 @@ pub mod opt {
#[cfg(test)]
mod tests {
#[test]
fn roundtrip() {
fn roundtrip() -> Result<(), Box<dyn std::error::Error>> {
for data in [&b""[..], b"a", b"ab", b"abc", b"hello world", &[0, 255, 128, 7]] {
assert_eq!(super::decode(&super::encode(data)).unwrap(), data);
assert_eq!(super::decode(&super::encode(data)).ok_or("none")?, data);
}
assert_eq!(super::encode(b"Man"), "TWFu");
Ok(())
}
}
+4 -3
View File
@@ -27,7 +27,7 @@ mod tests {
use gridcraft_core::CellRef;
#[test]
fn comments_and_hyperlinks_survive_json() {
fn comments_and_hyperlinks_survive_json() -> Result<(), Box<dyn std::error::Error>> {
let mut s = Sheet::new("S");
let a1 = CellRef::new(0, 0);
let c3 = CellRef::new(2, 2);
@@ -45,9 +45,10 @@ mod tests {
},
);
s.hyperlinks.insert(c3, Hyperlink { target: "Sheet1!A1".into(), tooltip: Some("jump".into()), external: false });
let json = serde_json::to_string(&s).expect("serialize");
let back: Sheet = serde_json::from_str(&json).expect("deserialize");
let json = serde_json::to_string(&s)?;
let back: Sheet = serde_json::from_str(&json)?;
assert_eq!(back.comments, s.comments);
assert_eq!(back.hyperlinks, s.hyperlinks);
Ok(())
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
use super::*;
use super::{CellRef, RangeRef};
use serde::{Deserialize, Serialize};
// ---------------------------------------------------------------- pivot tables
+2 -1
View File
@@ -1,4 +1,5 @@
use super::*;
use super::{CellRef, RangeRef};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Orientation {
+10 -8
View File
@@ -106,28 +106,30 @@ mod tests {
use super::*;
#[test]
fn legacy_hash_matches_the_published_example() {
fn legacy_hash_matches_the_published_example() -> Result<(), Box<dyn std::error::Error>> {
// The hash Excel writes for the password "password" (`password="83AF"`).
assert_eq!(legacy_hash("password"), 0x83AF);
assert!(PasswordHash::Legacy(0x83AF).verify("password").unwrap());
assert!(!PasswordHash::Legacy(0x83AF).verify("Password").unwrap());
assert!(PasswordHash::Legacy(0x83AF).verify("password")?);
assert!(!PasswordHash::Legacy(0x83AF).verify("Password")?);
assert_eq!(legacy_hash(""), 0xCE4B);
Ok(())
}
#[test]
fn iterated_hash_verifies_and_refuses_what_it_cannot_check() {
fn iterated_hash_verifies_and_refuses_what_it_cannot_check() -> Result<(), Box<dyn std::error::Error>> {
let h = PasswordHash::new_sha512("s3cret", b"0123456789abcdef");
assert!(h.verify("s3cret").unwrap());
assert!(!h.verify("s3cret ").unwrap());
assert!(h.verify("s3cret")?);
assert!(!h.verify("s3cret ")?);
assert!(matches!(&h, PasswordHash::Iterated { algorithm, spin_count: DEFAULT_SPIN_COUNT, .. } if algorithm == "SHA-512"));
let PasswordHash::Iterated { hash, salt, .. } = h else { return };
let PasswordHash::Iterated { hash, salt, .. } = h else { return Err("not an iterated hash".into()) };
let other =
|algorithm: &str, spin_count| PasswordHash::Iterated { algorithm: algorithm.into(), hash: hash.clone(), salt: salt.clone(), spin_count };
assert!(other("SHA-1", 10).verify("s3cret").is_err());
assert!(other("MD5", 10).verify("s3cret").is_err());
assert!(other("SHA-512", MAX_SPIN_COUNT + 1).verify("s3cret").is_err());
assert!(!other("SHA-256", 2).verify("s3cret").unwrap());
assert!(!other("SHA-256", 2).verify("s3cret")?);
let broken = PasswordHash::Iterated { algorithm: "SHA-512".into(), hash: "!!".into(), salt: salt.clone(), spin_count: 1 };
assert!(broken.verify("s3cret").is_err());
Ok(())
}
}
+11 -7
View File
@@ -330,30 +330,32 @@ mod tests {
}
#[test]
fn set_get_remove() {
fn set_get_remove() -> Result<(), Box<dyn std::error::Error>> {
let mut s = CellStore::new();
s.set(c(0, 0), Cell::value(1.0.into()));
s.set(c(100, 5), Cell::value("x".into()));
assert_eq!(s.len(), 2);
assert_eq!(s.get(c(100, 5)).map(|x| x.value.clone()), Some(Value::from("x")));
assert_eq!(s.used_range().unwrap().a1(), "A1:F101");
assert_eq!(s.used_range().ok_or("none")?.a1(), "A1:F101");
s.set(c(0, 0), Cell::default());
assert_eq!(s.len(), 1);
assert!(s.remove(c(100, 5)).is_some());
assert!(s.is_empty());
assert!(s.used_range().is_none());
Ok(())
}
#[test]
fn cow_snapshots() {
fn cow_snapshots() -> Result<(), Box<dyn std::error::Error>> {
let mut s = CellStore::new();
for r in 0..1000 {
s.set(c(r, 0), Cell::value((r as f64).into()));
}
let snap = s.clone();
s.set(c(5, 0), Cell::value(99.0.into()));
assert_eq!(snap.get(c(5, 0)).unwrap().value, Value::Number(5.0));
assert_eq!(s.get(c(5, 0)).unwrap().value, Value::Number(99.0));
assert_eq!(snap.get(c(5, 0)).ok_or("none")?.value, Value::Number(5.0));
assert_eq!(s.get(c(5, 0)).ok_or("none")?.value, Value::Number(99.0));
Ok(())
}
#[test]
@@ -371,14 +373,16 @@ mod tests {
}
#[test]
fn range_iteration_order() {
fn range_iteration_order() -> Result<(), Box<dyn std::error::Error>> {
let mut s = CellStore::new();
s.set(c(70, 2), Cell::value(3.0.into()));
s.set(c(1, 1), Cell::value(1.0.into()));
s.set(c(1, 3), Cell::value(2.0.into()));
s.set(c(500, 0), Cell::value(4.0.into()));
let v: Vec<f64> = s.iter_range(RangeRef::parse("A1:D100").unwrap()).map(|(_, x)| x.value.as_f64().unwrap()).collect();
let v: Vec<f64> =
s.iter_range(RangeRef::parse("A1:D100").ok_or("none")?).map(|(_, x)| x.value.as_f64()).collect::<Option<_>>().ok_or("none")?;
assert_eq!(v, [1.0, 2.0, 3.0]);
Ok(())
}
#[test]
+16 -13
View File
@@ -500,32 +500,34 @@ mod tests {
}
#[test]
fn a_theme_saved_before_per_script_fonts_still_opens() {
let mut json = serde_json::to_value(Theme::default()).unwrap();
let fields = json.as_object_mut().unwrap();
fn a_theme_saved_before_per_script_fonts_still_opens() -> Result<(), Box<dyn std::error::Error>> {
let mut json = serde_json::to_value(Theme::default())?;
let fields = json.as_object_mut().ok_or("none")?;
assert!(fields.remove("major_font_scripts").is_some() && fields.remove("minor_font_scripts").is_some());
assert_eq!(serde_json::from_value::<Theme>(json).unwrap(), Theme::default());
assert_eq!(serde_json::from_value::<Theme>(json)?, Theme::default());
Ok(())
}
#[test]
fn colors() {
fn colors() -> Result<(), Box<dyn std::error::Error>> {
let th = Theme::default();
assert_eq!(Color::from_hex("#FF0000").unwrap().resolve(&th), Some([255, 0, 0]));
assert_eq!(Color::from_hex("FF00FF00").unwrap().resolve(&th), Some([0, 255, 0]));
assert_eq!(Color::from_hex("#FF0000").ok_or("none")?.resolve(&th), Some([255, 0, 0]));
assert_eq!(Color::from_hex("FF00FF00").ok_or("none")?.resolve(&th), Some([0, 255, 0]));
assert_eq!(Color::Theme(0, -500).resolve(&th), Some([128, 128, 128]));
assert_eq!(Color::Theme(1, 500).resolve(&th), Some([128, 128, 128]));
assert_eq!(Color::Auto.resolve(&th), None);
Ok(())
}
#[test]
fn theme_json_keeps_ea_and_cs_and_reads_old_json() {
fn theme_json_keeps_ea_and_cs_and_reads_old_json() -> Result<(), Box<dyn std::error::Error>> {
// JSON written before the ea/cs fields existed still deserializes (`serde(default)`).
let mut old = serde_json::to_value(Theme::default()).unwrap();
let obj = old.as_object_mut().unwrap();
let mut old = serde_json::to_value(Theme::default())?;
let obj = old.as_object_mut().ok_or("none")?;
for key in ["major_font_ea", "minor_font_ea", "major_font_cs", "minor_font_cs"] {
assert!(obj.remove(key).is_some(), "the default theme serializes {key}");
}
assert_eq!(serde_json::from_value::<Theme>(old).unwrap(), Theme::default());
assert_eq!(serde_json::from_value::<Theme>(old)?, Theme::default());
// Set fields serialize and come back.
let t = Theme {
@@ -535,8 +537,9 @@ mod tests {
minor_font_cs: "Times New Roman".into(),
..Theme::default()
};
let json = serde_json::to_string(&t).unwrap();
let json = serde_json::to_string(&t)?;
assert!(json.contains("major_font_ea") && json.contains("游ゴシック"), "{json}");
assert_eq!(serde_json::from_str::<Theme>(&json).unwrap(), t);
assert_eq!(serde_json::from_str::<Theme>(&json)?, t);
Ok(())
}
}
+7 -6
View File
@@ -439,7 +439,7 @@ mod tests {
use super::*;
#[test]
fn phonetics_are_additive_in_json() {
fn phonetics_are_additive_in_json() -> Result<(), Box<dyn std::error::Error>> {
let mut wb = Workbook::new();
assert!(wb.phonetics.is_empty(), "new workbooks have no phonetic data");
wb.phonetics.push(PhoneticString {
@@ -447,12 +447,13 @@ mod tests {
runs: vec![PhoneticRun { start: 0, end: 1, text: "ヒガシ".into() }],
props: Some(PhoneticProps { font_id: 3, kind: Some("Hiragana".into()), alignment: None }),
});
let json = serde_json::to_string(&wb).expect("serialize");
assert_eq!(serde_json::from_str::<Workbook>(&json).expect("deserialize").phonetics, wb.phonetics);
let json = serde_json::to_string(&wb)?;
assert_eq!(serde_json::from_str::<Workbook>(&json)?.phonetics, wb.phonetics);
// JSON written before the field existed still loads.
let mut value: serde_json::Value = serde_json::from_str(&json).expect("value");
value.as_object_mut().expect("an object").remove("phonetics");
assert!(serde_json::from_value::<Workbook>(value).expect("older JSON").phonetics.is_empty());
let mut value: serde_json::Value = serde_json::from_str(&json)?;
value.as_object_mut().ok_or("none")?.remove("phonetics");
assert!(serde_json::from_value::<Workbook>(value)?.phonetics.is_empty());
Ok(())
}
#[test]