use std::fmt;
pub const MAX_TABLE_CELLS_VAR: &str = "DOCLING_RS_MAX_TABLE_CELLS";
pub const MAX_TOTAL_TABLE_CELLS_VAR: &str = "DOCLING_RS_MAX_TOTAL_TABLE_CELLS";
pub fn max_table_cells() -> usize {
crate::env::parse::<usize>(MAX_TABLE_CELLS_VAR).unwrap_or(1_000_000)
}
pub fn max_total_table_cells() -> usize {
crate::env::parse::<usize>(MAX_TOTAL_TABLE_CELLS_VAR).unwrap_or(5_000_000)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LimitExceeded {
pub variable: &'static str,
pub limit: usize,
pub actual: usize,
pub what: String,
}
impl fmt::Display for LimitExceeded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {} cells exceeds the limit of {} ({})",
self.what, self.actual, self.limit, self.variable
)
}
}
impl std::error::Error for LimitExceeded {}
#[derive(Debug, Clone)]
pub struct TableCellBudget {
per_table: usize,
total_cap: usize,
total: usize,
}
impl Default for TableCellBudget {
fn default() -> Self {
Self::new()
}
}
impl TableCellBudget {
pub fn new() -> Self {
Self {
per_table: max_table_cells(),
total_cap: max_total_table_cells(),
total: 0,
}
}
pub fn table(&mut self, what: &str, rows: usize, cols: usize) -> Result<(), LimitExceeded> {
let area = rows.saturating_mul(cols);
if rows > self.per_table || cols > self.per_table || area > self.per_table {
return Err(LimitExceeded {
variable: MAX_TABLE_CELLS_VAR,
limit: self.per_table,
actual: area.max(rows).max(cols),
what: format!("{what} of {rows}x{cols}"),
});
}
self.total = self.total.saturating_add(area);
if self.total > self.total_cap {
return Err(LimitExceeded {
variable: MAX_TOTAL_TABLE_CELLS_VAR,
limit: self.total_cap,
actual: self.total,
what: format!("{what}: the document's tables together"),
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn per_table_and_total_caps() {
let mut b = TableCellBudget {
per_table: 100,
total_cap: 250,
total: 0,
};
assert!(b.table("t", 10, 10).is_ok());
let e = b.table("t", 10, 11).unwrap_err();
assert_eq!(e.variable, MAX_TABLE_CELLS_VAR);
assert_eq!(e.actual, 110);
assert_eq!(b.table("t", 101, 0).unwrap_err().actual, 101);
assert!(b.table("t", 0, 101).is_err());
assert!(b.table("t", 10, 10).is_ok());
let e = b.table("t", 10, 10).unwrap_err();
assert_eq!(e.variable, MAX_TOTAL_TABLE_CELLS_VAR);
assert_eq!(e.actual, 300);
let mut b = TableCellBudget::new();
assert!(b.table("t", usize::MAX, usize::MAX).is_err());
assert!(b.table("t", usize::MAX, 0).is_err());
}
}