Skip to content

Commit

Permalink
feat!: standardize supported block elements (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
cakekindel authored May 24, 2021
1 parent aefb137 commit bdbf7c7
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 131 deletions.
125 changes: 55 additions & 70 deletions src/blocks/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use serde::{Deserialize, Serialize};
use validator::Validate;

use crate::{convert,
elems,
elems::{select,
BlockElement,
Button,
Checkboxes,
DatePicker,
Expand All @@ -41,7 +41,7 @@ use crate::{convert,
Validate)]
pub struct Contents<'a> {
#[validate(length(max = 5))]
elements: Vec<BlockElement<'a>>,
elements: Vec<SupportedElement<'a>>,

#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(max = 255))]
Expand Down Expand Up @@ -87,11 +87,11 @@ impl<'a> Contents<'a> {
self
}

/// Populate an Actions block with a collection of `elems::BlockElement`s,
/// Populate an Actions block with a collection of `BlockElement`s,
/// which may not be supported by `Actions` blocks.
///
/// If you _can_ create a collection of `actions::BlockElement`,
/// either by creating them directly or invoking `elems::BlockElement::into`,
/// either by creating them directly or invoking `BlockElement::into`,
/// use `from_action_elements`.
///
/// # Arguments
Expand All @@ -102,7 +102,7 @@ impl<'a> Contents<'a> {
/// [element objects 🔗]: https://api.slack.com/reference/messaging/block-elements
///
/// # Errors
/// Errors if the `elems::BlockElement` is one that is not supported by
/// Errors if the `BlockElement` is one that is not supported by
/// `Actions` blocks.
///
/// For a list of `BlockElement` types that are supported, see `::blocks::actions::BlockElement`.
Expand All @@ -127,15 +127,15 @@ impl<'a> Contents<'a> {
/// # }
/// ```
pub fn from_elements<Iter>(elements: Iter) -> Result<Self, ()>
where Iter: IntoIterator<Item = elems::BlockElement<'a>>
where Iter: IntoIterator<Item = BlockElement<'a>>
{
elements.into_iter().collect::<Vec<_>>().try_into()
}

/// Populate an Actions block with a collection of `BlockElement`s that
/// are supported by `Actions` blocks.
///
/// This also can be called via the `From<Vec<self::BlockElement>>` implementation.
/// This also can be called via the `From<Vec<self::SupportedElement>>` implementation.
///
/// If you have a collection of elements that may not be supported,
/// see `from_elements`.
Expand All @@ -152,7 +152,7 @@ impl<'a> Contents<'a> {
/// [Iterator and Option implement IntoIterator 🔗]: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html#impl-IntoIterator-28
///
/// # Errors
/// Errors if the `elems::BlockElement` is one that is not supported by
/// Errors if the `BlockElement` is one that is not supported by
/// `Actions` blocks.
///
/// # Runtime Validation
Expand All @@ -174,7 +174,7 @@ impl<'a> Contents<'a> {
/// # }
/// ```
pub fn from_action_elements<Iter>(elements: Iter) -> Self
where Iter: IntoIterator<Item = self::BlockElement<'a>>
where Iter: IntoIterator<Item = self::SupportedElement<'a>>
{
elements.into_iter().collect::<Vec<_>>().into()
}
Expand Down Expand Up @@ -207,92 +207,77 @@ impl<'a> Contents<'a> {

/// The Block Elements supported in an Action Block.
///
/// This list was pulled from the docs for all [block elements 🔗],
/// where each declares the blocks it is usable in.
///
/// [block elements 🔗]: https://api.slack.com/reference/block-kit/block-elements
/// Supports:
/// - Overflow
/// - RadioButtons
/// - Button
/// - TextInput
/// - Checkboxes
/// - DatePicker
/// - Select Menus:
/// - PublicChannel
/// - Conversation
/// - External
/// - Static
/// - User
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[allow(missing_docs)]
pub enum BlockElement<'a> {
Button(Button<'a>),
Checkboxes(Checkboxes<'a>),
DatePicker(DatePicker<'a>),
Overflow(Overflow<'a>),
TextInput(TextInput<'a>),
RadioButtons(Radio<'a>),

/// All Select types are supported.
SelectPublicChannel(select::PublicChannel<'a>),

/// All Select types are supported.
SelectConversation(select::Conversation<'a>),

/// All Select types are supported.
SelectUser(select::User<'a>),
pub struct SupportedElement<'a>(BlockElement<'a>);

/// All Select types are supported.
SelectExternal(select::External<'a>),

/// All Select types are supported.
SelectStatic(select::Static<'a>),
}

convert!(impl<'a> From<Vec<self::BlockElement<'a>>> for Contents<'a>
convert!(impl<'a> From<Vec<self::SupportedElement<'a>>> for Contents<'a>
=> |elements| Self {
elements,
..Default::default()
}
);

impl<'a> TryFrom<elems::BlockElement<'a>> for Contents<'a> {
impl<'a> TryFrom<BlockElement<'a>> for Contents<'a> {
type Error = ();
fn try_from(element: elems::BlockElement<'a>) -> Result<Self, Self::Error> {
self::BlockElement::<'a>::try_from(element)
fn try_from(element: BlockElement<'a>) -> Result<Self, Self::Error> {
self::SupportedElement::<'a>::try_from(element)
.map(|el| Self::from_action_elements(std::iter::once(el)))
}
}

impl<'a> TryFrom<Vec<elems::BlockElement<'a>>> for Contents<'a> {
impl<'a> TryFrom<Vec<BlockElement<'a>>> for Contents<'a> {
type Error = ();
fn try_from(elements: Vec<elems::BlockElement<'a>>)
-> Result<Self, Self::Error> {
fn try_from(elements: Vec<BlockElement<'a>>) -> Result<Self, Self::Error> {
elements.into_iter()
.map(self::BlockElement::<'a>::try_from)
.map(self::SupportedElement::<'a>::try_from)
.collect::<Result<Vec<_>, _>>()
.map(self::Contents::<'a>::from)
}
}

impl<'a> TryFrom<elems::BlockElement<'a>> for self::BlockElement<'a> {
impl<'a> TryFrom<BlockElement<'a>> for self::SupportedElement<'a> {
type Error = ();
fn try_from(el: elems::BlockElement<'a>) -> Result<Self, Self::Error> {
use elems::BlockElement as El;

use self::BlockElement::*;
fn try_from(el: BlockElement<'a>) -> Result<Self, Self::Error> {
use BlockElement as El;

match el {
| El::SelectPublicChannel(sel) => Ok(SelectPublicChannel(sel)),
| El::SelectConversation(sel) => Ok(SelectConversation(sel)),
| El::SelectExternal(sel) => Ok(SelectExternal(sel)),
| El::SelectStatic(sel) => Ok(SelectStatic(sel)),
| El::SelectUser(sel) => Ok(SelectUser(sel)),
| El::Overflow(o) => Ok(Overflow(o)),
| El::RadioButtons(r) => Ok(RadioButtons(r)),
| El::Button(cts) => Ok(Button(cts)),
| El::TextInput(t) => Ok(TextInput(t)),
| El::Checkboxes(c) => Ok(Checkboxes(c)),
| El::DatePicker(d) => Ok(DatePicker(d)),
| El::SelectPublicChannel(_)
| El::SelectConversation(_)
| El::SelectExternal(_)
| El::SelectStatic(_)
| El::SelectUser(_)
| El::Overflow(_)
| El::RadioButtons(_)
| El::Button(_)
| El::TextInput(_)
| El::Checkboxes(_)
| El::DatePicker(_) => Ok(SupportedElement(el)),
| _ => Err(()),
}
}
}

convert!(impl<'a> From<select::PublicChannel<'a>> for self::BlockElement<'a> => |s| self::BlockElement::SelectPublicChannel(s));
convert!(impl<'a> From<select::Conversation<'a>> for self::BlockElement<'a> => |s| self::BlockElement::SelectConversation(s));
convert!(impl<'a> From<select::User<'a>> for self::BlockElement<'a> => |s| self::BlockElement::SelectUser(s));
convert!(impl<'a> From<select::External<'a>> for self::BlockElement<'a> => |s| self::BlockElement::SelectExternal(s));
convert!(impl<'a> From<select::Static<'a>> for self::BlockElement<'a> => |s| self::BlockElement::SelectStatic(s));
convert!(impl<'a> From<Button<'a>> for self::BlockElement<'a> => |b| self::BlockElement::Button(b));
convert!(impl<'a> From<Radio<'a>> for self::BlockElement<'a> => |b| self::BlockElement::RadioButtons(b));
convert!(impl<'a> From<TextInput<'a>> for self::BlockElement<'a> => |t| self::BlockElement::TextInput(t));
convert!(impl<'a> From<DatePicker<'a>> for self::BlockElement<'a> => |t| self::BlockElement::DatePicker(t));
convert!(impl<'a> From<select::PublicChannel<'a>> for self::SupportedElement<'a> => |s| self::SupportedElement(BlockElement::from(s)));
convert!(impl<'a> From<select::Conversation<'a>> for self::SupportedElement<'a> => |s| self::SupportedElement(BlockElement::from(s)));
convert!(impl<'a> From<select::User<'a>> for self::SupportedElement<'a> => |s| self::SupportedElement(BlockElement::from(s)));
convert!(impl<'a> From<select::External<'a>> for self::SupportedElement<'a> => |s| self::SupportedElement(BlockElement::from(s)));
convert!(impl<'a> From<select::Static<'a>> for self::SupportedElement<'a> => |s| self::SupportedElement(BlockElement::from(s)));
convert!(impl<'a> From<Button<'a>> for self::SupportedElement<'a> => |b| self::SupportedElement(BlockElement::from(b)));
convert!(impl<'a> From<Radio<'a>> for self::SupportedElement<'a> => |b| self::SupportedElement(BlockElement::from(b)));
convert!(impl<'a> From<TextInput<'a>> for self::SupportedElement<'a> => |t| self::SupportedElement(BlockElement::from(t)));
convert!(impl<'a> From<DatePicker<'a>> for self::SupportedElement<'a> => |t| self::SupportedElement(BlockElement::from(t)));
convert!(impl<'a> From<Checkboxes<'a>> for self::SupportedElement<'a> => |t| self::SupportedElement(BlockElement::from(t)));
convert!(impl<'a> From<Overflow<'a>> for self::SupportedElement<'a> => |t| self::SupportedElement(BlockElement::from(t)));
22 changes: 12 additions & 10 deletions src/blocks/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{convert, elems::Image, text, val_helpr::ValidationResult};
Validate)]
pub struct Contents<'a> {
#[validate(length(max = 10))]
elements: Vec<Compose<'a>>,
elements: Vec<ImageOrText<'a>>,

#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(max = 255))]
Expand Down Expand Up @@ -103,7 +103,9 @@ impl<'a> Contents<'a> {
/// let block: Block = context.into();
/// // < send block to slack's API >
/// ```
pub fn with_element(mut self, element: impl Into<self::Compose<'a>>) -> Self {
pub fn with_element(mut self,
element: impl Into<self::ImageOrText<'a>>)
-> Self {
self.elements.push(element.into());
self
}
Expand Down Expand Up @@ -133,7 +135,7 @@ impl<'a> Contents<'a> {
/// // < send block to slack's API >
/// }
/// ```
pub fn from_context_elements(elements: impl IntoIterator<Item = impl Into<Compose<'a>>>)
pub fn from_context_elements(elements: impl IntoIterator<Item = impl Into<ImageOrText<'a>>>)
-> Self {
elements.into_iter()
.map(|i| i.into())
Expand Down Expand Up @@ -164,8 +166,8 @@ impl<'a> Contents<'a> {
}
}

impl<'a> From<Vec<Compose<'a>>> for Contents<'a> {
fn from(elements: Vec<Compose<'a>>) -> Self {
impl<'a> From<Vec<ImageOrText<'a>>> for Contents<'a> {
fn from(elements: Vec<ImageOrText<'a>>) -> Self {
Self { elements,
..Default::default() }
}
Expand All @@ -174,12 +176,12 @@ impl<'a> From<Vec<Compose<'a>>> for Contents<'a> {
/// The Composition objects supported by this block
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[allow(missing_docs)]
pub enum Compose<'a> {
pub enum ImageOrText<'a> {
Text(text::Text),
Image(Image<'a>),
}

convert!(impl From<text::Text> for Compose<'static> => |txt| Compose::Text(txt));
convert!(impl<'a> From<Image<'a>> for Compose<'a> => |i| Compose::Image(i));
convert!(impl From<text::Plain> for Compose<'static> => |t| text::Text::from(t).into());
convert!(impl From<text::Mrkdwn> for Compose<'static> => |t| text::Text::from(t).into());
convert!(impl From<text::Text> for ImageOrText<'static> => |txt| ImageOrText::Text(txt));
convert!(impl<'a> From<Image<'a>> for ImageOrText<'a> => |i| ImageOrText::Image(i));
convert!(impl From<text::Plain> for ImageOrText<'static> => |t| text::Text::from(t).into());
convert!(impl From<text::Mrkdwn> for ImageOrText<'static> => |t| text::Text::from(t).into());
54 changes: 30 additions & 24 deletions src/blocks/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use validator::Validate;

use crate::{compose::text,
convert,
elems::{select, Radio, TextInput},
elems,
elems::{select, BlockElement},
val_helpr::ValidationResult};

/// # Input Block
Expand All @@ -34,7 +35,7 @@ pub struct Contents<'a> {
#[validate(custom = "validate::label")]
label: text::Text,

element: InputElement<'a>,
element: SupportedElement<'a>,

#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(max = 255))]
Expand Down Expand Up @@ -63,7 +64,7 @@ impl<'a> Contents<'a> {
/// - `element` - An interactive `block_element` that will be used to gather
/// the input for this block.
/// For the kinds of Elements supported by
/// Input blocks, see the `InputElement` enum.
/// Input blocks, see the `SupportedElement` enum.
/// For info about Block Elements in general,
/// see the `elems` module.
///
Expand All @@ -83,7 +84,7 @@ impl<'a> Contents<'a> {
/// // < send to slack API >
/// ```
pub fn from_label_and_element(label: impl Into<text::Plain>,
element: impl Into<InputElement<'a>>)
element: impl Into<SupportedElement<'a>>)
-> Self {
Contents { label: label.into().into(),
element: element.into(),
Expand Down Expand Up @@ -234,29 +235,34 @@ impl<'a> Contents<'a> {
}
}

/// Enum representing the [`BlockElement` 🔗] types
/// supported by InputElement.
/// The Block Elements supported in an Input Block.
///
/// Supports:
/// - Radio Buttons
/// - Text Input
/// - Checkboxes
/// - Date Picker
/// - All Select Menus
/// - All Multi-Select Menus
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[allow(missing_docs)]
pub enum InputElement<'a> {
#[serde(rename = "channels_select")]
SelectPublicChannel(select::PublicChannel<'a>),
Checkboxes,
DatePicker,
MultiSelect,
TextInput(TextInput<'a>),
Radio(Radio<'a>),
}
pub struct SupportedElement<'a>(BlockElement<'a>);

use select::PublicChannel as SelectPublicChannel;
convert! {
impl<'_> From<SelectPublicChannel> for InputElement
=> |contents| InputElement::SelectPublicChannel(contents)
}
convert!(impl<'a> From<elems::Radio<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<elems::TextInput<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<elems::Checkboxes<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<elems::DatePicker<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));

convert!(impl<'a> From<select::Static<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::External<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::User<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::Conversation<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::PublicChannel<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));

convert!(impl<'a> From<Radio<'a>> for InputElement<'a> => |r| InputElement::Radio(r));
convert!(impl<'a> From<TextInput<'a>> for InputElement<'a> => |r| InputElement::TextInput(r));
convert!(impl<'a> From<select::multi::Static<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::multi::External<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::multi::User<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::multi::Conversation<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));
convert!(impl<'a> From<select::multi::PublicChannel<'a>> for SupportedElement<'a> => |r| SupportedElement(BlockElement::from(r)));

mod validate {
use crate::{compose::text,
Expand Down
4 changes: 2 additions & 2 deletions src/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type ValidationResult = Result<(), validator::ValidationErrors>;
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Block<'a> {
/// # Section Block
Section(Section),
Section(Section<'a>),

/// # Divider Block
///
Expand Down Expand Up @@ -136,7 +136,7 @@ impl<'a> Block<'a> {

convert!(impl<'a> From<Actions<'a>> for Block<'a> => |a| Block::Actions(a));
convert!(impl<'a> From<Input<'a>> for Block<'a> => |a| Block::Input(a));
convert!(impl From<Section> for Block<'static> => |a| Block::Section(a));
convert!(impl<'a> From<Section<'a>> for Block<'a> => |a| Block::Section(a));
convert!(impl From<Image> for Block<'static> => |a| Block::Image(a));
convert!(impl<'a> From<Context<'a>> for Block<'a> => |a| Block::Context(a));
convert!(impl From<File> for Block<'static> => |a| Block::File(a));
Loading

0 comments on commit bdbf7c7

Please sign in to comment.