@@ -10,6 +10,9 @@ use crate::*;
1010#[ derive( Debug , PartialEq , Clone , Eq , Hash , Serialize , Deserialize , ValueStruct ) ]
1111pub struct SlackBlockId ( pub String ) ;
1212
13+ #[ derive( Debug , PartialEq , Clone , Eq , Hash , Serialize , Deserialize , ValueStruct ) ]
14+ pub struct SlackTaskId ( pub String ) ;
15+
1316#[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
1417#[ serde( tag = "type" ) ]
1518pub enum SlackBlock {
@@ -35,12 +38,14 @@ pub enum SlackBlock {
3538 Markdown ( SlackMarkdownBlock ) ,
3639 #[ serde( rename = "rich_text" ) ]
3740 RichText ( SlackRichTextBlock ) ,
41+ #[ serde( rename = "table" ) ]
42+ Table ( SlackTableBlock ) ,
43+ #[ serde( rename = "task_card" ) ]
44+ TaskCard ( SlackTaskCardBlock ) ,
3845 #[ serde( rename = "share_shortcut" ) ]
3946 ShareShortcut ( serde_json:: Value ) ,
4047 #[ serde( rename = "event" ) ]
4148 Event ( serde_json:: Value ) ,
42- #[ serde( rename = "table" ) ]
43- Table ( serde_json:: Value ) ,
4449}
4550
4651#[ skip_serializing_none]
@@ -1088,6 +1093,19 @@ impl From<SlackRichTextBlock> for SlackBlock {
10881093 }
10891094}
10901095
1096+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
1097+ #[ serde( tag = "type" , rename_all = "snake_case" ) ]
1098+ pub enum SlackRichTextInlineContent {
1099+ #[ serde( rename = "rich_text" ) ]
1100+ RichText ( SlackRichTextBlock ) ,
1101+ }
1102+
1103+ impl From < SlackRichTextBlock > for SlackRichTextInlineContent {
1104+ fn from ( block : SlackRichTextBlock ) -> Self {
1105+ SlackRichTextInlineContent :: RichText ( block)
1106+ }
1107+ }
1108+
10911109#[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
10921110#[ serde( tag = "type" ) ]
10931111pub enum SlackRichTextElement {
@@ -1343,6 +1361,113 @@ impl From<SlackFileId> for SlackFileIdOrUrl {
13431361 }
13441362}
13451363
1364+ /**
1365+ * https://docs.slack.dev/reference/block-kit/blocks/table-block
1366+ */
1367+ #[ skip_serializing_none]
1368+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize , Builder ) ]
1369+ pub struct SlackTableBlock {
1370+ pub block_id : Option < SlackBlockId > ,
1371+ pub rows : Vec < Vec < SlackTableCell > > ,
1372+ pub column_settings : Option < Vec < SlackTableColumnSetting > > ,
1373+ }
1374+
1375+ impl From < SlackTableBlock > for SlackBlock {
1376+ fn from ( block : SlackTableBlock ) -> Self {
1377+ SlackBlock :: Table ( block)
1378+ }
1379+ }
1380+
1381+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
1382+ #[ serde( tag = "type" ) ]
1383+ pub enum SlackTableCell {
1384+ #[ serde( rename = "raw_text" ) ]
1385+ RawText ( SlackTableRawTextCell ) ,
1386+ #[ serde( rename = "rich_text" ) ]
1387+ RichText ( SlackTableRichTextCell ) ,
1388+ }
1389+
1390+ #[ skip_serializing_none]
1391+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize , Builder ) ]
1392+ pub struct SlackTableRawTextCell {
1393+ pub text : String ,
1394+ }
1395+
1396+ #[ skip_serializing_none]
1397+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize , Builder ) ]
1398+ pub struct SlackTableRichTextCell {
1399+ pub elements : Vec < SlackRichTextElement > ,
1400+ }
1401+
1402+ #[ skip_serializing_none]
1403+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize , Builder ) ]
1404+ pub struct SlackTableColumnSetting {
1405+ pub align : Option < SlackTableColumnAlign > ,
1406+ pub is_wrapped : Option < bool > ,
1407+ }
1408+
1409+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
1410+ #[ serde( rename_all = "snake_case" ) ]
1411+ pub enum SlackTableColumnAlign {
1412+ Left ,
1413+ Center ,
1414+ Right ,
1415+ }
1416+
1417+ /**
1418+ * https://docs.slack.dev/reference/block-kit/blocks/task-card-block
1419+ */
1420+ #[ skip_serializing_none]
1421+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize , Builder ) ]
1422+ pub struct SlackTaskCardBlock {
1423+ pub task_id : SlackTaskId ,
1424+ pub title : String ,
1425+ pub block_id : Option < SlackBlockId > ,
1426+ pub status : Option < SlackTaskCardStatus > ,
1427+ #[ serde( rename = "details" ) ]
1428+ pub details : Option < SlackRichTextInlineContent > ,
1429+ #[ serde( rename = "output" ) ]
1430+ pub output : Option < SlackRichTextInlineContent > ,
1431+ pub sources : Option < Vec < SlackTaskCardSource > > ,
1432+ }
1433+
1434+ impl From < SlackTaskCardBlock > for SlackBlock {
1435+ fn from ( block : SlackTaskCardBlock ) -> Self {
1436+ SlackBlock :: TaskCard ( block)
1437+ }
1438+ }
1439+
1440+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
1441+ #[ serde( rename_all = "snake_case" ) ]
1442+ pub enum SlackTaskCardStatus {
1443+ Pending ,
1444+ InProgress ,
1445+ Complete ,
1446+ Error ,
1447+ }
1448+
1449+ /**
1450+ * https://docs.slack.dev/reference/block-kit/block-elements/url-source-element
1451+ */
1452+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize , Builder ) ]
1453+ pub struct SlackUrlSourceElement {
1454+ pub url : Url ,
1455+ pub text : String ,
1456+ }
1457+
1458+ #[ derive( Debug , PartialEq , Clone , Serialize , Deserialize ) ]
1459+ #[ serde( tag = "type" ) ]
1460+ pub enum SlackTaskCardSource {
1461+ #[ serde( rename = "url" ) ]
1462+ Url ( SlackUrlSourceElement ) ,
1463+ }
1464+
1465+ impl From < SlackUrlSourceElement > for SlackTaskCardSource {
1466+ fn from ( element : SlackUrlSourceElement ) -> Self {
1467+ SlackTaskCardSource :: Url ( element)
1468+ }
1469+ }
1470+
13461471#[ cfg( test) ]
13471472mod test {
13481473 use super :: * ;
@@ -1538,4 +1663,93 @@ mod test {
15381663 assert_eq ! ( block, block2) ;
15391664 Ok ( ( ) )
15401665 }
1666+
1667+ #[ test]
1668+ fn test_slack_table_block_deserialize ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1669+ let payload = include_str ! ( "./fixtures/slack_table_block.json" ) ;
1670+ let block: SlackBlock = serde_json:: from_str ( payload) ?;
1671+
1672+ let table = match block {
1673+ SlackBlock :: Table ( t) => t,
1674+ _ => panic ! ( "Expected a Table block" ) ,
1675+ } ;
1676+
1677+ assert_eq ! ( table. block_id, Some ( SlackBlockId ( "table_block_1" . into( ) ) ) ) ;
1678+ assert_eq ! ( table. rows. len( ) , 2 ) ;
1679+ assert_eq ! ( table. rows[ 0 ] . len( ) , 2 ) ;
1680+
1681+ // first row, first cell is raw_text
1682+ match & table. rows [ 0 ] [ 0 ] {
1683+ SlackTableCell :: RawText ( c) => assert_eq ! ( c. text, "Header A" ) ,
1684+ _ => panic ! ( "Expected RawText cell" ) ,
1685+ }
1686+
1687+ // second row, second cell is rich_text
1688+ match & table. rows [ 1 ] [ 1 ] {
1689+ SlackTableCell :: RichText ( c) => assert_eq ! ( c. elements. len( ) , 1 ) ,
1690+ _ => panic ! ( "Expected RichText cell" ) ,
1691+ }
1692+
1693+ let settings = table
1694+ . column_settings
1695+ . expect ( "column_settings should be present" ) ;
1696+ assert_eq ! ( settings. len( ) , 2 ) ;
1697+ assert_eq ! ( settings[ 0 ] . is_wrapped, Some ( true ) ) ;
1698+ assert_eq ! ( settings[ 1 ] . align, Some ( SlackTableColumnAlign :: Right ) ) ;
1699+
1700+ Ok ( ( ) )
1701+ }
1702+
1703+ #[ test]
1704+ fn test_slack_table_block_roundtrip ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1705+ let payload = include_str ! ( "./fixtures/slack_table_block.json" ) ;
1706+ let block: SlackBlock = serde_json:: from_str ( payload) ?;
1707+ let serialized = serde_json:: to_string ( & block) ?;
1708+ let block2: SlackBlock = serde_json:: from_str ( & serialized) ?;
1709+ assert_eq ! ( block, block2) ;
1710+ Ok ( ( ) )
1711+ }
1712+
1713+ #[ test]
1714+ fn test_slack_task_card_block_deserialize ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1715+ let payload = include_str ! ( "./fixtures/slack_task_card_block.json" ) ;
1716+ let block: SlackBlock = serde_json:: from_str ( payload) ?;
1717+
1718+ let task_card = match block {
1719+ SlackBlock :: TaskCard ( t) => t,
1720+ _ => panic ! ( "Expected a TaskCard block" ) ,
1721+ } ;
1722+
1723+ assert_eq ! ( task_card. task_id, SlackTaskId ( "task_1" . into( ) ) ) ;
1724+ assert_eq ! ( task_card. title, "Fetching weather data" ) ;
1725+ assert_eq ! (
1726+ task_card. block_id,
1727+ Some ( SlackBlockId ( "task_card_block_1" . into( ) ) )
1728+ ) ;
1729+ assert_eq ! ( task_card. status, Some ( SlackTaskCardStatus :: InProgress ) ) ;
1730+
1731+ let output = task_card. output . expect ( "output should be present" ) ;
1732+ let output_block = match output {
1733+ SlackRichTextInlineContent :: RichText ( b) => b,
1734+ } ;
1735+ assert_eq ! ( output_block. elements. len( ) , 1 ) ;
1736+
1737+ let sources = task_card. sources . expect ( "sources should be present" ) ;
1738+ assert_eq ! ( sources. len( ) , 2 ) ;
1739+ match & sources[ 0 ] {
1740+ SlackTaskCardSource :: Url ( u) => assert_eq ! ( u. text, "weather.com" ) ,
1741+ }
1742+
1743+ Ok ( ( ) )
1744+ }
1745+
1746+ #[ test]
1747+ fn test_slack_task_card_block_roundtrip ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1748+ let payload = include_str ! ( "./fixtures/slack_task_card_block.json" ) ;
1749+ let block: SlackBlock = serde_json:: from_str ( payload) ?;
1750+ let serialized = serde_json:: to_string ( & block) ?;
1751+ let block2: SlackBlock = serde_json:: from_str ( & serialized) ?;
1752+ assert_eq ! ( block, block2) ;
1753+ Ok ( ( ) )
1754+ }
15411755}
0 commit comments