use crate::core::validation::ValidationResult;
pub fn validate_cell_range(range: &str) -> ValidationResult {
if range.is_empty() {
return ValidationResult::Invalid("Cell range cannot be empty".to_string());
}
let (_sheet_name, cell_range) = if range.contains('!') {
let parts: Vec<&str> = range.split('!').collect();
if parts.len() != 2 {
return ValidationResult::Invalid(
"Invalid cell range format. Expected 'SheetName!A1:B10' or 'A1:B10'".to_string(),
);
}
let sheet = parts[0];
let cell_ref = parts[1];
if sheet.is_empty() {
return ValidationResult::Invalid("Sheet name cannot be empty".to_string());
}
if sheet.starts_with('\'') && (!sheet.ends_with('\'') || sheet.len() < 2) {
return ValidationResult::Invalid(
"Invalid quoted sheet name. Must start and end with single quotes".to_string(),
);
}
(Some(sheet.to_string()), cell_ref)
} else {
(None, range)
};
if let Err(e) = validate_cell_reference_range(cell_range) {
return ValidationResult::Invalid(e);
}
ValidationResult::Valid
}
fn validate_cell_reference_range(range: &str) -> Result<(), String> {
if range.is_empty() {
return Err("Cell range cannot be empty".to_string());
}
if range.contains(':') {
let parts: Vec<&str> = range.split(':').collect();
if parts.len() != 2 {
return Err("Invalid range format. Expected 'A1:B10'".to_string());
}
validate_cell_reference(parts[0])?;
validate_cell_reference(parts[1])?;
} else {
validate_cell_reference(range)?;
}
Ok(())
}
fn validate_cell_reference(cell_ref: &str) -> Result<(), String> {
if cell_ref.is_empty() {
return Err("Cell reference cannot be empty".to_string());
}
let (col_part, row_part) = split_column_and_row(cell_ref)?;
if col_part.is_empty() {
return Err("Column part cannot be empty".to_string());
}
for c in col_part.chars() {
if !c.is_ascii_uppercase() {
return Err(format!("Invalid column letter '{}'. Only A-Z allowed", c));
}
}
if row_part.is_empty() {
return Err("Row number cannot be empty".to_string());
}
let row_num = row_part.parse::<u32>().map_err(|_| {
format!(
"Invalid row number '{}'. Must be a positive integer",
row_part
)
})?;
if row_num == 0 {
return Err("Row number must be greater than 0".to_string());
}
if column_number_to_index(col_part) > 18278 {
return Err("Column exceeds maximum limit (ZHD)".to_string());
}
if row_num > 9_999_999 {
return Err("Row exceeds maximum limit (9,999,999)".to_string());
}
Ok(())
}
fn split_column_and_row(cell_ref: &str) -> Result<(&str, &str), String> {
let mut row_start = 0;
for (i, c) in cell_ref.chars().enumerate() {
if c.is_ascii_digit() {
row_start = i;
break;
}
}
if row_start == 0 {
return Err("Invalid cell reference format. Expected format like 'A1'".to_string());
}
let col_part = &cell_ref[..row_start];
let row_part = &cell_ref[row_start..];
Ok((col_part, row_part))
}
fn column_number_to_index(col: &str) -> u32 {
let mut index = 0;
for (i, c) in col.chars().enumerate() {
index += (c as u32 - 'A' as u32 + 1) * 26u32.pow((col.len() - 1 - i) as u32);
}
index
}
pub fn validate_value_render_option(option: &Option<String>) -> ValidationResult {
match option {
Some(opt) => match opt.as_str() {
"DisplayValue" | "Unformatted" | "FormattedString" => ValidationResult::Valid,
_ => ValidationResult::Invalid(
"Invalid valueRenderOption. Must be 'DisplayValue', 'Unformatted', or 'FormattedString'"
.to_string(),
),
},
None => ValidationResult::Valid,
}
}
pub fn validate_date_time_render_option(option: &Option<String>) -> ValidationResult {
match option {
Some(opt) => match opt.as_str() {
"FormattedString" | "SerialNumber" => ValidationResult::Valid,
_ => ValidationResult::Invalid(
"Invalid dateTimeRenderOption. Must be 'FormattedString' or 'SerialNumber'"
.to_string(),
),
},
None => ValidationResult::Valid,
}
}
pub fn validate_data_matrix_consistency(data: &[Vec<serde_json::Value>]) -> ValidationResult {
if data.is_empty() {
return ValidationResult::Valid; }
let first_row_len = data[0].len();
for (i, row) in data.iter().enumerate() {
if row.len() != first_row_len {
return ValidationResult::Invalid(format!(
"Inconsistent matrix dimensions. Row 0 has {} columns, but row {} has {} columns",
first_row_len,
i,
row.len()
));
}
}
ValidationResult::Valid
}
pub fn validate_merge_range(range: &str) -> ValidationResult {
if let ValidationResult::Invalid(msg) = validate_cell_range(range) {
return ValidationResult::Invalid(msg);
}
if !range.contains(':') {
return ValidationResult::Invalid(
"Merge range must include multiple cells (e.g., A1:B2)".to_string(),
);
}
ValidationResult::Valid
}
pub fn validate_find_options(
_case_sensitive: &Option<bool>,
_match_entire_cell: &Option<bool>,
search_by_regex: &Option<bool>,
_include_formulas: &Option<bool>,
) -> ValidationResult {
if let Some(true) = search_by_regex {
if let Some(true) = _case_sensitive {
}
}
ValidationResult::Valid
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_cell_range_valid() {
assert!(matches!(
validate_cell_range("A1:B10"),
ValidationResult::Valid
));
assert!(matches!(
validate_cell_range("Z100:AA200"),
ValidationResult::Valid
));
assert!(matches!(
validate_cell_range("Sheet1!A1:B10"),
ValidationResult::Valid
));
assert!(matches!(
validate_cell_range("'Sheet Name'!A1:C10"),
ValidationResult::Valid
));
assert!(matches!(validate_cell_range("A1"), ValidationResult::Valid));
assert!(matches!(
validate_cell_range("Sheet1!Z100"),
ValidationResult::Valid
));
}
#[test]
fn test_validate_cell_range_invalid() {
assert!(matches!(
validate_cell_range(""),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_cell_range("A1:B10:C20"),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_cell_range("Sheet1!A1:"),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_cell_range("Sheet1!"),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_value_render_option() {
assert!(matches!(
validate_value_render_option(&Some("DisplayValue".to_string())),
ValidationResult::Valid
));
assert!(matches!(
validate_value_render_option(&Some("Invalid".to_string())),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_value_render_option(&None),
ValidationResult::Valid
));
}
#[test]
fn test_column_number_to_index() {
assert_eq!(column_number_to_index("A"), 1);
assert_eq!(column_number_to_index("Z"), 26);
assert_eq!(column_number_to_index("AA"), 27);
assert_eq!(column_number_to_index("AZ"), 52);
assert_eq!(column_number_to_index("BA"), 53);
}
#[test]
fn test_validate_data_matrix_consistency() {
let data1 = vec![
vec![
serde_json::Value::String("A".to_string()),
serde_json::Value::String("B".to_string()),
],
vec![
serde_json::Value::String("C".to_string()),
serde_json::Value::String("D".to_string()),
],
];
assert!(matches!(
validate_data_matrix_consistency(&data1),
ValidationResult::Valid
));
let data2 = vec![
vec![
serde_json::Value::String("A".to_string()),
serde_json::Value::String("B".to_string()),
],
vec![serde_json::Value::String("C".to_string())],
];
assert!(matches!(
validate_data_matrix_consistency(&data2),
ValidationResult::Invalid(_)
));
}
}