1use std::fmt::Display;
61
62use derive_builder::{Builder, UninitializedFieldError};
63use serde::Serialize;
64use thiserror::Error;
65
66#[doc = "The Text message type"]
67#[derive(Serialize, Clone, Builder)]
68#[serde(rename_all = "camelCase")]
69#[builder(build_fn(error = "ChatTypeBuildError"))]
70pub struct Text {
71 #[builder(setter(into))]
72 text: String,
73}
74
75#[doc = "The Card message type"]
76#[derive(Serialize, Clone, Builder)]
77#[serde(rename_all = "camelCase")]
78#[builder(build_fn(error = "ChatTypeBuildError"))]
79pub struct Cards {
80 cards: Vec<Card>,
81}
82
83#[derive(Serialize, Clone, Default, Builder)]
86#[serde(rename_all = "camelCase")]
87#[builder(build_fn(error = "ChatTypeBuildError"))]
88pub struct Card {
89 #[builder(setter(strip_option), default)]
90 #[serde(skip_serializing_if = "Option::is_none")]
91 header: Option<Header>,
92 #[builder(setter(strip_option), default)]
93 #[serde(skip_serializing_if = "Option::is_none")]
94 sections: Option<Vec<Section>>,
95}
96
97#[derive(Serialize, Clone, Default, Builder)]
98#[serde(rename_all = "camelCase")]
99#[builder(build_fn(error = "ChatTypeBuildError"))]
100pub struct Header {
101 #[builder(setter(into, strip_option), default)]
102 #[serde(skip_serializing_if = "Option::is_none")]
103 title: Option<String>,
104 #[builder(setter(into, strip_option), default)]
105 #[serde(skip_serializing_if = "Option::is_none")]
106 subtitle: Option<String>,
107 #[builder(setter(into, strip_option), default)]
108 #[serde(skip_serializing_if = "Option::is_none")]
109 image_url: Option<String>,
110 #[builder(setter(into, strip_option), default)]
111 #[serde(skip_serializing_if = "Option::is_none")]
112 image_style: Option<String>,
113}
114
115#[derive(Serialize, Clone, Default, Builder)]
116#[serde(rename_all = "camelCase")]
117#[builder(build_fn(error = "ChatTypeBuildError"))]
118pub struct Section {
119 #[builder(setter(into, strip_option), default)]
120 #[serde(skip_serializing_if = "Option::is_none")]
121 header: Option<String>,
122 #[builder(setter(strip_option), default)]
123 #[serde(skip_serializing_if = "Option::is_none")]
124 widgets: Option<Vec<Widget>>,
125}
126
127#[derive(Serialize, Clone, Default, Builder)]
128#[serde(rename_all = "camelCase")]
129#[builder(build_fn(error = "ChatTypeBuildError"))]
130pub struct Widget {
131 #[builder(setter(strip_option), default)]
132 #[serde(skip_serializing_if = "Option::is_none")]
133 text_paragraph: Option<TextParagraph>,
134 #[builder(setter(strip_option), default)]
135 #[serde(skip_serializing_if = "Option::is_none")]
136 key_value: Option<KeyValue>,
137 #[builder(setter(strip_option), default)]
138 #[serde(skip_serializing_if = "Option::is_none")]
139 image: Option<Image>,
140 #[builder(setter(strip_option), default)]
141 #[serde(skip_serializing_if = "Option::is_none")]
142 buttons: Option<Vec<Button>>,
143}
144
145#[derive(Serialize, Clone, Builder)]
146#[serde(rename_all = "camelCase")]
147#[builder(build_fn(error = "ChatTypeBuildError"))]
148pub struct TextParagraph {
149 #[builder(setter(into))]
150 text: String,
151}
152
153#[derive(Serialize, Clone, Default, Builder)]
154#[serde(rename_all = "camelCase")]
155#[builder(build_fn(error = "ChatTypeBuildError"))]
156pub struct KeyValue {
157 #[builder(setter(into, strip_option), default)]
158 #[serde(skip_serializing_if = "Option::is_none")]
159 top_label: Option<String>,
160 #[builder(setter(into, strip_option), default)]
161 #[serde(skip_serializing_if = "Option::is_none")]
162 content: Option<String>,
163 #[builder(setter(into, strip_option), default)]
164 #[serde(skip_serializing_if = "Option::is_none")]
165 icon: Option<String>,
166 #[builder(setter(into, strip_option), default)]
167 #[serde(skip_serializing_if = "Option::is_none")]
168 content_multiline: Option<String>,
169 #[builder(setter(into, strip_option), default)]
170 #[serde(skip_serializing_if = "Option::is_none")]
171 bottom_label: Option<String>,
172 #[builder(setter(strip_option), default)]
173 #[serde(skip_serializing_if = "Option::is_none")]
174 on_click: Option<OnClick>,
175 #[builder(setter(strip_option), default)]
176 #[serde(skip_serializing_if = "Option::is_none")]
177 button: Option<Button>,
178}
179
180impl KeyValue {
181 pub fn to_widget(&self) -> Widget {
182 Widget {
183 text_paragraph: None,
184 image: None,
185 buttons: None,
186 key_value: Some(self.to_owned()),
187 }
188 }
189}
190
191#[derive(Serialize, Clone, Default, Builder)]
192#[serde(rename_all = "camelCase")]
193#[builder(build_fn(error = "ChatTypeBuildError"))]
194pub struct Image {
195 #[builder(setter(into, strip_option), default)]
196 #[serde(skip_serializing_if = "Option::is_none")]
197 image_url: Option<String>,
198 #[builder(setter(strip_option), default)]
199 #[serde(skip_serializing_if = "Option::is_none")]
200 on_click: Option<OnClick>,
201}
202
203impl Image {
204 pub fn to_widget(&self) -> Widget {
205 Widget {
206 text_paragraph: None,
207 key_value: None,
208 image: Some(self.to_owned()),
209 buttons: None,
210 }
211 }
212}
213
214#[derive(Serialize, Clone, Default, Builder)]
215#[serde(rename_all = "camelCase")]
216#[builder(build_fn(error = "ChatTypeBuildError"))]
217pub struct Button {
218 #[builder(setter(strip_option), default)]
219 #[serde(skip_serializing_if = "Option::is_none")]
220 text_button: Option<TextButton>,
221 #[builder(setter(strip_option), default)]
222 #[serde(skip_serializing_if = "Option::is_none")]
223 image_button: Option<ImageButton>,
224}
225
226#[derive(Serialize, Clone, Builder)]
227#[serde(rename_all = "camelCase")]
228#[builder(build_fn(error = "ChatTypeBuildError"))]
229pub struct TextButton {
230 text: String,
231 on_click: OnClick,
232}
233
234#[derive(Serialize, Clone, Default, Builder)]
235#[serde(rename_all = "camelCase")]
236#[builder(build_fn(error = "ChatTypeBuildError"))]
237pub struct ImageButton {
238 #[builder(setter(into, strip_option), default)]
239 #[serde(skip_serializing_if = "Option::is_none")]
240 icon_url: Option<String>,
241 #[builder(setter(into, strip_option), default)]
242 #[serde(skip_serializing_if = "Option::is_none")]
243 icon: Option<String>,
244 #[builder(setter(strip_option), default)]
245 #[serde(skip_serializing_if = "Option::is_none")]
246 on_click: Option<OnClick>,
247}
248
249#[derive(Serialize, Clone, Builder)]
250#[serde(rename_all = "camelCase")]
251#[builder(build_fn(error = "ChatTypeBuildError"))]
252pub struct OnClick {
253 #[builder(private)]
254 open_link: OpenLink,
255}
256
257#[derive(Serialize, Clone, Builder)]
258#[serde(rename_all = "camelCase")]
259#[builder(build_fn(error = "ChatTypeBuildError"))]
260pub struct OpenLink {
261 #[builder(private)]
262 url: String,
263}
264
265#[derive(Debug, Error)]
266pub struct ChatTypeBuildError(String);
267
268impl Display for ChatTypeBuildError {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.write_fmt(format_args!("Error in build process {}", self.0))
271 }
272}
273
274impl From<UninitializedFieldError> for ChatTypeBuildError {
275 fn from(e: UninitializedFieldError) -> Self {
276 Self(e.to_string())
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use crate::*;
283 #[test]
284 fn build_cards() {
285 let text_paragraph = TextParagraphBuilder::default()
286 .text("some text")
287 .build()
288 .unwrap();
289 let widget = WidgetBuilder::default()
290 .text_paragraph(text_paragraph)
291 .build()
292 .unwrap();
293 let section = SectionBuilder::default()
294 .widgets(vec![widget])
295 .build()
296 .unwrap();
297 let header = HeaderBuilder::default().title("some tile").build().unwrap();
298 let card = CardBuilder::default()
299 .sections(vec![section])
300 .header(header)
301 .build()
302 .unwrap();
303 let cards = CardsBuilder::default().cards(vec![card]).build().unwrap();
304 let json = serde_json::json!(cards).to_string();
305 assert_eq!(json,"{\"cards\":[{\"header\":{\"title\":\"some tile\"},\"sections\":[{\"widgets\":[{\"textParagraph\":{\"text\":\"some text\"}}]}]}]}")
306 }
307
308 #[test]
309 fn build_text() {
310 let text = TextBuilder::default().text("some text").build().unwrap();
311 let json = serde_json::json!(text).to_string();
312 assert_eq!(json, "{\"text\":\"some text\"}")
313 }
314}