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}