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