1use std::fmt;
19
20pub const MAX_TABLE_CELLS_VAR: &str = "DOCLING_RS_MAX_TABLE_CELLS";
23pub const MAX_TOTAL_TABLE_CELLS_VAR: &str = "DOCLING_RS_MAX_TOTAL_TABLE_CELLS";
27
28pub fn max_table_cells() -> usize {
30 crate::env::parse::<usize>(MAX_TABLE_CELLS_VAR).unwrap_or(1_000_000)
31}
32
33pub fn max_total_table_cells() -> usize {
35 crate::env::parse::<usize>(MAX_TOTAL_TABLE_CELLS_VAR).unwrap_or(5_000_000)
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct LimitExceeded {
42 pub variable: &'static str,
43 pub limit: usize,
44 pub actual: usize,
45 pub what: String,
46}
47
48impl fmt::Display for LimitExceeded {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(
51 f,
52 "{}: {} cells exceeds the limit of {} ({})",
53 self.what, self.actual, self.limit, self.variable
54 )
55 }
56}
57
58impl std::error::Error for LimitExceeded {}
59
60#[derive(Debug, Clone)]
65pub struct TableCellBudget {
66 per_table: usize,
67 total_cap: usize,
68 total: usize,
69}
70
71impl Default for TableCellBudget {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl TableCellBudget {
78 pub fn new() -> Self {
79 Self {
80 per_table: max_table_cells(),
81 total_cap: max_total_table_cells(),
82 total: 0,
83 }
84 }
85
86 pub fn table(&mut self, what: &str, rows: usize, cols: usize) -> Result<(), LimitExceeded> {
89 let area = rows.saturating_mul(cols);
90 if rows > self.per_table || cols > self.per_table || area > self.per_table {
91 return Err(LimitExceeded {
92 variable: MAX_TABLE_CELLS_VAR,
93 limit: self.per_table,
94 actual: area.max(rows).max(cols),
95 what: format!("{what} of {rows}x{cols}"),
96 });
97 }
98 self.total = self.total.saturating_add(area);
99 if self.total > self.total_cap {
100 return Err(LimitExceeded {
101 variable: MAX_TOTAL_TABLE_CELLS_VAR,
102 limit: self.total_cap,
103 actual: self.total,
104 what: format!("{what}: the document's tables together"),
105 });
106 }
107 Ok(())
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn per_table_and_total_caps() {
117 let mut b = TableCellBudget {
118 per_table: 100,
119 total_cap: 250,
120 total: 0,
121 };
122 assert!(b.table("t", 10, 10).is_ok());
123 let e = b.table("t", 10, 11).unwrap_err();
124 assert_eq!(e.variable, MAX_TABLE_CELLS_VAR);
125 assert_eq!(e.actual, 110);
126 assert_eq!(b.table("t", 101, 0).unwrap_err().actual, 101);
128 assert!(b.table("t", 0, 101).is_err());
129 assert!(b.table("t", 10, 10).is_ok());
130 let e = b.table("t", 10, 10).unwrap_err();
131 assert_eq!(e.variable, MAX_TOTAL_TABLE_CELLS_VAR);
132 assert_eq!(e.actual, 300);
133 let mut b = TableCellBudget::new();
135 assert!(b.table("t", usize::MAX, usize::MAX).is_err());
136 assert!(b.table("t", usize::MAX, 0).is_err());
137 }
138}