mdcast_api/wire/capabilities.rs
1//! `GET /v1/capabilities` response body (issue #81) — deployment shape a
2//! client can use to feature-gate its UI before it has even wired up
3//! credentials, since the route sits outside the bearer gate.
4
5use serde::{Deserialize, Serialize};
6
7use crate::doc::Target;
8use crate::wire::render::TemplateFormat;
9
10/// What this `mdcast-server` deployment can actually render right now.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct CapabilitiesResponse {
13 /// Targets `POST /v1/render` can produce. The typst-backed entries
14 /// (`pdf`, `pdf-presentation`) are always present; the pandoc-backed ones
15 /// (`docx`, `odt`, `pptx`, `html-reveal`) appear only when the server
16 /// found a working `pandoc` binary at startup.
17 pub targets: Vec<Target>,
18 /// Formats `POST /v1/render/template` can produce. `pdf` is always
19 /// present; `html` appears only when this deployment can actually export
20 /// it — the `typst-html` build feature, or `MDCAST_TYPST=cli`.
21 pub template_formats: Vec<TemplateFormat>,
22 /// Echoes `MDCAST_MAX_UPLOAD_BYTES` — the ceiling on every request body,
23 /// not just uploads — so a client can reject an oversized bundle before
24 /// any round trip instead of discovering the limit via `413`.
25 pub max_upload_bytes: u64,
26 /// The running server's crate version (`CARGO_PKG_VERSION`).
27 pub version: String,
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn round_trips_through_json() {
36 let res = CapabilitiesResponse {
37 targets: vec![Target::Pdf, Target::PdfPresentation, Target::Docx],
38 template_formats: vec![TemplateFormat::Pdf, TemplateFormat::Html],
39 max_upload_bytes: 268_435_456,
40 version: "0.3.0".to_string(),
41 };
42
43 let json = serde_json::to_string(&res).unwrap();
44 assert_eq!(
45 serde_json::from_str::<CapabilitiesResponse>(&json).unwrap(),
46 res
47 );
48 }
49
50 #[test]
51 fn targets_serialize_as_their_kebab_case_strings() {
52 let res = CapabilitiesResponse {
53 targets: vec![Target::HtmlReveal],
54 template_formats: vec![],
55 max_upload_bytes: 0,
56 version: String::new(),
57 };
58
59 let json = serde_json::to_value(&res).unwrap();
60 assert_eq!(json["targets"], serde_json::json!(["html-reveal"]));
61 }
62}