1use std::io::Write;
7
8use crate::error::Result;
9use crate::models::{Column, Row, Value};
10
11use super::formatter::Formatter;
12
13pub struct TsvFormatter {
18 _row_count: usize,
20}
21
22impl TsvFormatter {
23 pub fn new() -> Self {
25 Self { _row_count: 0 }
26 }
27}
28
29impl Default for TsvFormatter {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl Formatter for TsvFormatter {
36 fn write_header(&mut self, writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
37 let header: Vec<String> = columns.iter().map(|c| escape_tsv(&c.name)).collect();
38 writeln!(writer, "{}", header.join("\t"))?;
39 Ok(())
40 }
41
42 fn write_row(&mut self, writer: &mut dyn Write, row: &Row) -> Result<()> {
43 let values: Vec<String> = row.columns.iter().map(format_value_tsv).collect();
44 writeln!(writer, "{}", values.join("\t"))?;
45 self._row_count += 1;
46 Ok(())
47 }
48
49 fn write_footer(&mut self, _writer: &mut dyn Write) -> Result<()> {
50 Ok(())
52 }
53
54 fn supports_streaming(&self) -> bool {
55 true
56 }
57}
58
59fn format_value_tsv(value: &Value) -> String {
61 match value {
62 Value::Null => String::new(),
63 Value::Bool(b) => b.to_string(),
64 Value::Int(i) => i.to_string(),
65 Value::Float(f) => f.to_string(),
66 Value::Text(s) => escape_tsv(s),
67 Value::Bytes(b) => {
68 escape_tsv(
70 &b.iter()
71 .map(|byte| format!("{:02x}", byte))
72 .collect::<String>(),
73 )
74 }
75 Value::Vector(v) => {
76 let json = serde_json::to_string(v).unwrap_or_default();
78 escape_tsv(&json)
79 }
80 }
81}
82
83fn escape_tsv(s: &str) -> String {
87 s.replace('\\', "\\\\")
88 .replace('\t', "\\t")
89 .replace('\n', "\\n")
90 .replace('\r', "\\r")
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::models::DataType;
97
98 fn test_columns() -> Vec<Column> {
99 vec![
100 Column::new("id", DataType::Int),
101 Column::new("name", DataType::Text),
102 ]
103 }
104
105 #[test]
106 fn test_tsv_basic() {
107 let mut formatter = TsvFormatter::new();
108 let mut output = Vec::new();
109
110 let columns = test_columns();
111 formatter.write_header(&mut output, &columns).unwrap();
112
113 let row = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
114 formatter.write_row(&mut output, &row).unwrap();
115
116 formatter.write_footer(&mut output).unwrap();
117
118 let result = String::from_utf8(output).unwrap();
119 assert_eq!(result, "id\tname\n1\tAlice\n");
120 }
121
122 #[test]
123 fn test_tsv_escape_tab() {
124 let mut formatter = TsvFormatter::new();
125 let mut output = Vec::new();
126
127 let columns = test_columns();
128 formatter.write_header(&mut output, &columns).unwrap();
129
130 let row = Row::new(vec![Value::Int(1), Value::Text("Alice\tBob".to_string())]);
131 formatter.write_row(&mut output, &row).unwrap();
132
133 let result = String::from_utf8(output).unwrap();
134 assert!(result.contains("Alice\\tBob"));
135 }
136
137 #[test]
138 fn test_tsv_escape_newline() {
139 let mut formatter = TsvFormatter::new();
140 let mut output = Vec::new();
141
142 let columns = test_columns();
143 formatter.write_header(&mut output, &columns).unwrap();
144
145 let row = Row::new(vec![Value::Int(1), Value::Text("Line1\nLine2".to_string())]);
146 formatter.write_row(&mut output, &row).unwrap();
147
148 let result = String::from_utf8(output).unwrap();
149 assert!(result.contains("Line1\\nLine2"));
150 }
151
152 #[test]
153 fn test_tsv_escape_backslash() {
154 let mut formatter = TsvFormatter::new();
155 let mut output = Vec::new();
156
157 let columns = test_columns();
158 formatter.write_header(&mut output, &columns).unwrap();
159
160 let row = Row::new(vec![
161 Value::Int(1),
162 Value::Text("path\\to\\file".to_string()),
163 ]);
164 formatter.write_row(&mut output, &row).unwrap();
165
166 let result = String::from_utf8(output).unwrap();
167 assert!(result.contains("path\\\\to\\\\file"));
168 }
169
170 #[test]
171 fn test_tsv_null_value() {
172 let mut formatter = TsvFormatter::new();
173 let mut output = Vec::new();
174
175 let columns = test_columns();
176 formatter.write_header(&mut output, &columns).unwrap();
177
178 let row = Row::new(vec![Value::Int(1), Value::Null]);
179 formatter.write_row(&mut output, &row).unwrap();
180
181 let result = String::from_utf8(output).unwrap();
182 assert_eq!(result, "id\tname\n1\t\n");
183 }
184
185 #[test]
186 fn test_tsv_supports_streaming() {
187 let formatter = TsvFormatter::new();
188 assert!(formatter.supports_streaming());
189 }
190}