1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::convert::{TryFrom, TryInto};
use validator::Validate;
use serde::{Serialize, Deserialize};

use crate::val_helpr::ValidationResult;
use crate::block_elements;

#[derive(Clone, Debug, Default, Deserialize, Hash, PartialEq, Serialize, Validate)]
pub struct Contents {
    /// An array of interactive [element objects 🔗]
    /// - [buttons 🔗]
    /// - [select menus 🔗]
    /// - [overflow menus 🔗]
    /// - [date pickers 🔗]
    ///
    /// There is a maximum of 5 elements in each action block.
    ///
    /// [element objects 🔗]: https://api.slack.com/reference/messaging/block-elements
    /// [buttons 🔗]: https://api.slack.com/reference/messaging/block-elements#button
    /// [select menus 🔗]: https://api.slack.com/reference/messaging/block-elements#select
    /// [overflow menus 🔗]: https://api.slack.com/reference/messaging/block-elements#overflow
    /// [date pickers 🔗]: https://api.slack.com/reference/messaging/block-elements#datepicker
    #[validate(length(max = 5))]
    elements: Vec<BlockElement>,

    /// A string acting as a unique identifier for a block.
    ///
    /// You can use this `block_id` when you receive an
    /// interaction payload to [identify the source of the action 🔗].
    ///
    /// If not specified, a `block_id` will be generated.
    ///
    /// Maximum length for this field is 255 characters.
    ///
    /// [identify the source of the action 🔗]: https://api.slack.com/interactivity/handling#payloads
    block_id: Option<String>,
}

impl Contents {
    /// Create a empty Actions block
    pub fn new() -> Self {
        Default::default()
    }

    /// Populate an Actions block with a collection of `BlockElement`s that
    /// may not be supported by `Actions` blocks.
    ///
    /// For an infallible version of this conversion function, see `from_action_elements`.
    ///
    /// ### Errors
    /// Errors if the `block_elements::BlockElement` is one that is not supported by
    /// `Actions` blocks.
    ///
    /// For a list of `BlockElement` types that are, see `self::BlockElement`.
    ///
    /// ### Runtime Validation
    /// **only** validates that the block elements are compatible with `Actions`,
    /// for full runtime model validation see the `validate` method.
    pub fn from_elements<Els: Into<Vec<block_elements::BlockElement>>>(elements: Els) -> Result<Self, ()> {
        elements // Into<Vec>
            .into() // Vec
            .try_into() // Result<Vec>
    }

    /// Populate an Actions block with a collection of `BlockElement`s that
    /// are supported by `Actions` blocks.
    ///
    /// For slightly easier to use (but fallible) version of this conversion function,
    /// see `from_action_elements`.
    ///
    /// ### Runtime Validation
    /// **only** validates that the block elements are compatible with `Actions`,
    /// for full runtime model validation see the `validate` method.
    pub fn from_action_elements<Els: Into<Vec<self::BlockElement>>>(elements: Els) -> Self {
        elements // Into<Vec>
            .into() // Vec
            .into() // Contents
    }

    /// Validate the entire block and all of its
    /// elements against Slack's model requirements
    pub fn validate(&self) -> ValidationResult {
        Validate::validate(self)
    }
}

// TryFrom implementation backing `Contents::from_elements`
impl TryFrom<Vec<block_elements::BlockElement>> for Contents {
    type Error = ();
    fn try_from(elements: Vec<block_elements::BlockElement>) -> Result<Self, Self::Error> {
        elements.into_iter()
            // Try to convert the bag of "any block element" to "block element supported by Actions"
            .map(TryInto::try_into)
            // If we hit one that is not supported, stop and continue with err
            .collect::<Result<_, _>>()
            // If it went ok, convert the supported elements into Contents
            .map(|els: Vec<self::BlockElement>| -> Self { els.into() })
    }
}

// From implementation backing `Contents::from_action_elements`
impl From<Vec<self::BlockElement>> for Contents {
    fn from(elements: Vec<self::BlockElement>) -> Self {
        Self {
            elements,
            ..Default::default()
        }
    }
}

#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
pub enum BlockElement {
    Button,
    Checkboxes,
    DatePicker,
    OverflowMenu,
    PlainInput,
    RadioButtons,
    Select(block_elements::select::Contents),
}

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

        match el {
            El::Button => Ok(Button),
            El::Checkboxes => Ok(Checkboxes),
            El::DatePicker => Ok(DatePicker),
            El::OverflowMenu => Ok(OverflowMenu),
            El::PlainInput => Ok(PlainInput),
            El::RadioButtons => Ok(RadioButtons),
            El::Select(contents) => Ok(Select(contents)),
            _ => Err(()),
        }
    }
}