use crate::blocks::{elements, Actions, Context, Divider, File, Header, Image, Input, RichText, Section, Table, Video};
use crate::composition_objects::{Plain, Text, TextContent};
use crate::validators::*;
use serde::Serialize;
use slack_messaging_derive::Builder;
#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
#[serde(tag = "type", rename = "container")]
pub struct Container {
#[builder(validate("required", "text_object::max_150"))]
pub(crate) title: Option<Text<Plain>>,
#[builder(push_item = "child_block", validate("required", "list::max_item_10"))]
pub(crate) child_blocks: Option<Vec<ContainerChildBlock>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(validate("text::max_255"))]
pub(crate) block_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) width: Option<ContainerWidth>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(validate("text_object::max_150"))]
pub(crate) subtitle: Option<TextContent>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(validate("image::alt_text_max_2000"))]
pub(crate) icon: Option<elements::Image>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) is_collapsible: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) default_collapsed: Option<bool>,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ContainerWidth {
Narrow,
Standard,
Wide,
Full,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(untagged)]
pub enum ContainerChildBlock {
Actions(Actions),
Context(Context),
Divider(Divider),
File(File),
Header(Header),
Image(Image),
Input(Input),
RichText(RichText),
Section(Section),
Table(Table),
Video(Video),
}
macro_rules! impl_from_for_container_child_block {
($($variant:ident),*) => {
$(
impl From<$variant> for ContainerChildBlock {
fn from(block: $variant) -> Self {
ContainerChildBlock::$variant(block)
}
}
)*
};
}
impl_from_for_container_child_block! {
Actions,
Context,
Divider,
File,
Header,
Image,
Input,
RichText,
Section,
Table,
Video
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blocks::test_helpers::*;
use crate::composition_objects::test_helpers::*;
use crate::errors::*;
#[test]
fn it_implements_builder() {
let expected = Container {
title: Some(plain_text("Container Title")),
child_blocks: Some(vec![section("Section Text").into()]),
block_id: Some("container_block".into()),
width: Some(ContainerWidth::Standard),
subtitle: Some(mrkdwn_text("Container Subtitle").into()),
icon: Some(image("Icon Alt Text")),
is_collapsible: Some(true),
default_collapsed: Some(false),
};
let val = Container::builder()
.set_title(Some(plain_text("Container Title")))
.set_child_blocks(Some(vec![section("Section Text")]))
.set_block_id(Some("container_block"))
.set_width(Some(ContainerWidth::Standard))
.set_subtitle(Some(mrkdwn_text("Container Subtitle")))
.set_icon(Some(image("Icon Alt Text")))
.set_is_collapsible(Some(true))
.set_default_collapsed(Some(false))
.build()
.unwrap();
assert_eq!(val, expected);
let val = Container::builder()
.title(plain_text("Container Title"))
.child_blocks(vec![section("Section Text")])
.block_id("container_block")
.width(ContainerWidth::Standard)
.subtitle(mrkdwn_text("Container Subtitle"))
.icon(image("Icon Alt Text"))
.is_collapsible(true)
.default_collapsed(false)
.build()
.unwrap();
assert_eq!(val, expected);
}
#[test]
fn it_implements_push_item_method() {
let expected = Container {
title: Some(plain_text("Container Title")),
child_blocks: Some(vec![
header("Header Text").into(),
section("Section Text").into(),
]),
block_id: None,
width: None,
subtitle: None,
icon: None,
is_collapsible: None,
default_collapsed: None,
};
let val = Container::builder()
.title(plain_text("Container Title"))
.child_block(header("Header Text"))
.child_block(section("Section Text"))
.build()
.unwrap();
assert_eq!(val, expected);
}
#[test]
fn it_requires_title_field() {
let err = Container::builder()
.child_block(section("Section Text"))
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("title");
assert!(errors.includes(ValidationErrorKind::Required));
}
#[test]
fn it_requires_title_text_less_than_150_characters() {
let long_title = "a".repeat(151);
let err = Container::builder()
.title(plain_text(&long_title))
.child_block(section("Section Text"))
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("title");
assert!(errors.includes(ValidationErrorKind::MaxTextLength(150)));
}
#[test]
fn it_requires_child_blocks_field() {
let err = Container::builder()
.title(plain_text("Container Title"))
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("child_blocks");
assert!(errors.includes(ValidationErrorKind::Required));
}
#[test]
fn it_requires_child_blocks_list_size_less_than_10() {
let child_blocks: Vec<ContainerChildBlock> = (0..11)
.map(|_| section("Section Text").into())
.collect();
let err = Container::builder()
.title(plain_text("Container Title"))
.child_blocks(child_blocks)
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("child_blocks");
assert!(errors.includes(ValidationErrorKind::MaxArraySize(10)));
}
#[test]
fn it_requires_block_id_less_than_255_characters_long() {
let err = Container::builder()
.title(plain_text("Container Title"))
.child_block(section("Section Text"))
.block_id("a".repeat(256))
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("block_id");
assert!(errors.includes(ValidationErrorKind::MaxTextLength(255)));
}
#[test]
fn it_requires_subtitle_text_less_than_150_characters() {
let long_subtitle = "a".repeat(151);
let err = Container::builder()
.title(plain_text("Container Title"))
.child_block(section("Section Text"))
.subtitle(mrkdwn_text(&long_subtitle))
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("subtitle");
assert!(errors.includes(ValidationErrorKind::MaxTextLength(150)));
}
#[test]
fn it_requires_icon_alt_text_less_than_2000_characters() {
let long_alt_text = "a".repeat(2001);
let err = Container::builder()
.title(plain_text("Container Title"))
.child_block(section("Section Text"))
.icon(image(&long_alt_text))
.build()
.unwrap_err();
assert_eq!(err.object(), "Container");
let errors = err.field("icon");
assert!(errors.includes(ValidationErrorKind::MaxAltTextLength(2000)));
}
fn image(alt_text: &str) -> elements::Image {
elements::Image {
alt_text: Some(alt_text.into()),
image_url: Some("http://placekitten.com/700/500".into()),
slack_file: None,
}
}
}