formualizer_eval/engine/
reference_fingerprint.rs

1use formualizer_parse::parser::ReferenceType;
2use std::collections::hash_map::DefaultHasher;
3use std::hash::{Hash, Hasher};
4
5/// Extension trait for ReferenceType to generate deterministic fingerprints
6pub trait ReferenceFingerprint {
7    /// Generate a stable, deterministic fingerprint for this reference
8    /// Used as a cache key for flattened ranges
9    fn fingerprint(&self) -> String;
10}
11
12impl ReferenceFingerprint for ReferenceType {
13    fn fingerprint(&self) -> String {
14        // Create a deterministic string representation
15        // Avoid using Debug trait which might change
16        match self {
17            ReferenceType::Cell { sheet, row, col } => {
18                format!("cell:{}:{}:{}", sheet.as_deref().unwrap_or("_"), row, col)
19            }
20            ReferenceType::Range {
21                sheet,
22                start_row,
23                start_col,
24                end_row,
25                end_col,
26            } => {
27                format!(
28                    "range:{}:{}:{}:{}:{}",
29                    sheet.as_deref().unwrap_or("_"),
30                    start_row.map_or("*".to_string(), |r| r.to_string()),
31                    start_col.map_or("*".to_string(), |c| c.to_string()),
32                    end_row.map_or("*".to_string(), |r| r.to_string()),
33                    end_col.map_or("*".to_string(), |c| c.to_string())
34                )
35            }
36            ReferenceType::Table(table_ref) => {
37                // Use a hash for complex table references
38                let mut hasher = DefaultHasher::new();
39                table_ref.hash(&mut hasher);
40                format!("table:{:x}", hasher.finish())
41            }
42            ReferenceType::NamedRange(name) => {
43                format!("named:{name}")
44            }
45        }
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_cell_fingerprint() {
55        let ref1 = ReferenceType::Cell {
56            sheet: None,
57            row: 5,
58            col: 10,
59        };
60
61        let ref2 = ReferenceType::Cell {
62            sheet: None,
63            row: 5,
64            col: 10,
65        };
66
67        assert_eq!(ref1.fingerprint(), ref2.fingerprint());
68        assert_eq!(ref1.fingerprint(), "cell:_:5:10");
69    }
70
71    #[test]
72    fn test_range_fingerprint() {
73        let ref1 = ReferenceType::Range {
74            sheet: Some("Sheet1".to_string()),
75            start_row: Some(0),
76            start_col: Some(0),
77            end_row: Some(99),
78            end_col: Some(0),
79        };
80
81        let ref2 = ReferenceType::Range {
82            sheet: Some("Sheet1".to_string()),
83            start_row: Some(0),
84            start_col: Some(0),
85            end_row: Some(99),
86            end_col: Some(0),
87        };
88
89        assert_eq!(ref1.fingerprint(), ref2.fingerprint());
90        assert_eq!(ref1.fingerprint(), "range:Sheet1:0:0:99:0");
91    }
92
93    #[test]
94    fn test_whole_column_fingerprint() {
95        let ref1 = ReferenceType::Range {
96            sheet: None,
97            start_row: None,
98            start_col: Some(0),
99            end_row: None,
100            end_col: Some(0),
101        };
102
103        assert_eq!(ref1.fingerprint(), "range:_:*:0:*:0");
104    }
105
106    #[test]
107    fn test_named_range_fingerprint() {
108        let ref1 = ReferenceType::NamedRange("MyRange".to_string());
109        let ref2 = ReferenceType::NamedRange("MyRange".to_string());
110
111        assert_eq!(ref1.fingerprint(), ref2.fingerprint());
112        assert_eq!(ref1.fingerprint(), "named:MyRange");
113    }
114
115    #[test]
116    fn test_different_refs_different_fingerprints() {
117        let ref1 = ReferenceType::Cell {
118            sheet: None,
119            row: 1,
120            col: 1,
121        };
122
123        let ref2 = ReferenceType::Cell {
124            sheet: None,
125            row: 1,
126            col: 2,
127        };
128
129        assert_ne!(ref1.fingerprint(), ref2.fingerprint());
130    }
131}