1use crate::base62::encode_base62;
8use crate::error::Result;
9use crate::schema::TypeHint;
10use crate::types::{DxArray, DxObject, DxTable, DxValue};
11use rustc_hash::FxHashMap;
12use std::io::Write;
13
14#[derive(Debug, Clone)]
16pub struct EncoderConfig {
17 pub use_aliases: bool,
19 pub use_ditto: bool,
21 pub alias_min_length: usize,
23 pub pretty: bool,
25}
26
27impl Default for EncoderConfig {
28 fn default() -> Self {
29 Self {
30 use_aliases: true,
31 use_ditto: true,
32 alias_min_length: 6,
33 pretty: false,
34 }
35 }
36}
37
38pub struct Encoder {
40 config: EncoderConfig,
41 aliases: FxHashMap<String, String>,
43 next_alias: usize,
45 prev_row: Option<Vec<DxValue>>,
47}
48
49impl Encoder {
50 pub fn new(config: EncoderConfig) -> Self {
52 Self {
53 config,
54 aliases: FxHashMap::default(),
55 next_alias: 0,
56 prev_row: None,
57 }
58 }
59
60 pub fn with_defaults() -> Self {
62 Self::new(EncoderConfig::default())
63 }
64
65 pub fn encode(&mut self, value: &DxValue) -> Result<Vec<u8>> {
67 let mut output = Vec::new();
68 self.encode_to_writer(value, &mut output)?;
69 Ok(output)
70 }
71
72 pub fn encode_to_writer<W: Write>(&mut self, value: &DxValue, writer: &mut W) -> Result<()> {
74 match value {
75 DxValue::Object(obj) => self.encode_object(obj, writer, ""),
76 _ => {
77 let mut obj = DxObject::new();
79 obj.insert("value".to_string(), value.clone());
80 self.encode_object(&obj, writer, "")
81 }
82 }
83 }
84
85 fn encode_object<W: Write>(
87 &mut self,
88 obj: &DxObject,
89 writer: &mut W,
90 prefix: &str,
91 ) -> Result<()> {
92 if self.config.use_aliases {
94 self.generate_aliases(obj);
95 }
96
97 for (alias, full_key) in &self.aliases {
99 writeln!(writer, "${}={}", alias, full_key)?;
100 }
101
102 for (key, value) in obj.iter() {
104 let full_key = if prefix.is_empty() {
105 key.clone()
106 } else {
107 format!("{}.{}", prefix, key)
108 };
109
110 self.encode_field(&full_key, value, writer)?;
111 }
112
113 Ok(())
114 }
115
116 fn generate_aliases(&mut self, obj: &DxObject) {
118 let mut key_freq: FxHashMap<String, usize> = FxHashMap::default();
119
120 for (key, _) in obj.iter() {
121 if key.len() >= self.config.alias_min_length {
122 *key_freq.entry(key.clone()).or_insert(0) += 1;
123 }
124 }
125
126 for (key, _count) in key_freq.iter() {
128 if !self.aliases.contains_key(key) {
129 let alias = format!("k{}", self.next_alias);
130 self.next_alias += 1;
131 self.aliases.insert(alias, key.clone());
132 }
133 }
134 }
135
136 fn encode_field<W: Write>(&mut self, key: &str, value: &DxValue, writer: &mut W) -> Result<()> {
138 let key_to_write = self
140 .aliases
141 .iter()
142 .find(|(_, v)| v.as_str() == key)
143 .map(|(k, _)| format!("${}", k))
144 .unwrap_or_else(|| key.to_string());
145
146 match value {
147 DxValue::Table(table) => {
148 write!(writer, "{}=", key_to_write)?;
150 self.encode_table(table, writer)?;
151 }
152 DxValue::Array(arr) if arr.is_stream => {
153 write!(writer, "{}> ", key_to_write)?;
155 self.encode_stream_array(arr, writer)?;
156 writeln!(writer)?;
157 }
158 DxValue::Bool(true) => {
159 writeln!(writer, "{}!", key_to_write)?;
161 }
162 DxValue::Null => {
163 writeln!(writer, "{}?", key_to_write)?;
165 }
166 _ => {
167 write!(writer, "{}:", key_to_write)?;
169 self.encode_value(value, writer)?;
170 writeln!(writer)?;
171 }
172 }
173
174 Ok(())
175 }
176
177 fn encode_table<W: Write>(&mut self, table: &DxTable, writer: &mut W) -> Result<()> {
179 for (i, col) in table.schema.columns.iter().enumerate() {
181 if i > 0 {
182 write!(writer, " ")?;
183 }
184 write!(writer, "{}", col.name)?;
185 if col.type_hint != TypeHint::Auto {
186 write!(writer, "%{}", col.type_hint.to_byte() as char)?;
187 }
188 }
189 writeln!(writer)?;
190
191 self.prev_row = None;
193 for row in &table.rows {
194 self.encode_row(row, &table.schema, writer)?;
195 self.prev_row = Some(row.clone());
196 }
197
198 Ok(())
199 }
200
201 fn encode_row<W: Write>(
203 &mut self,
204 row: &[DxValue],
205 schema: &crate::schema::Schema,
206 writer: &mut W,
207 ) -> Result<()> {
208 for (i, value) in row.iter().enumerate() {
209 if i < schema.columns.len() && schema.columns[i].type_hint == TypeHint::AutoIncrement {
211 continue;
212 }
213
214 if i > 0 {
215 write!(writer, " ")?;
216 }
217
218 if self.config.use_ditto {
220 if let Some(prev) = &self.prev_row {
221 if i < prev.len() && &prev[i] == value {
222 write!(writer, "_")?;
223 continue;
224 }
225 }
226 }
227
228 if i < schema.columns.len() && schema.columns[i].type_hint == TypeHint::Base62 {
230 if let DxValue::Int(n) = value {
231 if *n >= 0 {
232 write!(writer, "{}", encode_base62(*n as u64))?;
233 continue;
234 }
235 }
236 }
237
238 self.encode_value(value, writer)?;
239 }
240 writeln!(writer)?;
241
242 Ok(())
243 }
244
245 fn encode_stream_array<W: Write>(&mut self, arr: &DxArray, writer: &mut W) -> Result<()> {
247 for (i, value) in arr.values.iter().enumerate() {
248 if i > 0 {
249 write!(writer, "|")?;
250 }
251 self.encode_value(value, writer)?;
252 }
253 Ok(())
254 }
255
256 fn encode_value<W: Write>(&mut self, value: &DxValue, writer: &mut W) -> Result<()> {
258 match value {
259 DxValue::Null => write!(writer, "~")?,
260 DxValue::Bool(true) => write!(writer, "+")?,
261 DxValue::Bool(false) => write!(writer, "-")?,
262 DxValue::Int(i) => write!(writer, "{}", i)?,
263 DxValue::Float(f) => write!(writer, "{}", f)?,
264 DxValue::String(s) => {
265 write!(writer, "{}", s)?;
267 }
268 DxValue::Array(arr) => {
269 if arr.is_stream {
270 self.encode_stream_array(arr, writer)?;
271 } else {
272 write!(writer, "[]")?;
274 }
275 }
276 DxValue::Object(_) => write!(writer, "{{}}")?,
277 DxValue::Table(_) => write!(writer, "[[]]")?,
278 DxValue::Ref(id) => write!(writer, "@{}", id)?,
279 }
280 Ok(())
281 }
282}
283
284#[must_use = "encoding result should be used"]
318pub fn encode(value: &DxValue) -> Result<Vec<u8>> {
319 let mut encoder = Encoder::with_defaults();
320 encoder.encode(value)
321}
322
323pub fn encode_to_writer<W: Write>(value: &DxValue, writer: &mut W) -> Result<()> {
352 let mut encoder = Encoder::with_defaults();
353 encoder.encode_to_writer(value, writer)
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::parser::parse;
360
361 #[test]
362 fn test_encode_simple() {
363 let mut obj = DxObject::new();
364 obj.insert("name".to_string(), DxValue::String("Alice".to_string()));
365 obj.insert("age".to_string(), DxValue::Int(30));
366 obj.insert("active".to_string(), DxValue::Bool(true));
367
368 let value = DxValue::Object(obj);
369 let encoded = encode(&value).unwrap();
370 let encoded_str = std::str::from_utf8(&encoded).unwrap();
371
372 assert!(encoded_str.contains("name:Alice"));
373 assert!(encoded_str.contains("age:30"));
374 assert!(
377 encoded_str.contains("!"),
378 "Expected boolean true to be encoded with !"
379 );
380 }
381
382 #[test]
383 fn test_round_trip() {
384 let input = b"name:Test
386score:9.5
387ok:+";
388
389 let parsed = parse(input).unwrap();
390 let encoded = encode(&parsed).unwrap();
391 let reparsed = parse(&encoded).unwrap();
392
393 assert_eq!(parsed, reparsed);
394 }
395}