1use std::ops::Deref;
5
6use serde::{de::Error as _, Deserialize, Deserializer, Serialize};
7
8#[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 Pdf {
30 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
32 engine: PdfEngine,
33
34 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
36 theme: Option<Theme>,
37 },
38
39 Docx {
41 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
43 theme: Option<Theme>,
44 },
45
46 Odt {
48 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
50 theme: Option<Theme>,
51 },
52
53 Markdown {
55 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
57 theme: Option<Theme>,
58 },
59
60 Commonmark {
62 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
64 theme: Option<Theme>,
65 },
66
67 CommonmarkX {
69 #[serde(default, skip_serializing_if = "crate::utils::is_default")]
71 theme: Option<Theme>,
72 },
73}
74
75impl OutputFormat {
76 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
159#[serde(rename_all = "snake_case")]
160pub enum PdfEngine {
161 #[default]
163 Latex,
164
165 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 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}