1use std::collections::BTreeMap;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum Target {
12 Docx,
13 Odt,
14 Pdf,
15 PdfPresentation,
16 Pptx,
17 HtmlReveal,
18}
19
20impl Target {
21 pub fn as_str(&self) -> &'static str {
22 match self {
23 Target::Docx => "docx",
24 Target::Odt => "odt",
25 Target::Pdf => "pdf",
26 Target::PdfPresentation => "pdf-presentation",
27 Target::Pptx => "pptx",
28 Target::HtmlReveal => "html-reveal",
29 }
30 }
31
32 pub fn extension(&self) -> &'static str {
34 match self {
35 Target::Docx => "docx",
36 Target::Odt => "odt",
37 Target::Pdf | Target::PdfPresentation => "pdf",
38 Target::Pptx => "pptx",
39 Target::HtmlReveal => "html",
40 }
41 }
42}
43
44#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
46pub struct DocMeta {
47 pub title: Option<String>,
48 pub author: Option<String>,
49 pub date: Option<String>,
50 #[serde(default)]
51 pub extra: BTreeMap<String, String>,
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn target_serializes_as_its_kebab_case_str() {
60 for target in [
61 Target::Docx,
62 Target::Odt,
63 Target::Pdf,
64 Target::PdfPresentation,
65 Target::Pptx,
66 Target::HtmlReveal,
67 ] {
68 let json = serde_json::to_string(&target).unwrap();
69 assert_eq!(json, format!("\"{}\"", target.as_str()));
70 assert_eq!(serde_json::from_str::<Target>(&json).unwrap(), target);
71 }
72 }
73
74 #[test]
75 fn target_extension_collapses_both_pdf_targets() {
76 assert_eq!(Target::Pdf.extension(), "pdf");
77 assert_eq!(Target::PdfPresentation.extension(), "pdf");
78 assert_eq!(Target::HtmlReveal.extension(), "html");
79 }
80
81 #[test]
82 fn doc_meta_round_trips_through_json() {
83 let meta = DocMeta {
84 title: Some("Q3 Review".to_string()),
85 author: Some("Martin".to_string()),
86 date: None,
87 extra: BTreeMap::from([("subtitle".to_string(), "Board copy".to_string())]),
88 };
89
90 let json = serde_json::to_string(&meta).unwrap();
91 assert_eq!(serde_json::from_str::<DocMeta>(&json).unwrap(), meta);
92 }
93
94 #[test]
95 fn doc_meta_defaults_extra_when_absent() {
96 let meta: DocMeta = serde_json::from_str(r#"{"title":"T","author":null,"date":null}"#)
97 .expect("extra is optional");
98 assert!(meta.extra.is_empty());
99 }
100}