Skip to main content

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}