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
use serde::{Deserialize, Serialize};
use validator::Validate;

use crate::compose::Compose;
use crate::val_helpr::ValidationResult;

/// # Context Block
///
/// _[slack api docs 🔗][context_docs]_
///
/// Displays message context, which can include both images and text.
///
/// [context_docs]: https://api.slack.com/reference/block-kit/blocks#context
#[derive(Clone, Debug, Default, Deserialize, Hash, PartialEq, Serialize, Validate)]
pub struct Contents {
    /// A collection of [image elements 🔗] and [text objects 🔗].
    ///
    /// Maximum number of items is 10
    /// [image elements 🔗]: https://api.slack.com/reference/messaging/block-elements#image
    /// [text objects 🔗]: https://api.slack.com/reference/messaging/composition-objects#text
    #[validate(length(max = 10))]
    elements: Vec<Compose>,

    /// 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
    #[validate(length(max = 255))]
    block_id: Option<String>,
}

impl Contents {
    /// Construct a new empty Context block
    /// (uses `Default`)
    pub fn new() -> Self {
        Default::default()
    }

    /// Set the `block_id` for interactions on an existing `context::Contents`
    ///
    /// ```
    /// use slack_blocks::blocks::{Block, context};
    ///
    /// pub fn main() {
    ///     let context = context::Contents::new().with_block_id("tally_ho");
    ///     let block: Block = context.into();
    ///     // < send block to slack's API >
    /// }
    /// ```
    pub fn with_block_id<StrIsh: AsRef<str>>(mut self, block_id: StrIsh) -> Self {
        self.block_id = Some(block_id.as_ref().to_string());
        self
    }

    /// Construct a new `context::Contents` from a bunch of
    /// composition objects
    ///
    /// ```
    /// use slack_blocks::blocks::{Block, context};
    /// use slack_blocks::compose;
    ///
    /// pub fn main() {
    ///     let text = compose::Text::markdown("*s i c k*");
    ///     let context = context::Contents::from_elements(vec![text]);
    ///     let block: Block = context.into();
    ///     // < send block to slack's API >
    /// }
    /// ```
    pub fn from_elements<Els: IntoIterator<Item = impl Into<Compose>>>(elements: Els) -> Self {
        elements
            .into_iter()
            .map(Into::<Compose>::into)
            .collect::<Vec<_>>()
            .into()
    }

    /// Validate that the model agrees with slack's validation
    /// requirements
    pub fn validate(&self) -> ValidationResult {
        Validate::validate(self)
    }
}

// From impl backing the `from_elements` constructor
impl From<Vec<Compose>> for Contents {
    fn from(elements: Vec<Compose>) -> Self {
        Self {
            elements,
            ..Default::default()
        }
    }
}