sfr_types/
slash_command.rs1use crate::{Block, Layouts, MessagePayloads};
6use serde::{Deserialize, Serialize};
7
8#[derive(Deserialize, Debug, Clone)]
12#[serde(rename_all = "snake_case")]
13pub struct SlashCommandBody {
14 pub token: String,
16
17 pub command: String,
19
20 pub text: String,
22
23 pub response_url: String,
25
26 pub trigger_id: String,
28
29 pub user_id: String,
31
32 pub user_name: String,
34
35 pub team_id: String,
37
38 pub team_domain: String,
40
41 pub enterprise_id: Option<String>,
43
44 pub enterprise_name: Option<String>,
46
47 pub channel_id: String,
49
50 pub channel_name: String,
52
53 pub api_app_id: String,
55
56 pub is_enterprise_install: bool,
58}
59
60#[derive(Debug, Clone)]
62pub enum SlashCommandResponse {
63 Empty,
65
66 String(String),
68
69 Layouts(Layouts),
71
72 LayoutsInChannel(Layouts),
74}
75
76impl SlashCommandResponse {
77 pub fn empty() -> Self {
79 Self::Empty
80 }
81
82 pub fn string(string: String) -> Self {
84 Self::String(string)
85 }
86}
87
88mod inner {
89 use super::*;
92 use serde::ser::{Error as SerError, Serialize, Serializer};
93
94 #[derive(Serialize)]
96 struct TmpLayouts<'a> {
97 #[serde(skip_serializing_if = "Option::is_none")]
101 response_type: Option<SlashCommandResponseType>,
102
103 #[serde(flatten)]
107 layouts: TmpLayoutsInner<'a>,
108 }
109
110 #[derive(Serialize)]
112 #[serde(untagged)]
113 enum TmpLayoutsInner<'a> {
114 Block {
116 blocks: [&'a Block; 1],
118 },
119
120 Blocks {
122 blocks: &'a Vec<Block>,
124 },
125
126 BlocksArray(&'a MessagePayloads),
128 }
129
130 #[derive(Serialize, Debug, Clone)]
132 #[serde(rename_all = "snake_case")]
133 enum SlashCommandResponseType {
134 InChannel,
137 }
138
139 impl Serialize for SlashCommandResponse {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141 where
142 S: Serializer,
143 {
144 match self {
145 Self::Layouts(layouts) | Self::LayoutsInChannel(layouts) => {
146 let response_type = matches!(self, Self::LayoutsInChannel(_))
147 .then_some(SlashCommandResponseType::InChannel);
148
149 into_tmp_layouts(response_type, layouts).serialize(serializer)
150 }
151
152 Self::Empty | Self::String(_) => {
153 Err(S::Error::custom("Empty or String must not serialize"))
154 }
155 }
156 }
157 }
158
159 fn into_tmp_layouts(
161 response_type: Option<SlashCommandResponseType>,
162 layouts: &Layouts,
163 ) -> TmpLayouts<'_> {
164 let layouts = match layouts {
165 Layouts::SingleBlock(block) => TmpLayoutsInner::Block { blocks: [block] },
166 Layouts::MultipleBlocks(blocks) => TmpLayoutsInner::Blocks { blocks },
167 Layouts::BlocksArray(payload) => TmpLayoutsInner::BlocksArray(payload),
168 };
169
170 TmpLayouts {
171 response_type,
172 layouts,
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 use crate::SectionBlock;
182 use crate::{PlainTextObject, TextObject};
183 use anyhow::Result;
184
185 #[test]
186 fn test_serialize_empty() -> Result<()> {
187 let testdate = SlashCommandResponse::empty();
188 assert!(serde_json::to_string(&testdate).is_err());
189 Ok(())
190 }
191
192 #[test]
193 fn test_serialize_text() -> Result<()> {
194 let testdate = SlashCommandResponse::String("test".into());
195 assert!(serde_json::to_string(&testdate).is_err());
196 Ok(())
197 }
198
199 fn only_text_layouts(text: String) -> Layouts {
200 Layouts::BlocksArray(MessagePayloads {
201 text,
202
203 blocks: Default::default(),
204 attachments: Default::default(),
205 thread_ts: Default::default(),
206 mrkdwn: Default::default(),
207 })
208 }
209
210 #[test]
211 fn test_serialize_layouts_to_ephemeral() -> Result<()> {
212 let testdate = SlashCommandResponse::Layouts(only_text_layouts("test".into()));
213 let expected = r#"{"text":"test"}"#;
214 assert_eq!(serde_json::to_string(&testdate)?, expected);
215
216 let testdate = SlashCommandResponse::Layouts(Layouts::MultipleBlocks(vec![]));
217 let expected = r#"{"blocks":[]}"#;
218 assert_eq!(serde_json::to_string(&testdate)?, expected);
219
220 let testdate = SlashCommandResponse::Layouts(Layouts::SingleBlock(Box::new(
221 Block::Section(SectionBlock {
222 text: Some(TextObject::PlainText(PlainTextObject {
223 text: "dummy".into(),
224 emoji: None,
225 })),
226 ..Default::default()
227 }),
228 )));
229 let expected =
230 r#"{"blocks":[{"type":"section","text":{"type":"plain_text","text":"dummy"}}]}"#;
231 assert_eq!(serde_json::to_string(&testdate)?, expected);
232
233 Ok(())
234 }
235
236 #[test]
237 fn test_serialize_layouts_to_in_channel() -> Result<()> {
238 let testdate = SlashCommandResponse::LayoutsInChannel(only_text_layouts("test".into()));
239 let expected = r#"{"response_type":"in_channel","text":"test"}"#;
240 assert_eq!(serde_json::to_string(&testdate)?, expected);
241 Ok(())
242 }
243}