terdoc_types/
output_format.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::ops::Deref;
5
6use serde::{de::Error as _, Deserialize, Deserializer, Serialize};
7
8/// Enum type for defining the possible output file formats
9#[derive(
10    Debug,
11    Clone,
12    PartialEq,
13    Eq,
14    Serialize,
15    Deserialize,
16    strum::AsRefStr,
17    strum::Display,
18    strum::EnumCount,
19    strum::EnumIter,
20    strum::EnumString,
21    strum::VariantNames,
22    strum::IntoStaticStr,
23)]
24#[serde(rename_all = "snake_case")]
25#[strum(serialize_all = "snake_case")]
26#[serde(tag = "format")]
27pub enum OutputFormat {
28    /// The PDF file format
29    Pdf {
30        /// Specifies the program that is used to render the PDF document
31        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
32        engine: PdfEngine,
33
34        /// The theme which should be used for the rendered document
35        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
36        theme: Option<Theme>,
37    },
38
39    /// Microsoft Office Open XML docx format
40    Docx {
41        /// The theme which should be used for the rendered document
42        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
43        theme: Option<Theme>,
44    },
45
46    /// OpenDocument Text format
47    Odt {
48        /// The theme which should be used for the rendered document
49        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
50        theme: Option<Theme>,
51    },
52
53    /// Markdown
54    Markdown {
55        /// The theme which should be used for the rendered document
56        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
57        theme: Option<Theme>,
58    },
59
60    /// CommonMark flavored Markdown
61    Commonmark {
62        /// The theme which should be used for the rendered document
63        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
64        theme: Option<Theme>,
65    },
66
67    /// CommonMark flavored Markdown with pandoc extensions
68    CommonmarkX {
69        /// The theme which should be used for the rendered document
70        #[serde(default, skip_serializing_if = "crate::utils::is_default")]
71        theme: Option<Theme>,
72    },
73}
74
75impl OutputFormat {
76    /// Create the PDF output format with the default configuration.
77    pub fn default_pdf() -> Self {
78        Self::Pdf {
79            engine: Default::default(),
80            theme: Default::default(),
81        }
82    }
83}
84
85impl Default for OutputFormat {
86    fn default() -> Self {
87        Self::default_pdf()
88    }
89}
90
91impl OutputFormat {
92    /// The color theme of the output document.
93    pub fn theme(&self) -> Option<&str> {
94        match self {
95            Self::Pdf { theme, .. }
96            | Self::Docx { theme }
97            | Self::Odt { theme }
98            | Self::Markdown { theme }
99            | Self::Commonmark { theme }
100            | Self::CommonmarkX { theme } => theme.as_deref(),
101        }
102    }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
106pub struct Theme(String);
107
108impl Theme {
109    pub fn new(theme: String) -> Result<Self, &'static str> {
110        if !theme
111            .chars()
112            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
113        {
114            return Err("Only alphanumeric chars and `-_` are allowed");
115        }
116
117        Ok(Self(theme))
118    }
119    pub fn inner(&self) -> &str {
120        &self.0
121    }
122
123    pub fn into_inner(self) -> String {
124        self.0
125    }
126}
127
128impl<'de> Deserialize<'de> for Theme {
129    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130    where
131        D: Deserializer<'de>,
132    {
133        let inner = String::deserialize(deserializer)?;
134
135        // Return the actual contract
136        Self::new(inner).map_err(D::Error::custom)
137    }
138}
139
140impl Deref for Theme {
141    type Target = str;
142
143    fn deref(&self) -> &Self::Target {
144        self.0.as_str()
145    }
146}
147
148impl TryFrom<String> for Theme {
149    type Error = &'static str;
150
151    fn try_from(value: String) -> Result<Self, Self::Error> {
152        Self::new(value)
153    }
154}
155
156/// Specifies the program that is used to render the PDF document from the source template or an
157/// intermediary format.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
159#[serde(rename_all = "snake_case")]
160pub enum PdfEngine {
161    /// Use latex as a PDF rendering engine
162    #[default]
163    Latex,
164
165    /// Use LibreOffice as a PDFs rendering engine
166    LibreOffice,
167}
168
169#[cfg(test)]
170mod tests {
171    use pretty_assertions::assert_eq;
172    use serde_json::json;
173    use strum::VariantNames;
174
175    use super::*;
176
177    #[test]
178    fn default_value() {
179        assert_eq!(
180            OutputFormat::Pdf {
181                engine: Default::default(),
182                theme: Default::default(),
183            },
184            OutputFormat::default()
185        );
186    }
187
188    #[test]
189    fn serialize_strum_variant_names() {
190        assert_eq!(
191            "pdf",
192            OutputFormat::Pdf {
193                engine: Default::default(),
194                theme: Default::default(),
195            }
196            .as_ref()
197        );
198        assert_eq!(
199            "docx",
200            OutputFormat::Docx {
201                theme: Default::default(),
202            }
203            .as_ref()
204        );
205        assert_eq!(
206            "odt",
207            OutputFormat::Odt {
208                theme: Default::default(),
209            }
210            .as_ref()
211        );
212        assert_eq!(
213            "markdown",
214            OutputFormat::Markdown {
215                theme: Default::default(),
216            }
217            .as_ref()
218        );
219        assert_eq!(
220            "commonmark",
221            OutputFormat::Commonmark {
222                theme: Default::default(),
223            }
224            .as_ref()
225        );
226        assert_eq!(
227            "commonmark_x",
228            OutputFormat::CommonmarkX {
229                theme: Default::default(),
230            }
231            .as_ref()
232        );
233    }
234
235    #[test]
236    fn iterate_strum_variant_names() {
237        assert_eq!(
238            [
239                "pdf",
240                "docx",
241                "odt",
242                "markdown",
243                "commonmark",
244                "commonmark_x",
245            ],
246            OutputFormat::VARIANTS
247        );
248    }
249
250    #[test]
251    fn serialize_json() {
252        assert_eq!(
253            json!({
254                "format": "pdf",
255            }),
256            serde_json::to_value(OutputFormat::Pdf {
257                engine: Default::default(),
258                theme: Default::default(),
259            })
260            .unwrap()
261        );
262        assert_eq!(
263            json!({
264            "format": "docx",
265            }),
266            serde_json::to_value(OutputFormat::Docx {
267                theme: Default::default(),
268            })
269            .unwrap()
270        );
271        assert_eq!(
272            json!({
273            "format": "odt",
274            }),
275            serde_json::to_value(OutputFormat::Odt {
276                theme: Default::default(),
277            })
278            .unwrap()
279        );
280        assert_eq!(
281            json!({
282            "format": "markdown",
283            }),
284            serde_json::to_value(OutputFormat::Markdown {
285                theme: Default::default(),
286            })
287            .unwrap()
288        );
289        assert_eq!(
290            json!({
291            "format": "commonmark",
292            }),
293            serde_json::to_value(OutputFormat::Commonmark {
294                theme: Default::default(),
295            })
296            .unwrap()
297        );
298        assert_eq!(
299            json!({
300            "format": "commonmark_x",
301            }),
302            serde_json::to_value(OutputFormat::CommonmarkX {
303                theme: Default::default(),
304            })
305            .unwrap()
306        );
307    }
308
309    #[test]
310    fn deserialize_json() {
311        assert_eq!(
312            OutputFormat::Pdf {
313                engine: Default::default(),
314                theme: Default::default(),
315            },
316            serde_json::from_value(json!({"format": "pdf"})).unwrap()
317        );
318        assert_eq!(
319            OutputFormat::Docx {
320                theme: Default::default(),
321            },
322            serde_json::from_value(json!({"format": "docx"})).unwrap()
323        );
324        assert_eq!(
325            OutputFormat::Odt {
326                theme: Default::default(),
327            },
328            serde_json::from_value(json!({"format": "odt"})).unwrap()
329        );
330        assert_eq!(
331            OutputFormat::Markdown {
332                theme: Default::default(),
333            },
334            serde_json::from_value(json!({"format": "markdown"})).unwrap()
335        );
336        assert_eq!(
337            OutputFormat::Commonmark {
338                theme: Default::default(),
339            },
340            serde_json::from_value(json!({"format": "commonmark"})).unwrap()
341        );
342        assert_eq!(
343            OutputFormat::CommonmarkX {
344                theme: Default::default(),
345            },
346            serde_json::from_value(json!({"format": "commonmark_x"})).unwrap()
347        );
348
349        // Invalid theme
350        assert!(serde_json::from_value::<OutputFormat>(
351            json!({"format": "commonmark_x", "theme": "/asd/"})
352        )
353        .is_err());
354    }
355
356    #[test]
357    fn theme_invalid() {
358        assert!(Theme::try_from("value".to_string()).is_ok());
359        assert!(Theme::try_from("/".to_string()).is_err());
360        assert!(Theme::try_from("..".to_string()).is_err());
361    }
362}