mdcast_api/wire/
render.rs1use serde::{Deserialize, Serialize};
6
7use crate::brand::BrandSpec;
8use crate::doc::{DocMeta, Target};
9use crate::manifest::AssetManifest;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct RenderMarkdownRequest {
17 pub markdown: String,
21 #[serde(default)]
22 pub meta: DocMeta,
23 #[serde(default)]
26 pub brand: Option<BrandSpec>,
27 pub target: Target,
28 #[serde(default)]
33 pub assets: AssetManifest,
34 #[serde(default)]
35 pub options: RenderOptions,
36}
37
38#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct RenderOptions {
42 #[serde(default)]
45 pub html_image_tags: bool,
46 #[serde(default)]
49 pub mermaid: bool,
50 #[serde(default)]
52 pub toc_depth: Option<u8>,
53 #[serde(default)]
56 pub layout_assets: Vec<String>,
57 #[serde(default)]
60 pub layout_fonts: Vec<String>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct RenderTemplateRequest {
68 pub template_source: String,
73 pub data: String,
78 #[serde(default)]
79 pub meta: DocMeta,
80 #[serde(default)]
81 pub brand: Option<BrandSpec>,
82 #[serde(default)]
83 pub format: TemplateFormat,
84 #[serde(default)]
87 pub assets: AssetManifest,
88}
89
90#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "kebab-case")]
95pub enum TemplateFormat {
96 #[default]
97 Pdf,
98 Html,
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::digest::Digest;
105
106 fn manifest() -> AssetManifest {
107 AssetManifest::from_iter([("img/logo.png".to_string(), Digest::from_sha256([7; 32]))])
108 }
109
110 #[test]
111 fn markdown_request_round_trips_through_json() {
112 let req = RenderMarkdownRequest {
113 markdown: "# Hi".to_string(),
114 meta: DocMeta {
115 title: Some("Hi".to_string()),
116 ..DocMeta::default()
117 },
118 brand: Some(BrandSpec::default()),
119 target: Target::HtmlReveal,
120 assets: manifest(),
121 options: RenderOptions {
122 html_image_tags: true,
123 mermaid: true,
124 toc_depth: Some(3),
125 layout_assets: vec!["img/logo.png".to_string()],
126 layout_fonts: vec!["fonts/Brand-Regular.ttf".to_string()],
127 },
128 };
129
130 let json = serde_json::to_string(&req).unwrap();
131 assert_eq!(
132 serde_json::from_str::<RenderMarkdownRequest>(&json).unwrap(),
133 req
134 );
135 }
136
137 #[test]
138 fn markdown_request_needs_only_markdown_and_target() {
139 let req: RenderMarkdownRequest =
140 serde_json::from_str(r##"{"markdown":"# Hi","target":"pdf-presentation"}"##)
141 .expect("every other field defaults");
142
143 assert_eq!(req.target, Target::PdfPresentation);
144 assert_eq!(req.meta, DocMeta::default());
145 assert_eq!(req.brand, None);
146 assert!(req.assets.is_empty());
147 assert_eq!(req.options, RenderOptions::default());
148 }
149
150 #[test]
151 fn template_request_round_trips_through_json() {
152 let req = RenderTemplateRequest {
153 template_source: "= Invoice #json(\"/data.json\").number".to_string(),
154 data: r#"{"number":"INV-042"}"#.to_string(),
155 meta: DocMeta::default(),
156 brand: None,
157 format: TemplateFormat::Html,
158 assets: manifest(),
159 };
160
161 let json = serde_json::to_string(&req).unwrap();
162 assert_eq!(
163 serde_json::from_str::<RenderTemplateRequest>(&json).unwrap(),
164 req
165 );
166 }
167
168 #[test]
169 fn template_format_defaults_to_pdf() {
170 let req: RenderTemplateRequest =
171 serde_json::from_str(r#"{"template_source":"= Hi","data":"{}"}"#)
172 .expect("format is optional");
173
174 assert_eq!(req.format, TemplateFormat::Pdf);
175 assert_eq!(serde_json::to_string(&req.format).unwrap(), "\"pdf\"");
176 }
177}