slack_messaging/composition_objects/
text.rs1use crate::validators::*;
2
3use serde::{Serialize, Serializer};
4use slack_messaging_derive::Builder;
5
6#[derive(Debug, Clone, Builder)]
81pub struct Text<T> {
82 #[builder(phantom = "T")]
83 pub(crate) r#type: std::marker::PhantomData<T>,
84
85 #[builder(validate("required", "text::min_1", "text::max_3000"))]
86 pub(crate) text: Option<String>,
87
88 #[builder(no_accessors)]
89 pub(crate) emoji: Option<bool>, #[builder(no_accessors)]
92 pub(crate) verbatim: Option<bool>, }
94
95pub trait TextExt {
97 fn text(&self) -> Option<&str>;
98}
99
100impl<T> TextExt for Text<T> {
101 fn text(&self) -> Option<&str> {
103 self.text.as_deref()
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq)]
109pub struct Plain;
110
111#[derive(Debug, Clone, Copy, PartialEq)]
113pub struct Mrkdwn;
114
115impl TextBuilder<Plain> {
116 pub fn get_emoji(&self) -> Option<bool> {
118 self.emoji.inner_ref().copied()
119 }
120
121 pub fn set_emoji(self, emoji: Option<impl Into<bool>>) -> TextBuilder<Plain> {
123 Self {
124 emoji: Self::new_emoji(emoji.map(|v| v.into())),
125 ..self
126 }
127 }
128
129 pub fn emoji(self, emoji: impl Into<bool>) -> TextBuilder<Plain> {
131 self.set_emoji(Some(emoji))
132 }
133}
134
135impl TextBuilder<Mrkdwn> {
136 pub fn get_verbatim(&self) -> Option<bool> {
138 self.verbatim.inner_ref().copied()
139 }
140
141 pub fn set_verbatim(self, verbatim: Option<impl Into<bool>>) -> TextBuilder<Mrkdwn> {
143 Self {
144 verbatim: Self::new_verbatim(verbatim.map(|v| v.into())),
145 ..self
146 }
147 }
148
149 pub fn verbatim(self, verbatim: impl Into<bool>) -> TextBuilder<Mrkdwn> {
151 self.set_verbatim(Some(verbatim))
152 }
153}
154
155impl PartialEq for Text<Plain> {
156 fn eq(&self, other: &Self) -> bool {
157 match (self.text(), other.text()) {
158 (Some(text1), Some(text2)) if text1 != text2 => false,
159 (None, Some(_)) | (Some(_), None) => false,
160 _ => self.emoji.unwrap_or(false) == other.emoji.unwrap_or(false),
161 }
162 }
163}
164
165impl PartialEq for Text<Mrkdwn> {
166 fn eq(&self, other: &Self) -> bool {
167 match (self.text(), other.text()) {
168 (Some(text1), Some(text2)) if text1 != text2 => false,
169 (None, Some(_)) | (Some(_), None) => false,
170 _ => self.verbatim.unwrap_or(false) == other.verbatim.unwrap_or(false),
171 }
172 }
173}
174
175impl Serialize for Text<Plain> {
176 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: Serializer,
179 {
180 use serde::ser::SerializeStruct;
181
182 let mut state = serializer.serialize_struct("Text", 3)?;
183
184 state.serialize_field("type", "plain_text")?;
185
186 if let Some(text) = &self.text {
187 state.serialize_field("text", text)?;
188 }
189
190 if let Some(emoji) = &self.emoji {
191 state.serialize_field("emoji", emoji)?;
192 }
193
194 state.end()
195 }
196}
197
198impl Serialize for Text<Mrkdwn> {
199 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
200 where
201 S: Serializer,
202 {
203 use serde::ser::SerializeStruct;
204
205 let mut state = serializer.serialize_struct("Text", 3)?;
206
207 state.serialize_field("type", "mrkdwn")?;
208
209 if let Some(text) = &self.text {
210 state.serialize_field("text", text)?;
211 }
212
213 if let Some(verbatim) = &self.verbatim {
214 state.serialize_field("verbatim", verbatim)?;
215 }
216
217 state.end()
218 }
219}
220
221#[derive(Debug, Clone, Serialize, PartialEq)]
224#[serde(untagged)]
225pub enum TextContent {
226 Plain(Text<Plain>),
228 Mrkdwn(Text<Mrkdwn>),
230}
231
232impl TextExt for TextContent {
233 fn text(&self) -> Option<&str> {
235 match self {
236 TextContent::Plain(t) => t.text(),
237 TextContent::Mrkdwn(t) => t.text(),
238 }
239 }
240}
241
242impl From<Text<Plain>> for TextContent {
243 fn from(text: Text<Plain>) -> Self {
244 TextContent::Plain(text)
245 }
246}
247
248impl From<Text<Mrkdwn>> for TextContent {
249 fn from(text: Text<Mrkdwn>) -> Self {
250 TextContent::Mrkdwn(text)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use crate::errors::*;
258
259 #[test]
260 fn it_requires_text() {
261 let err = Text::<Plain>::builder().build().unwrap_err();
262 assert_eq!(err.object(), "Text");
263 let text_err = err.field("text");
264 assert!(text_err.includes(ValidationErrorKind::Required));
265 }
266
267 #[test]
268 fn it_requires_text_more_than_1_character() {
269 let err = Text::<Plain>::builder().text("").build().unwrap_err();
270 assert_eq!(err.object(), "Text");
271 let text_err = err.field("text");
272 assert!(text_err.includes(ValidationErrorKind::MinTextLength(1)));
273 }
274
275 #[test]
276 fn it_requires_text_less_than_3000_characters() {
277 let err = Text::<Plain>::builder()
278 .text("a".repeat(3001))
279 .build()
280 .unwrap_err();
281 assert_eq!(err.object(), "Text");
282 let text_err = err.field("text");
283 assert!(text_err.includes(ValidationErrorKind::MaxTextLength(3000)));
284 }
285
286 mod plain_text {
287 use super::*;
288
289 #[test]
290 fn it_implements_builder() {
291 let expected = Text::<Plain> {
292 r#type: std::marker::PhantomData,
293 text: Some("Hello World:smile:".into()),
294 emoji: Some(true),
295 verbatim: None,
296 };
297
298 let text = Text::builder()
299 .text("Hello World:smile:")
300 .emoji(true)
301 .build()
302 .unwrap();
303
304 assert_eq!(text, expected);
305
306 let text = Text::builder()
307 .set_text(Some("Hello World:smile:"))
308 .set_emoji(Some(true))
309 .build()
310 .unwrap();
311
312 assert_eq!(text, expected);
313 }
314
315 #[test]
316 fn it_serializes_to_json() {
317 let text = Text::<Plain> {
318 r#type: std::marker::PhantomData,
319 text: Some("Hello World :smile:".into()),
320 emoji: Some(true),
321 verbatim: None,
322 };
323
324 let expected = serde_json::json!({
325 "type": "plain_text",
326 "text": "Hello World :smile:",
327 "emoji": true
328 });
329 let json = serde_json::to_value(&text).unwrap();
330 assert_eq!(json, expected);
331 }
332
333 #[test]
334 fn it_equals_another_plain_text() {
335 let text1 = Text::<Plain> {
336 r#type: std::marker::PhantomData,
337 text: Some("Hello".into()),
338 emoji: Some(true),
339 verbatim: None,
340 };
341 let text2 = Text::<Plain> {
342 r#type: std::marker::PhantomData,
343 text: Some("Hello".into()),
344 emoji: Some(true),
345 verbatim: None,
346 };
347 let text3 = Text::<Plain> {
348 r#type: std::marker::PhantomData,
349 text: Some("Hello".into()),
350 emoji: Some(false),
351 verbatim: None,
352 };
353 let text4 = Text::<Plain> {
354 r#type: std::marker::PhantomData,
355 text: Some("World".into()),
356 emoji: Some(true),
357 verbatim: None,
358 };
359 let text5 = Text::<Plain> {
360 r#type: std::marker::PhantomData,
361 text: Some("Hello".into()),
362 emoji: None,
363 verbatim: None,
364 };
365 assert_eq!(text1, text2);
366 assert_ne!(text1, text3);
367 assert_ne!(text1, text4);
368 assert_eq!(text3, text5);
369 }
370 }
371
372 mod mrkdwn_text {
373 use super::*;
374
375 #[test]
376 fn it_implements_builder() {
377 let expected = Text::<Mrkdwn> {
378 r#type: std::marker::PhantomData,
379 text: Some("*Hello* _World_ :smile:".into()),
380 emoji: None,
381 verbatim: Some(false),
382 };
383
384 let text = Text::builder()
385 .text("*Hello* _World_ :smile:")
386 .verbatim(false)
387 .build()
388 .unwrap();
389
390 assert_eq!(text, expected);
391
392 let text = Text::builder()
393 .set_text(Some("*Hello* _World_ :smile:"))
394 .set_verbatim(Some(false))
395 .build()
396 .unwrap();
397
398 assert_eq!(text, expected);
399 }
400
401 #[test]
402 fn it_serializes_to_json() {
403 let text = Text::<Mrkdwn> {
404 r#type: std::marker::PhantomData,
405 text: Some("*Hello* _World_ :smile:".into()),
406 emoji: None,
407 verbatim: Some(false),
408 };
409
410 let expected = serde_json::json!({
411 "type": "mrkdwn",
412 "text": "*Hello* _World_ :smile:",
413 "verbatim": false
414 });
415 let json = serde_json::to_value(&text).unwrap();
416 assert_eq!(json, expected);
417 }
418
419 #[test]
420 fn it_equals_another_mrkdwn_text() {
421 let text1 = Text::<Mrkdwn> {
422 r#type: std::marker::PhantomData,
423 text: Some("*Hello*".into()),
424 emoji: None,
425 verbatim: Some(true),
426 };
427 let text2 = Text::<Mrkdwn> {
428 r#type: std::marker::PhantomData,
429 text: Some("*Hello*".into()),
430 emoji: None,
431 verbatim: Some(true),
432 };
433 let text3 = Text::<Mrkdwn> {
434 r#type: std::marker::PhantomData,
435 text: Some("*Hello*".into()),
436 emoji: None,
437 verbatim: Some(false),
438 };
439 let text4 = Text::<Mrkdwn> {
440 r#type: std::marker::PhantomData,
441 text: Some("_World_".into()),
442 emoji: None,
443 verbatim: Some(true),
444 };
445 let text5 = Text::<Mrkdwn> {
446 r#type: std::marker::PhantomData,
447 text: Some("*Hello*".into()),
448 emoji: None,
449 verbatim: None,
450 };
451 assert_eq!(text1, text2);
452 assert_ne!(text1, text3);
453 assert_ne!(text1, text4);
454 assert_eq!(text3, text5);
455 }
456 }
457
458 mod text_content {
459 use super::*;
460
461 #[test]
462 fn it_serializes_plain_text_variant_to_json() {
463 let text = TextContent::from(Text::<Plain> {
464 r#type: std::marker::PhantomData,
465 text: Some("Hello World :smile:".into()),
466 emoji: Some(true),
467 verbatim: None,
468 });
469
470 let expected = serde_json::json!({
471 "type": "plain_text",
472 "text": "Hello World :smile:",
473 "emoji": true
474 });
475 let json = serde_json::to_value(&text).unwrap();
476 assert_eq!(json, expected);
477 }
478
479 #[test]
480 fn it_serializes_mrkdwn_text_variant_to_json() {
481 let text = TextContent::from(Text::<Mrkdwn> {
482 r#type: std::marker::PhantomData,
483 text: Some("*Hello* _World_ :smile:".into()),
484 emoji: None,
485 verbatim: Some(false),
486 });
487
488 let expected = serde_json::json!({
489 "type": "mrkdwn",
490 "text": "*Hello* _World_ :smile:",
491 "verbatim": false
492 });
493 let json = serde_json::to_value(&text).unwrap();
494 assert_eq!(json, expected);
495 }
496 }
497}