use serde::{Deserialize, Serialize};
use crate::source::StyleSource;
use crate::source_style::convert_style;
use crate::{
ErrorCode, FileMakerError, Length, ResourceLimits, Result, TableColumn, TableIr, TableSpec,
TableStyleRule,
};
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TableStyleRuleSource {
pub when: String,
#[serde(default)]
pub style: StyleSource,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TableSource {
pub columns: Vec<TableColumn>,
#[serde(default = "default_true")]
pub repeat_header: bool,
#[serde(default)]
pub group_by: Option<String>,
#[serde(default)]
pub total_fields: Vec<String>,
#[serde(default)]
pub conditional_styles: Vec<TableStyleRuleSource>,
#[serde(default = "default_auto_samples")]
pub auto_sample_rows: usize,
#[serde(default)]
pub max_rows: Option<u64>,
#[serde(default)]
pub max_row_fields: Option<usize>,
#[serde(default)]
pub max_cell_bytes: Option<usize>,
#[serde(default = "default_header_height")]
pub header_height: Length,
#[serde(default)]
pub row_height: Option<Length>,
}
pub(crate) fn convert_table(source: &TableSource, limits: &ResourceLimits) -> Result<TableIr> {
if matches!(source.header_height, Length::Auto) {
return Err(table_source_error("table header height cannot be auto"));
}
reject_raised_limit(source.max_rows, limits.max_rows, "table row")?;
reject_raised_limit(
source.max_row_fields,
limits.max_elements,
"table row field",
)?;
reject_raised_limit(
source.max_cell_bytes,
limits.max_text_bytes,
"table cell byte",
)?;
let spec = TableSpec {
columns: source.columns.clone(),
repeat_header: source.repeat_header,
group_by: source.group_by.clone(),
total_fields: source.total_fields.clone(),
conditional_styles: source
.conditional_styles
.iter()
.map(|rule| {
Ok(TableStyleRule {
when: rule.when.clone(),
style: convert_style(&rule.style)?,
})
})
.collect::<Result<Vec<_>>>()?,
style_expression_steps: limits.max_expression_steps,
auto_sample_rows: source.auto_sample_rows,
max_rows: source.max_rows.unwrap_or(limits.max_rows),
max_row_fields: source.max_row_fields.unwrap_or(limits.max_elements),
max_cell_bytes: source.max_cell_bytes.unwrap_or(limits.max_text_bytes),
};
spec.validate()?;
Ok(TableIr {
spec,
header_height: source.header_height,
row_height: source.row_height,
rows: Vec::new(),
})
}
const fn default_true() -> bool {
true
}
const fn default_auto_samples() -> usize {
16
}
fn default_header_height() -> Length {
Length::Absolute(crate::Unit::from_raw(18_000_000))
}
fn table_source_error(message: impl Into<String>) -> FileMakerError {
FileMakerError::new(ErrorCode::SchemaField, message)
}
fn reject_raised_limit<T>(requested: Option<T>, global: T, label: &str) -> Result<()>
where
T: Copy + PartialOrd,
{
if requested.is_some_and(|value| value > global) {
return Err(FileMakerError::new(
ErrorCode::LimitExceeded,
format!("{label} limit cannot exceed the compiler limit"),
));
}
Ok(())
}