twiml_rust/
fax.rs

1//! TwiML generation for fax responses.
2//!
3//! This module provides types and builders for creating TwiML responses
4//! that handle incoming faxes. The main entry point is [`FaxResponse`],
5//! which can contain a [`Receive`] verb to configure how faxes are received.
6//!
7//! # Example
8//!
9//! ```rust
10//! use twiml_rust::{FaxResponse, fax::{ReceiveAttributes, ReceiveMediaType}, TwiML};
11//!
12//! let response = FaxResponse::new()
13//!     .receive(Some(
14//!         ReceiveAttributes::new()
15//!             .action("https://example.com/fax-received")
16//!             .media_type(ReceiveMediaType::ApplicationPdf)
17//!     ));
18//!
19//! println!("{}", response.to_xml());
20//! ```
21
22use crate::xml_escape::{escape_xml_attr, escape_xml_text};
23use crate::TwiML;
24
25/// Media type for fax storage
26#[derive(Debug, Clone, PartialEq)]
27pub enum ReceiveMediaType {
28    /// PDF format - "application/pdf"
29    ApplicationPdf,
30    /// TIFF format - "image/tiff"
31    ImageTiff,
32}
33
34impl ReceiveMediaType {
35    fn as_str(&self) -> &str {
36        match self {
37            ReceiveMediaType::ApplicationPdf => "application/pdf",
38            ReceiveMediaType::ImageTiff => "image/tiff",
39        }
40    }
41}
42
43/// Page size for received faxes
44#[derive(Debug, Clone, PartialEq)]
45pub enum ReceivePageSize {
46    /// Letter size (8.5" x 11")
47    Letter,
48    /// Legal size (8.5" x 14")
49    Legal,
50    /// A4 size (210mm x 297mm)
51    A4,
52}
53
54impl ReceivePageSize {
55    fn as_str(&self) -> &str {
56        match self {
57            ReceivePageSize::Letter => "letter",
58            ReceivePageSize::Legal => "legal",
59            ReceivePageSize::A4 => "a4",
60        }
61    }
62}
63
64/// Attributes to pass to receive
65#[derive(Debug, Clone, Default)]
66pub struct ReceiveAttributes {
67    /// action - Receive action URL
68    pub action: Option<String>,
69    /// mediaType - The media type used to store media in the fax media store
70    pub media_type: Option<ReceiveMediaType>,
71    /// method - Receive action URL method
72    pub method: Option<String>,
73    /// pageSize - What size to interpret received pages as
74    pub page_size: Option<ReceivePageSize>,
75    /// storeMedia - Whether or not to store received media in the fax media store
76    pub store_media: Option<bool>,
77}
78
79impl ReceiveAttributes {
80    /// Create a new ReceiveAttributes
81    pub fn new() -> Self {
82        Self::default()
83    }
84
85    /// Set the action URL
86    pub fn action(mut self, action: impl Into<String>) -> Self {
87        self.action = Some(action.into());
88        self
89    }
90
91    /// Set the media type
92    pub fn media_type(mut self, media_type: ReceiveMediaType) -> Self {
93        self.media_type = Some(media_type);
94        self
95    }
96
97    /// Set the HTTP method
98    pub fn method(mut self, method: impl Into<String>) -> Self {
99        self.method = Some(method.into());
100        self
101    }
102
103    /// Set the page size
104    pub fn page_size(mut self, page_size: ReceivePageSize) -> Self {
105        self.page_size = Some(page_size);
106        self
107    }
108
109    /// Set whether to store media
110    pub fn store_media(mut self, store_media: bool) -> Self {
111        self.store_media = Some(store_media);
112        self
113    }
114}
115
116/// <Receive> TwiML Verb
117#[derive(Debug, Clone)]
118pub struct Receive {
119    attributes: ReceiveAttributes,
120}
121
122impl Receive {
123    /// Create a new Receive verb with attributes
124    pub(crate) fn new(attributes: Option<ReceiveAttributes>) -> Self {
125        Self {
126            attributes: attributes.unwrap_or_default(),
127        }
128    }
129
130    fn to_xml(&self) -> String {
131        let mut attrs = Vec::new();
132
133        if let Some(ref action) = self.attributes.action {
134            attrs.push(format!(" action=\"{}\"", escape_xml_attr(action)));
135        }
136        if let Some(ref media_type) = self.attributes.media_type {
137            attrs.push(format!(" mediaType=\"{}\"", media_type.as_str()));
138        }
139        if let Some(ref method) = self.attributes.method {
140            attrs.push(format!(" method=\"{}\"", escape_xml_attr(method)));
141        }
142        if let Some(ref page_size) = self.attributes.page_size {
143            attrs.push(format!(" pageSize=\"{}\"", page_size.as_str()));
144        }
145        if let Some(store_media) = self.attributes.store_media {
146            attrs.push(format!(" storeMedia=\"{}\"", store_media));
147        }
148
149        format!("<Receive{}/>", attrs.join(""))
150    }
151}
152
153/// <Response> TwiML for Faxes
154#[derive(Debug, Clone, Default)]
155pub struct FaxResponse {
156    receive: Option<Receive>,
157    comments_before: Vec<String>,
158    comments: Vec<String>,
159    comments_after: Vec<String>,
160}
161
162impl FaxResponse {
163    /// Create a new FaxResponse
164    ///
165    /// <Response> TwiML for Faxes
166    pub fn new() -> Self {
167        Self::default()
168    }
169
170    /// <Receive> TwiML Verb
171    ///
172    /// # Arguments
173    /// * `attributes` - TwiML attributes
174    ///
175    /// # Returns
176    /// Returns self for method chaining
177    pub fn receive(mut self, attributes: Option<ReceiveAttributes>) -> Self {
178        self.receive = Some(Receive::new(attributes));
179        self
180    }
181
182    /// Comments in <Response>
183    ///
184    /// # Arguments
185    /// * `comment` - XML Comment
186    ///
187    /// # Returns
188    /// Returns self for method chaining
189    pub fn comment(mut self, comment: impl Into<String>) -> Self {
190        self.comments.push(comment.into());
191        self
192    }
193
194    /// Comments after <Response>
195    ///
196    /// # Arguments
197    /// * `comment` - XML Comment
198    ///
199    /// # Returns
200    /// Returns self for method chaining
201    pub fn comment_after(mut self, comment: impl Into<String>) -> Self {
202        self.comments_after.push(comment.into());
203        self
204    }
205
206    /// Comments before <Response>
207    ///
208    /// # Arguments
209    /// * `comment` - XML Comment
210    ///
211    /// # Returns
212    /// Returns self for method chaining
213    pub fn comment_before(mut self, comment: impl Into<String>) -> Self {
214        self.comments_before.push(comment.into());
215        self
216    }
217}
218
219impl TwiML for FaxResponse {
220    fn to_xml(&self) -> String {
221        let mut xml = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
222
223        // Add comments before Response
224        for comment in &self.comments_before {
225            xml.push_str(&format!("<!-- {} -->\n", escape_xml_text(comment)));
226        }
227
228        xml.push_str("<Response>");
229
230        // Add comments inside Response
231        for comment in &self.comments {
232            xml.push_str(&format!("\n  <!-- {} -->", escape_xml_text(comment)));
233        }
234
235        if let Some(ref receive) = self.receive {
236            xml.push_str(&receive.to_xml());
237        }
238
239        xml.push_str("</Response>");
240
241        // Add comments after Response
242        for comment in &self.comments_after {
243            xml.push_str(&format!("\n<!-- {} -->", escape_xml_text(comment)));
244        }
245
246        xml
247    }
248}