1use crate::expand::ExpandedContent;
8use crate::utils::defuse_code_fences;
9use serenity::all::{CreateAllowedMentions, CreateMessage, Message};
10
11pub fn build_messages(request: &Message, results: Vec<ExpandedContent>) -> Vec<CreateMessage> {
16 let mut embeds = Vec::new();
17 let mut code_blocks = Vec::new();
18
19 for result in results {
20 match result {
21 ExpandedContent::Embed(embed) => embeds.push(*embed),
22 ExpandedContent::CodeBlock {
23 language,
24 code,
25 metadata,
26 } => {
27 let code = defuse_code_fences(&code);
28 code_blocks.push(
34 CreateMessage::new()
35 .content(format!("{metadata}\n```{language}\n{code}\n```"))
36 .allowed_mentions(CreateAllowedMentions::new()),
37 );
38 }
39 }
40 }
41
42 if embeds.is_empty() {
43 return code_blocks;
44 }
45
46 let preview = CreateMessage::new()
49 .embeds(embeds)
50 .reference_message(request)
51 .allowed_mentions(CreateAllowedMentions::new().replied_user(true));
52
53 std::iter::once(preview).chain(code_blocks).collect()
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use serenity::all::CreateEmbed;
60
61 fn rendered(message: &CreateMessage) -> String {
65 format!("{message:?}")
66 }
67
68 fn embed() -> ExpandedContent {
69 ExpandedContent::Embed(Box::new(CreateEmbed::new().description("quoted")))
70 }
71
72 fn code_block() -> ExpandedContent {
73 ExpandedContent::CodeBlock {
74 language: "rust".to_string(),
75 code: "fn main() {}".to_string(),
76 metadata: "src/main.rs L1".to_string(),
77 }
78 }
79
80 #[test]
81 fn embeds_share_one_message_and_every_code_block_gets_its_own() {
82 let messages = build_messages(
83 &Message::default(),
84 vec![embed(), code_block(), embed(), code_block()],
85 );
86
87 assert_eq!(messages.len(), 3);
88 }
89
90 #[test]
91 fn embeds_are_sent_before_code_blocks() {
92 let messages = build_messages(&Message::default(), vec![code_block(), embed()]);
93
94 assert!(rendered(&messages[0]).contains("quoted"));
95 assert!(rendered(&messages[1]).contains("src/main.rs L1"));
96 }
97
98 #[test]
99 fn nothing_is_sent_without_expanded_content() {
100 assert!(build_messages(&Message::default(), Vec::new()).is_empty());
101 }
102
103 #[test]
104 fn code_block_is_fenced_with_its_language_below_its_metadata() {
105 let messages = build_messages(&Message::default(), vec![code_block()]);
106
107 assert!(rendered(&messages[0]).contains(r"src/main.rs L1\n```rust\nfn main() {}\n```"));
108 }
109
110 #[test]
111 fn only_the_reply_ping_is_allowed_in_the_preview() {
112 let messages = build_messages(&Message::default(), vec![embed()]);
113
114 let rendered = rendered(&messages[0]);
115 assert!(rendered.contains("parse: [], users: [], roles: [], replied_user: Some(true)"));
116 }
117
118 #[test]
119 fn a_code_block_mentions_nobody() {
120 let messages = build_messages(&Message::default(), vec![code_block()]);
121
122 let rendered = rendered(&messages[0]);
123 assert!(rendered.contains("parse: [], users: [], roles: [], replied_user: None"));
124 }
125}