Skip to main content

lex_babel/
format.rs

1//! Format trait definition
2//!
3//! This module defines the core Format trait that all format implementations must implement.
4//! The trait provides a uniform interface for parsing and serializing documents.
5
6use crate::error::FormatError;
7use lex_core::lex::ast::Document;
8use std::collections::HashMap;
9
10/// Serialized output produced by a [`Format`] implementation.
11pub enum SerializedDocument {
12    /// UTF-8 text output (e.g., lex, markdown, HTML)
13    Text(String),
14    /// Binary output (e.g., PDF)
15    Binary(Vec<u8>),
16}
17
18impl SerializedDocument {
19    /// Consume the serialized output and return the underlying bytes.
20    pub fn into_bytes(self) -> Vec<u8> {
21        match self {
22            SerializedDocument::Text(text) => text.into_bytes(),
23            SerializedDocument::Binary(bytes) => bytes,
24        }
25    }
26}
27
28/// Trait for document formats
29///
30/// Implementors provide bidirectional conversion between string representation and Document AST.
31/// Formats can support parsing, serialization, or both.
32///
33/// # Round-trip discipline
34///
35/// Implementing **both** `parse` and `serialize` carries an implicit
36/// expectation: the format participates in lex-babel's round-trip
37/// test suite, and regressions in either direction are release
38/// blockers. If a format only makes sense in one direction (e.g.
39/// HTML export with no importer yet, or RFC XML import with no
40/// serializer), implement just that direction — don't add a
41/// half-baked counterpart and inherit the round-trip obligation.
42/// See the v1 interop tiering for which formats actually carry this
43/// expectation — `comms/docs/interop-scope.lex` in-repo, or
44/// <https://github.com/lex-fmt/comms/blob/main/docs/interop-scope.lex>
45/// for readers on docs.rs (the `comms` directory is a git submodule
46/// and won't be in published crate sources).
47///
48/// # Examples
49///
50/// ```ignore
51/// struct MyFormat;
52///
53/// impl Format for MyFormat {
54///     fn name(&self) -> &str {
55///         "my-format"
56///     }
57///
58///     fn supports_parsing(&self) -> bool {
59///         true
60///     }
61///
62///     fn supports_serialization(&self) -> bool {
63///         true
64///     }
65///
66///     fn parse(&self, source: &str) -> Result<Document, FormatError> {
67///         // Parse source to Document
68///         todo!()
69///     }
70///
71///     fn serialize(&self, doc: &Document) -> Result<String, FormatError> {
72///         // Serialize Document to string
73///         todo!()
74///     }
75/// }
76/// ```
77pub trait Format: Send + Sync {
78    /// The name of this format (e.g., "lex", "markdown", "html")
79    fn name(&self) -> &str;
80
81    /// Optional description of this format
82    fn description(&self) -> &str {
83        ""
84    }
85
86    /// File extensions associated with this format (e.g., ["lex"], ["md", "markdown"])
87    ///
88    /// Returns a slice of file extensions without the leading dot.
89    /// Used for automatic format detection from filenames.
90    fn file_extensions(&self) -> &[&str] {
91        &[]
92    }
93
94    /// Whether this format supports parsing (source → Document)
95    fn supports_parsing(&self) -> bool {
96        false
97    }
98
99    /// Whether this format supports serialization (Document → source)
100    fn supports_serialization(&self) -> bool {
101        false
102    }
103
104    /// Parse source text into a Document
105    ///
106    /// Default implementation returns NotSupported error.
107    /// Formats that support parsing should override this method.
108    fn parse(&self, _source: &str) -> Result<Document, FormatError> {
109        Err(FormatError::NotSupported(format!(
110            "Format '{}' does not support parsing",
111            self.name()
112        )))
113    }
114
115    /// Serialize a Document into source text
116    ///
117    /// Default implementation returns NotSupported error.
118    /// Formats that support serialization should override this method.
119    fn serialize(&self, _doc: &Document) -> Result<String, FormatError> {
120        Err(FormatError::NotSupported(format!(
121            "Format '{}' does not support serialization",
122            self.name()
123        )))
124    }
125
126    /// Serialize a Document, optionally using extra parameters.
127    ///
128    /// Formats that only emit textual output can rely on the default implementation,
129    /// which delegates to [`Format::serialize`]. Binary formats should override this
130    /// method to return [`SerializedDocument::Binary`].
131    fn serialize_with_options(
132        &self,
133        doc: &Document,
134        options: &HashMap<String, String>,
135    ) -> Result<SerializedDocument, FormatError> {
136        if options.is_empty() {
137            self.serialize(doc).map(SerializedDocument::Text)
138        } else {
139            Err(FormatError::NotSupported(format!(
140                "Format '{}' does not support extra parameters",
141                self.name()
142            )))
143        }
144    }
145}