1use crate::xml_escape::{escape_xml_attr, escape_xml_text};
37use crate::TwiML;
38
39#[derive(Debug, Clone, Default)]
41pub struct MessageAttributes {
42 pub action: Option<String>,
44 pub from: Option<String>,
46 pub method: Option<String>,
48 pub status_callback: Option<String>,
50 pub to: Option<String>,
52}
53
54impl MessageAttributes {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn action(mut self, action: impl Into<String>) -> Self {
62 self.action = Some(action.into());
63 self
64 }
65
66 pub fn from(mut self, from: impl Into<String>) -> Self {
68 self.from = Some(from.into());
69 self
70 }
71
72 pub fn method(mut self, method: impl Into<String>) -> Self {
74 self.method = Some(method.into());
75 self
76 }
77
78 pub fn status_callback(mut self, status_callback: impl Into<String>) -> Self {
80 self.status_callback = Some(status_callback.into());
81 self
82 }
83
84 pub fn to(mut self, to: impl Into<String>) -> Self {
86 self.to = Some(to.into());
87 self
88 }
89}
90
91#[derive(Debug, Clone, Default)]
93pub struct RedirectAttributes {
94 pub method: Option<String>,
96}
97
98impl RedirectAttributes {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn method(mut self, method: impl Into<String>) -> Self {
106 self.method = Some(method.into());
107 self
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct Body {
114 message: String,
115}
116
117impl Body {
118 pub fn new(message: impl Into<String>) -> Self {
120 Self {
121 message: message.into(),
122 }
123 }
124
125 fn to_xml(&self) -> String {
126 format!("<Body>{}</Body>", escape_xml_text(&self.message))
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct Media {
133 url: String,
134}
135
136impl Media {
137 pub fn new(url: impl Into<String>) -> Self {
139 Self { url: url.into() }
140 }
141
142 fn to_xml(&self) -> String {
143 format!("<Media>{}</Media>", escape_xml_text(&self.url))
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct Message {
150 attributes: MessageAttributes,
151 body: Option<Body>,
152 media: Vec<Media>,
153}
154
155impl Message {
156 pub(crate) fn new(attributes: MessageAttributes, body: Option<String>) -> Self {
158 Self {
159 attributes,
160 body: body.map(Body::new),
161 media: Vec::new(),
162 }
163 }
164
165 pub fn with_nouns(attributes: MessageAttributes) -> Self {
167 Self {
168 attributes,
169 body: None,
170 media: Vec::new(),
171 }
172 }
173
174 pub fn body(mut self, body: Body) -> Self {
176 self.body = Some(body);
177 self
178 }
179
180 pub fn add_media(mut self, media: Media) -> Self {
182 self.media.push(media);
183 self
184 }
185
186 pub fn media(mut self, media: Vec<Media>) -> Self {
188 self.media = media;
189 self
190 }
191
192 fn to_xml(&self) -> String {
193 let mut xml = String::from("<Message");
194
195 if let Some(ref action) = self.attributes.action {
197 xml.push_str(&format!(" action=\"{}\"", escape_xml_attr(action)));
198 }
199 if let Some(ref from) = self.attributes.from {
200 xml.push_str(&format!(" from=\"{}\"", escape_xml_attr(from)));
201 }
202 if let Some(ref method) = self.attributes.method {
203 xml.push_str(&format!(" method=\"{}\"", escape_xml_attr(method)));
204 }
205 if let Some(ref status_callback) = self.attributes.status_callback {
206 xml.push_str(&format!(
207 " statusCallback=\"{}\"",
208 escape_xml_attr(status_callback)
209 ));
210 }
211 if let Some(ref to) = self.attributes.to {
212 xml.push_str(&format!(" to=\"{}\"", escape_xml_attr(to)));
213 }
214
215 if self.body.is_some() || !self.media.is_empty() {
217 xml.push('>');
218
219 if let Some(ref body) = self.body {
221 xml.push_str(&body.to_xml());
222 }
223
224 for media in &self.media {
226 xml.push_str(&media.to_xml());
227 }
228
229 xml.push_str("</Message>");
230 } else {
231 xml.push_str(" />");
232 }
233
234 xml
235 }
236}
237
238#[derive(Debug, Clone)]
240pub struct Redirect {
241 attributes: RedirectAttributes,
242 url: String,
243}
244
245impl Redirect {
246 pub(crate) fn new(attributes: RedirectAttributes, url: impl Into<String>) -> Self {
248 Self {
249 attributes,
250 url: url.into(),
251 }
252 }
253
254 fn to_xml(&self) -> String {
255 let mut xml = String::from("<Redirect");
256
257 if let Some(ref method) = self.attributes.method {
258 xml.push_str(&format!(" method=\"{}\"", escape_xml_attr(method)));
259 }
260
261 xml.push_str(&format!(">{}</Redirect>", escape_xml_text(&self.url)));
262 xml
263 }
264}
265
266#[derive(Debug, Clone)]
268pub(crate) enum MessagingVerb {
269 Message(Message),
270 Redirect(Redirect),
271}
272
273#[derive(Debug, Clone, Default)]
275pub struct MessagingResponse {
276 pub(crate) verbs: Vec<MessagingVerb>,
277 comments_before: Vec<String>,
278 comments: Vec<String>,
279 comments_after: Vec<String>,
280}
281
282impl MessagingResponse {
283 pub fn new() -> Self {
287 Self::default()
288 }
289
290 pub fn message(mut self, body: impl Into<String>) -> Self {
302 let message = Message::new(MessageAttributes::default(), Some(body.into()));
303 self.verbs.push(MessagingVerb::Message(message));
304 self
305 }
306
307 pub fn message_with_attributes(
316 mut self,
317 attributes: MessageAttributes,
318 body: impl Into<String>,
319 ) -> Self {
320 let message = Message::new(attributes, Some(body.into()));
321 self.verbs.push(MessagingVerb::Message(message));
322 self
323 }
324
325 pub fn message_with_nouns(mut self, message: Message) -> Self {
349 self.verbs.push(MessagingVerb::Message(message));
350 self
351 }
352
353 pub fn redirect(mut self, url: impl Into<String>) -> Self {
365 let redirect = Redirect::new(RedirectAttributes::default(), url);
366 self.verbs.push(MessagingVerb::Redirect(redirect));
367 self
368 }
369
370 pub fn redirect_with_attributes(
379 mut self,
380 attributes: RedirectAttributes,
381 url: impl Into<String>,
382 ) -> Self {
383 let redirect = Redirect::new(attributes, url);
384 self.verbs.push(MessagingVerb::Redirect(redirect));
385 self
386 }
387
388 pub fn comment(mut self, comment: impl Into<String>) -> Self {
396 self.comments.push(comment.into());
397 self
398 }
399
400 pub fn comment_after(mut self, comment: impl Into<String>) -> Self {
408 self.comments_after.push(comment.into());
409 self
410 }
411
412 pub fn comment_before(mut self, comment: impl Into<String>) -> Self {
420 self.comments_before.push(comment.into());
421 self
422 }
423}
424
425impl TwiML for MessagingResponse {
426 fn to_xml(&self) -> String {
427 let mut xml = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
428
429 for comment in &self.comments_before {
431 xml.push_str(&format!("<!-- {} -->\n", escape_xml_text(comment)));
432 }
433
434 xml.push_str("<Response>");
435
436 for comment in &self.comments {
438 xml.push_str(&format!("\n <!-- {} -->", escape_xml_text(comment)));
439 }
440
441 for verb in &self.verbs {
442 match verb {
443 MessagingVerb::Message(message) => {
444 xml.push_str(&message.to_xml());
445 }
446 MessagingVerb::Redirect(redirect) => {
447 xml.push_str(&redirect.to_xml());
448 }
449 }
450 }
451
452 xml.push_str("</Response>");
453
454 for comment in &self.comments_after {
456 xml.push_str(&format!("\n<!-- {} -->", escape_xml_text(comment)));
457 }
458
459 xml
460 }
461}