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 #[serde(default)]
94 pub fonts: Vec<String>,
95}
96
97#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "kebab-case")]
102pub enum TemplateFormat {
103 #[default]
104 Pdf,
105 Html,
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::digest::Digest;
112
113 fn manifest() -> AssetManifest {
114 AssetManifest::from_iter([("img/logo.png".to_string(), Digest::from_sha256([7; 32]))])
115 }
116
117 #[test]
118 fn markdown_request_round_trips_through_json() {
119 let req = RenderMarkdownRequest {
120 markdown: "# Hi".to_string(),
121 meta: DocMeta {
122 title: Some("Hi".to_string()),
123 ..DocMeta::default()
124 },
125 brand: Some(BrandSpec::default()),
126 target: Target::HtmlReveal,
127 assets: manifest(),
128 options: RenderOptions {
129 html_image_tags: true,
130 mermaid: true,
131 toc_depth: Some(3),
132 layout_assets: vec!["img/logo.png".to_string()],
133 layout_fonts: vec!["fonts/Brand-Regular.ttf".to_string()],
134 },
135 };
136
137 let json = serde_json::to_string(&req).unwrap();
138 assert_eq!(
139 serde_json::from_str::<RenderMarkdownRequest>(&json).unwrap(),
140 req
141 );
142 }
143
144 #[test]
145 fn markdown_request_needs_only_markdown_and_target() {
146 let req: RenderMarkdownRequest =
147 serde_json::from_str(r##"{"markdown":"# Hi","target":"pdf-presentation"}"##)
148 .expect("every other field defaults");
149
150 assert_eq!(req.target, Target::PdfPresentation);
151 assert_eq!(req.meta, DocMeta::default());
152 assert_eq!(req.brand, None);
153 assert!(req.assets.is_empty());
154 assert_eq!(req.options, RenderOptions::default());
155 }
156
157 #[test]
158 fn template_request_round_trips_through_json() {
159 let req = RenderTemplateRequest {
160 template_source: "= Invoice #json(\"/data.json\").number".to_string(),
161 data: r#"{"number":"INV-042"}"#.to_string(),
162 meta: DocMeta::default(),
163 brand: None,
164 format: TemplateFormat::Html,
165 assets: manifest(),
166 fonts: vec!["fonts/Brand-Regular.ttf".to_string()],
167 };
168
169 let json = serde_json::to_string(&req).unwrap();
170 assert_eq!(
171 serde_json::from_str::<RenderTemplateRequest>(&json).unwrap(),
172 req
173 );
174 }
175
176 #[test]
177 fn template_format_defaults_to_pdf() {
178 let req: RenderTemplateRequest =
179 serde_json::from_str(r#"{"template_source":"= Hi","data":"{}"}"#)
180 .expect("format is optional");
181
182 assert_eq!(req.format, TemplateFormat::Pdf);
183 assert_eq!(serde_json::to_string(&req.format).unwrap(), "\"pdf\"");
184 assert!(
185 req.fonts.is_empty(),
186 "fonts default to none (0.4.1 wire compat)"
187 );
188 }
189}