diff --git a/slack-messaging/src/blocks/data_table/cell.rs b/slack-messaging/src/blocks/data_table/cell.rs index a8d3485..0a5d0cb 100644 --- a/slack-messaging/src/blocks/data_table/cell.rs +++ b/slack-messaging/src/blocks/data_table/cell.rs @@ -17,6 +17,20 @@ pub enum DataTableCell { RichText(RichText), } +impl DataTableCell { + pub fn is_raw_text(&self) -> bool { + matches!(self, Self::RawText(_)) + } + + pub fn is_raw_number(&self) -> bool { + matches!(self, Self::RawNumber(_)) + } + + pub fn is_rich_text(&self) -> bool { + matches!(self, Self::RichText(_)) + } +} + impl> From for DataTableCell { fn from(value: T) -> Self { Self::RawText(RawText::from(value)) diff --git a/slack-messaging/src/blocks/data_table/mod.rs b/slack-messaging/src/blocks/data_table/mod.rs index 6c3e951..3843ef5 100644 --- a/slack-messaging/src/blocks/data_table/mod.rs +++ b/slack-messaging/src/blocks/data_table/mod.rs @@ -1,3 +1,5 @@ +use crate::errors::ValidationErrorKind; +use crate::value::Value; use crate::validators::*; use serde::Serialize; @@ -24,7 +26,7 @@ pub use row::DataTableRow; /// | Field | Type | Required | Validation | /// |-------|------|----------|------------| /// | block_id | String | No | Maximum 255 characters. | -/// | rows | Vec<[DataTableRow]> | Yes | Maximum 101 items (100 regular rows plus the header). Minimum 2 items (1 regular row plus the header). | +/// | rows | Vec<[DataTableRow]> | Yes | Maximum 101 items (100 regular rows plus the header). Minimum 2 items (1 regular row plus the header). The first row is a header, and `rich_text` cannot be used for the header cells. | /// | caption | String | Yes | N/A | /// | page_size | i64 | No | Minimum 1, maximum 100. | /// | row_header_column_index | i64 | No | Minimum 0. | @@ -247,7 +249,10 @@ pub struct DataTable { #[builder(validate("text::max_255"))] pub(crate) block_id: Option, - #[builder(push_item = "row", validate("required", "list::min_item_2", "list::max_item_101"))] + #[builder( + push_item = "row", + validate("required", "list::min_item_2", "list::max_item_101", "valid_header") + )] pub(crate) rows: Option>, #[builder(validate("required"))] @@ -262,10 +267,19 @@ pub struct DataTable { pub(crate) row_header_column_index: Option, } +fn valid_header(value: Value>) -> Value> { + list::inner_validator(value, ValidationErrorKind::RichTextTableHeader, |rows| { + rows.first().and_then(|row| row.cells.as_ref()).is_some_and(|cells| { + cells.iter().any(DataTableCell::is_rich_text) + }) + }) +} + #[cfg(test)] mod tests { use super::*; use crate::errors::*; + use crate::blocks::rich_text::prelude::*; #[test] fn it_implements_builder() { @@ -382,6 +396,20 @@ mod tests { assert!(errors.includes(ValidationErrorKind::MaxArraySize(101))); } + #[test] + fn it_requires_rows_field_to_have_header_row_without_rich_text() { + let err = DataTable::builder() + .caption("A Fabulous Table") + .row(rich_text_row(vec!["foo", "bar", "baz"])) + .row(row(vec!["000", "001", "002"])) + .build() + .unwrap_err(); + assert_eq!(err.object(), "DataTable"); + + let errors = err.field("rows"); + assert!(errors.includes(ValidationErrorKind::RichTextTableHeader)); + } + #[test] fn it_requires_caption_field() { let err = DataTable::builder() @@ -443,4 +471,27 @@ mod tests { fn row>(cells: Vec) -> DataTableRow { DataTableRow::from_iter(cells) } + + fn rich_text_row(cells: Vec<&str>) -> DataTableRow { + let cells = cells + .into_iter() + .map(|text| { + RichText::builder() + .element( + RichTextSection::builder() + .element( + RichTextElementText::builder() + .text(text) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .build() + .unwrap() + }) + .collect::>(); + row(cells) + } } diff --git a/slack-messaging/src/errors.rs b/slack-messaging/src/errors.rs index 443af0b..6042913 100644 --- a/slack-messaging/src/errors.rs +++ b/slack-messaging/src/errors.rs @@ -63,6 +63,9 @@ pub enum ValidationErrorKind { #[error("rich text block should have exactly one element")] RichTextSingleElement, + /// Rich text cannot be used for a table header row. + #[error("rich text cannot be used for a table header row")] + RichTextTableHeader, /// Each series within a chart must have a unique name. #[error("each series within a chart must have a unique name")] UniqueSeriesName, diff --git a/slack-messaging/src/validators/list.rs b/slack-messaging/src/validators/list.rs index 0bf1f76..eeb33cc 100644 --- a/slack-messaging/src/validators/list.rs +++ b/slack-messaging/src/validators/list.rs @@ -6,7 +6,7 @@ use paste::paste; type List = Value>; -fn inner_validator( +pub(crate) fn inner_validator( mut value: List, error: ValidationErrorKind, predicate: impl Fn(&[T]) -> bool,