use serde::{Deserialize, Serialize};
use crate::brand::BrandSpec;
use crate::doc::{DocMeta, Target};
use crate::manifest::AssetManifest;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RenderMarkdownRequest {
pub markdown: String,
#[serde(default)]
pub meta: DocMeta,
#[serde(default)]
pub brand: Option<BrandSpec>,
pub target: Target,
#[serde(default)]
pub assets: AssetManifest,
#[serde(default)]
pub options: RenderOptions,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RenderOptions {
#[serde(default)]
pub html_image_tags: bool,
#[serde(default)]
pub mermaid: bool,
#[serde(default)]
pub toc_depth: Option<u8>,
#[serde(default)]
pub layout_assets: Vec<String>,
#[serde(default)]
pub layout_fonts: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RenderTemplateRequest {
pub template_source: String,
pub data: String,
#[serde(default)]
pub meta: DocMeta,
#[serde(default)]
pub brand: Option<BrandSpec>,
#[serde(default)]
pub format: TemplateFormat,
#[serde(default)]
pub assets: AssetManifest,
#[serde(default)]
pub fonts: Vec<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TemplateFormat {
#[default]
Pdf,
Html,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::digest::Digest;
fn manifest() -> AssetManifest {
AssetManifest::from_iter([("img/logo.png".to_string(), Digest::from_sha256([7; 32]))])
}
#[test]
fn markdown_request_round_trips_through_json() {
let req = RenderMarkdownRequest {
markdown: "# Hi".to_string(),
meta: DocMeta {
title: Some("Hi".to_string()),
..DocMeta::default()
},
brand: Some(BrandSpec::default()),
target: Target::HtmlReveal,
assets: manifest(),
options: RenderOptions {
html_image_tags: true,
mermaid: true,
toc_depth: Some(3),
layout_assets: vec!["img/logo.png".to_string()],
layout_fonts: vec!["fonts/Brand-Regular.ttf".to_string()],
},
};
let json = serde_json::to_string(&req).unwrap();
assert_eq!(
serde_json::from_str::<RenderMarkdownRequest>(&json).unwrap(),
req
);
}
#[test]
fn markdown_request_needs_only_markdown_and_target() {
let req: RenderMarkdownRequest =
serde_json::from_str(r##"{"markdown":"# Hi","target":"pdf-presentation"}"##)
.expect("every other field defaults");
assert_eq!(req.target, Target::PdfPresentation);
assert_eq!(req.meta, DocMeta::default());
assert_eq!(req.brand, None);
assert!(req.assets.is_empty());
assert_eq!(req.options, RenderOptions::default());
}
#[test]
fn template_request_round_trips_through_json() {
let req = RenderTemplateRequest {
template_source: "= Invoice #json(\"/data.json\").number".to_string(),
data: r#"{"number":"INV-042"}"#.to_string(),
meta: DocMeta::default(),
brand: None,
format: TemplateFormat::Html,
assets: manifest(),
fonts: vec!["fonts/Brand-Regular.ttf".to_string()],
};
let json = serde_json::to_string(&req).unwrap();
assert_eq!(
serde_json::from_str::<RenderTemplateRequest>(&json).unwrap(),
req
);
}
#[test]
fn template_format_defaults_to_pdf() {
let req: RenderTemplateRequest =
serde_json::from_str(r#"{"template_source":"= Hi","data":"{}"}"#)
.expect("format is optional");
assert_eq!(req.format, TemplateFormat::Pdf);
assert_eq!(serde_json::to_string(&req.format).unwrap(), "\"pdf\"");
assert!(
req.fonts.is_empty(),
"fonts default to none (0.4.1 wire compat)"
);
}
}