Skip to main content

lex_extension/wire/
format.rs

1//! Target-format identifier used by the render hook.
2//!
3//! Built-in formats (`html`, `latex`, `markdown`, `pdf`) are exhaustively
4//! enumerated for ergonomic match arms. Namespace-defined formats slip
5//! through as [`Format::Custom`].
6
7use std::convert::Infallible;
8use std::str::FromStr;
9
10use serde::{Deserialize, Serialize};
11
12/// A render-target format. Wire form is the lowercase string name.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub enum Format {
15    Html,
16    Latex,
17    Markdown,
18    Pdf,
19    /// Any namespace-defined format string (e.g., `"plasma-spec-v4"`,
20    /// `"docx"`). Compared case-sensitively.
21    Custom(String),
22}
23
24impl Format {
25    /// Wire string for this format.
26    pub fn as_str(&self) -> &str {
27        match self {
28            Format::Html => "html",
29            Format::Latex => "latex",
30            Format::Markdown => "markdown",
31            Format::Pdf => "pdf",
32            Format::Custom(s) => s.as_str(),
33        }
34    }
35}
36
37impl FromStr for Format {
38    type Err = Infallible;
39
40    /// Unknown strings become [`Format::Custom`]; parsing is infallible.
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        Ok(match s {
43            "html" => Format::Html,
44            "latex" => Format::Latex,
45            "markdown" => Format::Markdown,
46            "pdf" => Format::Pdf,
47            other => Format::Custom(other.to_string()),
48        })
49    }
50}
51
52impl std::fmt::Display for Format {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.write_str(self.as_str())
55    }
56}
57
58impl Serialize for Format {
59    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
60    where
61        S: serde::Serializer,
62    {
63        serializer.serialize_str(self.as_str())
64    }
65}
66
67impl<'de> Deserialize<'de> for Format {
68    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69    where
70        D: serde::Deserializer<'de>,
71    {
72        let s = String::deserialize(deserializer)?;
73        Ok(s.parse().expect("Format::from_str is infallible"))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn html_round_trips() {
83        let f = Format::Html;
84        let s = serde_json::to_string(&f).unwrap();
85        assert_eq!(s, r#""html""#);
86        let back: Format = serde_json::from_str(&s).unwrap();
87        assert_eq!(back, f);
88    }
89
90    #[test]
91    fn unknown_becomes_custom() {
92        let s = r#""plasma-spec-v4""#;
93        let f: Format = serde_json::from_str(s).unwrap();
94        assert_eq!(f, Format::Custom("plasma-spec-v4".to_string()));
95        assert_eq!(serde_json::to_string(&f).unwrap(), s);
96    }
97
98    #[test]
99    fn from_str_via_trait() {
100        let f: Format = "html".parse().unwrap();
101        assert_eq!(f, Format::Html);
102        let f: Format = "docx".parse().unwrap();
103        assert_eq!(f, Format::Custom("docx".to_string()));
104    }
105
106    #[test]
107    fn display_uses_wire_string() {
108        assert_eq!(Format::Html.to_string(), "html");
109        assert_eq!(Format::Custom("docx".to_string()).to_string(), "docx");
110    }
111}