use crate::error::Error;
use crate::model::{MergedRegion, Sheet};
pub(crate) const MAX_MERGE_REGIONS: usize = 20_000;
pub(crate) fn resolve(sheet: &mut Sheet, regions: Vec<MergedRegion>) -> Result<(), Error> {
if regions.len() > MAX_MERGE_REGIONS {
return Err(Error::TooManyMergedRanges {
count: regions.len(),
limit: MAX_MERGE_REGIONS,
});
}
let mut accepted: Vec<MergedRegion> = Vec::with_capacity(regions.len());
for region in ®ions {
validate_region(region, &accepted)?;
accepted.push(*region);
}
for region in regions {
sheet.insert_merge(region);
}
sheet.finalize_merges();
Ok(())
}
fn validate_region(region: &MergedRegion, accepted: &[MergedRegion]) -> Result<(), Error> {
if region.start.row > region.end.row || region.start.col > region.end.col {
return Err(Error::InvalidMergedRange {
start: region.start.to_a1(),
end: region.end.to_a1(),
reason: "start must not be greater than end".to_string(),
});
}
for other in accepted {
if regions_overlap(region, other) {
return Err(Error::InvalidMergedRange {
start: region.start.to_a1(),
end: region.end.to_a1(),
reason: "overlaps with another merged range".to_string(),
});
}
}
Ok(())
}
fn regions_overlap(a: &MergedRegion, b: &MergedRegion) -> bool {
a.start.row <= b.end.row
&& a.end.row >= b.start.row
&& a.start.col <= b.end.col
&& a.end.col >= b.start.col
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{CellRef, SheetVisibility};
fn region(start: (u32, u32), end: (u32, u32)) -> MergedRegion {
MergedRegion {
start: CellRef {
row: start.0,
col: start.1,
},
end: CellRef {
row: end.0,
col: end.1,
},
}
}
#[test]
fn registers_non_overlapping_regions() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let regions = vec![region((1, 1), (2, 2)), region((3, 1), (4, 2))];
resolve(&mut sheet, regions).unwrap();
assert_eq!(
sheet.get(CellRef { row: 2, col: 2 }),
sheet.get(CellRef { row: 1, col: 1 })
);
assert_eq!(
sheet.get(CellRef { row: 4, col: 2 }),
sheet.get(CellRef { row: 3, col: 1 })
);
}
#[test]
fn reversed_start_end_is_an_error() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let err = resolve(&mut sheet, vec![region((3, 3), (1, 1))]).unwrap_err();
assert!(matches!(err, Error::InvalidMergedRange { .. }));
}
#[test]
fn overlapping_regions_are_an_error() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let regions = vec![region((1, 1), (3, 3)), region((2, 2), (4, 4))];
let err = resolve(&mut sheet, regions).unwrap_err();
assert!(matches!(err, Error::InvalidMergedRange { .. }));
}
#[test]
fn adjacent_non_overlapping_regions_are_not_flagged() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let regions = vec![region((1, 1), (2, 2)), region((1, 3), (2, 4))];
resolve(&mut sheet, regions).unwrap();
}
#[test]
fn huge_region_validates_without_cell_count_cost() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
resolve(&mut sheet, vec![region((1, 1), (1_048_576, 16_384))]).unwrap();
}
#[test]
fn validation_failure_registers_nothing() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let regions = vec![region((1, 1), (2, 2)), region((1, 1), (3, 3))];
let err = resolve(&mut sheet, regions).unwrap_err();
assert!(matches!(err, Error::InvalidMergedRange { .. }));
assert!(sheet.get(CellRef { row: 1, col: 1 }).is_none());
assert!(sheet.merged_region_at(CellRef { row: 1, col: 1 }).is_none());
}
#[test]
fn empty_region_list_is_a_no_op() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
resolve(&mut sheet, vec![]).unwrap();
}
#[test]
fn region_count_at_the_limit_is_accepted() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let regions: Vec<MergedRegion> = (1..=MAX_MERGE_REGIONS as u32)
.map(|row| region((row, 1), (row, 2)))
.collect();
resolve(&mut sheet, regions).unwrap();
}
#[test]
fn region_count_over_the_limit_is_too_many_merged_ranges() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
let regions = vec![region((1, 1), (1, 1)); MAX_MERGE_REGIONS + 1];
let err = resolve(&mut sheet, regions).unwrap_err();
assert!(matches!(
err,
Error::TooManyMergedRanges {
count,
limit
} if count == MAX_MERGE_REGIONS + 1 && limit == MAX_MERGE_REGIONS
));
assert!(sheet.get(CellRef { row: 1, col: 1 }).is_none());
}
#[test]
fn one_by_one_region_is_handled() {
let mut sheet = Sheet::new("Sheet1".into(), SheetVisibility::Visible);
resolve(&mut sheet, vec![region((1, 1), (1, 1))]).unwrap();
assert!(sheet.get(CellRef { row: 1, col: 1 }).is_some());
}
#[test]
fn regions_overlap_boundary_values() {
assert!(regions_overlap(
®ion((1, 1), (3, 3)),
®ion((3, 3), (5, 5))
)); assert!(!regions_overlap(
®ion((1, 1), (2, 2)),
®ion((1, 3), (2, 4))
));
}
}