slack_messaging/composition_objects/option_group.rs
1use crate::composition_objects::{Opt, Plain, Text, TextExt};
2use crate::validators::*;
3
4use serde::Serialize;
5use slack_messaging_derive::Builder;
6
7/// [Option group object](https://docs.slack.dev/reference/block-kit/composition-objects/option-group-object)
8/// representation.
9///
10/// This is a generic struct that can represent an option group object with different text object
11/// types.
12///
13/// # Type Parameters
14///
15/// * `T`: The type of text object used for the `text` field of the [`Opt`] objects in the
16/// `options` field. Defaults to `Text<Plain>`. Must implement the [`TextExt`] trait.
17///
18/// # Fields and Validations
19///
20/// For more details, see the [official
21/// documentation](https://docs.slack.dev/reference/block-kit/composition-objects/option-group-object).
22///
23/// | Field | Type | Required | Validation |
24/// |-------|------|----------|------------|
25/// | label | [Text]<[Plain]> | Yes | Max length 75 characters |
26/// | options | Vec<[Opt]<`T`>> | Yes | Must contain at most 100 items |
27///
28/// # Example
29///
30/// ```
31/// use slack_messaging::plain_text;
32/// use slack_messaging::composition_objects::{OptGroup, Opt, Plain, Text};
33/// # use std::error::Error;
34///
35/// # fn try_main() -> Result<(), Box<dyn Error>> {
36/// let options: OptGroup = OptGroup::builder()
37/// .label(plain_text!("Group One")?)
38/// .option(
39/// Opt::builder()
40/// .text(plain_text!("option-0")?)
41/// .value("value-0")
42/// .build()?
43/// )
44/// .option(
45/// Opt::builder()
46/// .text(plain_text!("option-1")?)
47/// .value("value-1")
48/// .build()?
49/// )
50/// .build()?;
51///
52/// let expected = serde_json::json!({
53/// "label": {
54/// "type": "plain_text",
55/// "text": "Group One"
56/// },
57/// "options": [
58/// {
59/// "text": {
60/// "type": "plain_text",
61/// "text": "option-0",
62/// },
63/// "value": "value-0"
64/// },
65/// {
66/// "text": {
67/// "type": "plain_text",
68/// "text": "option-1"
69/// },
70/// "value": "value-1"
71/// },
72/// ]
73/// });
74///
75/// let json = serde_json::to_value(options).unwrap();
76///
77/// assert_eq!(json, expected);
78///
79/// // If your object has any validation errors, the build method returns Result::Err
80/// let options = OptGroup::<Text<Plain>>::builder()
81/// .label(plain_text!("Group One")?)
82/// .build();
83///
84/// assert!(options.is_err());
85/// # Ok(())
86/// # }
87/// # fn main() {
88/// # try_main().unwrap()
89/// # }
90/// ```
91#[derive(Debug, Clone, Serialize, PartialEq, Builder)]
92#[serde(bound(serialize = "T: Serialize"))]
93pub struct OptGroup<T = Text<Plain>>
94where
95 T: TextExt,
96{
97 #[builder(validate("required", "text_object::max_75"))]
98 pub(crate) label: Option<Text<Plain>>,
99
100 #[builder(push_item = "option", validate("required", "list::max_item_100"))]
101 pub(crate) options: Option<Vec<Opt<T>>>,
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::composition_objects::test_helpers::*;
108 use crate::errors::*;
109
110 #[test]
111 fn it_implements_builder() {
112 let expected = OptGroup {
113 label: Some(plain_text("foo")),
114 options: Some(vec![
115 option("text_0", "value_0"),
116 option("text_1", "value_1"),
117 ]),
118 };
119
120 let val = OptGroup::builder()
121 .set_label(Some(plain_text("foo")))
122 .set_options(Some(vec![
123 option("text_0", "value_0"),
124 option("text_1", "value_1"),
125 ]))
126 .build()
127 .unwrap();
128
129 assert_eq!(val, expected);
130
131 let val = OptGroup::builder()
132 .label(plain_text("foo"))
133 .options(vec![
134 option("text_0", "value_0"),
135 option("text_1", "value_1"),
136 ])
137 .build()
138 .unwrap();
139
140 assert_eq!(val, expected);
141 }
142
143 #[test]
144 fn it_implements_push_item_method() {
145 let expected = OptGroup {
146 label: Some(plain_text("foo")),
147 options: Some(vec![
148 option("text_0", "value_0"),
149 option("text_1", "value_1"),
150 ]),
151 };
152
153 let val = OptGroup::builder()
154 .label(plain_text("foo"))
155 .option(option("text_0", "value_0"))
156 .option(option("text_1", "value_1"))
157 .build()
158 .unwrap();
159
160 assert_eq!(val, expected);
161 }
162
163 #[test]
164 fn it_requires_label_field() {
165 let err = OptGroup::builder()
166 .options(vec![
167 option("text_0", "value_0"),
168 option("text_1", "value_1"),
169 ])
170 .build()
171 .unwrap_err();
172 assert_eq!(err.object(), "OptGroup");
173
174 let errors = err.field("label");
175 assert!(errors.includes(ValidationErrorKind::Required));
176 }
177
178 #[test]
179 fn it_requires_label_less_than_75_characters_long() {
180 let err = OptGroup::builder()
181 .label(plain_text("a".repeat(76)))
182 .options(vec![
183 option("text_0", "value_0"),
184 option("text_1", "value_1"),
185 ])
186 .build()
187 .unwrap_err();
188 assert_eq!(err.object(), "OptGroup");
189
190 let errors = err.field("label");
191 assert!(errors.includes(ValidationErrorKind::MaxTextLength(75)));
192 }
193
194 #[test]
195 fn it_requires_options_field() {
196 let err = OptGroup::<Text<Plain>>::builder()
197 .label(plain_text("foo"))
198 .build()
199 .unwrap_err();
200 assert_eq!(err.object(), "OptGroup");
201
202 let errors = err.field("options");
203 assert!(errors.includes(ValidationErrorKind::Required));
204 }
205
206 #[test]
207 fn it_requires_options_size_less_than_100() {
208 let options: Vec<Opt> = (0..101).map(|_| option("opt", "val")).collect();
209 let err = OptGroup::builder()
210 .label(plain_text("foo"))
211 .options(options)
212 .build()
213 .unwrap_err();
214 assert_eq!(err.object(), "OptGroup");
215
216 let errors = err.field("options");
217 assert!(errors.includes(ValidationErrorKind::MaxArraySize(100)));
218 }
219}