Skip to main content

sqry_core/schema/
format.rs

1//! Canonical output format enumeration.
2//!
3//! Defines output formats for graph exports and visualizations.
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// Output formats for graph exports and visualizations.
9///
10/// Used by `export_graph` and visualization tools to specify
11/// the desired output format.
12///
13/// # Serialization
14///
15/// All variants serialize to lowercase: `"json"`, `"dot"`, etc.
16///
17/// # Examples
18///
19/// ```
20/// use sqry_core::schema::OutputFormat;
21///
22/// let fmt = OutputFormat::Mermaid;
23/// assert_eq!(fmt.as_str(), "mermaid");
24///
25/// let parsed = OutputFormat::parse("dot").unwrap();
26/// assert_eq!(parsed, OutputFormat::Dot);
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30#[derive(Default)]
31pub enum OutputFormat {
32    /// JSON format (default).
33    ///
34    /// Structured JSON with nodes, edges, and metadata.
35    /// Best for programmatic consumption and further processing.
36    #[default]
37    Json,
38
39    /// Graphviz DOT format.
40    ///
41    /// Standard graph description language for Graphviz tools.
42    /// Render with: `dot -Tpng graph.dot -o graph.png`
43    Dot,
44
45    /// D2 diagram format.
46    ///
47    /// Modern declarative diagramming language.
48    /// Render with: `d2 graph.d2 graph.svg`
49    D2,
50
51    /// Mermaid diagram format.
52    ///
53    /// Markdown-friendly diagram syntax.
54    /// Renders in GitHub, GitLab, and many documentation tools.
55    Mermaid,
56}
57
58impl OutputFormat {
59    /// Returns all variants in definition order.
60    #[must_use]
61    pub const fn all() -> &'static [Self] {
62        &[Self::Json, Self::Dot, Self::D2, Self::Mermaid]
63    }
64
65    /// Returns the canonical string representation.
66    #[must_use]
67    pub const fn as_str(self) -> &'static str {
68        match self {
69            Self::Json => "json",
70            Self::Dot => "dot",
71            Self::D2 => "d2",
72            Self::Mermaid => "mermaid",
73        }
74    }
75
76    /// Returns the typical file extension for this format.
77    #[must_use]
78    pub const fn file_extension(self) -> &'static str {
79        match self {
80            Self::Json => "json",
81            Self::Dot => "dot",
82            Self::D2 => "d2",
83            Self::Mermaid => "mmd",
84        }
85    }
86
87    /// Parses a string into an `OutputFormat`.
88    ///
89    /// Returns `None` if the string doesn't match any known format.
90    /// Case-insensitive.
91    #[must_use]
92    pub fn parse(s: &str) -> Option<Self> {
93        match s.to_lowercase().as_str() {
94            "json" => Some(Self::Json),
95            "dot" | "graphviz" => Some(Self::Dot),
96            "d2" => Some(Self::D2),
97            "mermaid" | "mmd" => Some(Self::Mermaid),
98            _ => None,
99        }
100    }
101
102    /// Returns `true` if this format produces text output.
103    ///
104    /// All current formats are text-based; this is for future
105    /// binary format support (e.g., PNG, SVG).
106    #[must_use]
107    pub const fn is_text_format(self) -> bool {
108        true
109    }
110
111    /// Returns `true` if this format is a diagram/visualization format.
112    #[must_use]
113    pub const fn is_diagram_format(self) -> bool {
114        matches!(self, Self::Dot | Self::D2 | Self::Mermaid)
115    }
116}
117
118impl fmt::Display for OutputFormat {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_str(self.as_str())
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_as_str() {
130        assert_eq!(OutputFormat::Json.as_str(), "json");
131        assert_eq!(OutputFormat::Dot.as_str(), "dot");
132        assert_eq!(OutputFormat::D2.as_str(), "d2");
133        assert_eq!(OutputFormat::Mermaid.as_str(), "mermaid");
134    }
135
136    #[test]
137    fn test_file_extension() {
138        assert_eq!(OutputFormat::Json.file_extension(), "json");
139        assert_eq!(OutputFormat::Dot.file_extension(), "dot");
140        assert_eq!(OutputFormat::D2.file_extension(), "d2");
141        assert_eq!(OutputFormat::Mermaid.file_extension(), "mmd");
142    }
143
144    #[test]
145    fn test_parse() {
146        assert_eq!(OutputFormat::parse("json"), Some(OutputFormat::Json));
147        assert_eq!(OutputFormat::parse("DOT"), Some(OutputFormat::Dot));
148        assert_eq!(OutputFormat::parse("graphviz"), Some(OutputFormat::Dot));
149        assert_eq!(OutputFormat::parse("mermaid"), Some(OutputFormat::Mermaid));
150        assert_eq!(OutputFormat::parse("mmd"), Some(OutputFormat::Mermaid));
151        assert_eq!(OutputFormat::parse("unknown"), None);
152    }
153
154    #[test]
155    fn test_display() {
156        assert_eq!(format!("{}", OutputFormat::Json), "json");
157        assert_eq!(format!("{}", OutputFormat::Mermaid), "mermaid");
158    }
159
160    #[test]
161    fn test_serde_roundtrip() {
162        for fmt in OutputFormat::all() {
163            let json = serde_json::to_string(fmt).unwrap();
164            let deserialized: OutputFormat = serde_json::from_str(&json).unwrap();
165            assert_eq!(*fmt, deserialized);
166        }
167    }
168
169    #[test]
170    fn test_classification() {
171        assert!(OutputFormat::Json.is_text_format());
172        assert!(!OutputFormat::Json.is_diagram_format());
173        assert!(OutputFormat::Dot.is_diagram_format());
174        assert!(OutputFormat::Mermaid.is_diagram_format());
175    }
176
177    #[test]
178    fn test_default() {
179        assert_eq!(OutputFormat::default(), OutputFormat::Json);
180    }
181}