toon_format/encode/
writer.rs1use crate::{
2 types::{
3 Delimiter,
4 EncodeOptions,
5 ToonResult,
6 },
7 utils::{
8 string::{
9 is_valid_unquoted_key,
10 needs_quoting,
11 quote_string,
12 },
13 QuotingContext,
14 },
15};
16
17pub struct Writer {
19 buffer: String,
20 pub(crate) options: EncodeOptions,
21 active_delimiters: Vec<Delimiter>,
22}
23
24impl Writer {
25 pub fn new(options: EncodeOptions) -> Self {
27 Self {
28 buffer: String::new(),
29 active_delimiters: vec![options.delimiter],
30 options,
31 }
32 }
33
34 pub fn finish(self) -> String {
36 self.buffer
37 }
38
39 pub fn write_str(&mut self, s: &str) -> ToonResult<()> {
40 self.buffer.push_str(s);
41 Ok(())
42 }
43
44 pub fn write_char(&mut self, ch: char) -> ToonResult<()> {
45 self.buffer.push(ch);
46 Ok(())
47 }
48
49 pub fn write_newline(&mut self) -> ToonResult<()> {
50 self.buffer.push('\n');
51 Ok(())
52 }
53
54 pub fn write_indent(&mut self, depth: usize) -> ToonResult<()> {
55 let indent_string = self.options.indent.get_string(depth);
56 if !indent_string.is_empty() {
57 self.buffer.push_str(&indent_string);
58 }
59 Ok(())
60 }
61
62 pub fn write_delimiter(&mut self) -> ToonResult<()> {
63 self.buffer.push(self.options.delimiter.as_char());
64 Ok(())
65 }
66
67 pub fn write_key(&mut self, key: &str) -> ToonResult<()> {
68 if is_valid_unquoted_key(key) {
69 self.write_str(key)
70 } else {
71 self.write_quoted_string(key)
72 }
73 }
74
75 pub fn write_array_header(
77 &mut self,
78 key: Option<&str>,
79 length: usize,
80 fields: Option<&[String]>,
81 depth: usize,
82 ) -> ToonResult<()> {
83 if let Some(k) = key {
84 if depth > 0 {
85 self.write_indent(depth)?;
86 }
87 self.write_key(k)?;
88 }
89
90 self.write_char('[')?;
91
92 let length_str = self.options.format_length(length);
93 self.write_str(&length_str)?;
94
95 if self.options.delimiter != Delimiter::Comma {
96 self.write_delimiter()?;
97 }
98
99 self.write_char(']')?;
100
101 if let Some(field_list) = fields {
102 self.write_char('{')?;
103 for (i, field) in field_list.iter().enumerate() {
104 if i > 0 {
105 self.write_delimiter()?;
106 }
107 self.write_key(field)?;
108 }
109 self.write_char('}')?;
110 }
111
112 self.write_char(':')
113 }
114
115 pub fn write_empty_array_with_key(&mut self, key: Option<&str>) -> ToonResult<()> {
117 if let Some(k) = key {
118 self.write_key(k)?;
119 }
120 self.write_char('[')?;
121
122 let length_str = self.options.format_length(0);
123 self.write_str(&length_str)?;
124
125 if self.options.delimiter != Delimiter::Comma {
126 self.write_delimiter()?;
127 }
128
129 self.write_char(']')?;
130 self.write_char(':')
131 }
132
133 pub fn needs_quoting(&self, s: &str, context: QuotingContext) -> bool {
134 let delim_char = match context {
135 QuotingContext::ObjectValue => self.get_document_delimiter_char(),
136 QuotingContext::ArrayValue => self.get_active_delimiter_char(),
137 };
138 needs_quoting(s, delim_char)
139 }
140
141 pub fn write_quoted_string(&mut self, s: &str) -> ToonResult<()> {
142 self.write_str("e_string(s))
143 }
144
145 pub fn write_value(&mut self, s: &str, context: QuotingContext) -> ToonResult<()> {
146 if self.needs_quoting(s, context) {
147 self.write_quoted_string(s)
148 } else {
149 self.write_str(s)
150 }
151 }
152
153 pub fn push_active_delimiter(&mut self, delim: Delimiter) {
154 self.active_delimiters.push(delim);
155 }
156 pub fn pop_active_delimiter(&mut self) {
157 if self.active_delimiters.len() > 1 {
158 self.active_delimiters.pop();
159 }
160 }
161 fn get_active_delimiter_char(&self) -> char {
162 self.active_delimiters
163 .last()
164 .unwrap_or(&self.options.delimiter)
165 .as_char()
166 }
167
168 fn get_document_delimiter_char(&self) -> char {
169 self.options.delimiter.as_char()
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_writer_basic() {
179 let opts = EncodeOptions::default();
180 let mut writer = Writer::new(opts);
181
182 writer.write_str("hello").unwrap();
183 writer.write_str(" ").unwrap();
184 writer.write_str("world").unwrap();
185
186 assert_eq!(writer.finish(), "hello world");
187 }
188
189 #[test]
190 fn test_write_delimiter() {
191 let mut opts = EncodeOptions::default();
192 let mut writer = Writer::new(opts.clone());
193
194 writer.write_str("a").unwrap();
195 writer.write_delimiter().unwrap();
196 writer.write_str("b").unwrap();
197
198 assert_eq!(writer.finish(), "a,b");
199
200 opts = opts.with_delimiter(Delimiter::Pipe);
201 let mut writer = Writer::new(opts);
202
203 writer.write_str("a").unwrap();
204 writer.write_delimiter().unwrap();
205 writer.write_str("b").unwrap();
206
207 assert_eq!(writer.finish(), "a|b");
208 }
209
210 #[test]
211 fn test_write_indent() {
212 let opts = EncodeOptions::default();
213 let mut writer = Writer::new(opts);
214
215 writer.write_indent(0).unwrap();
216 writer.write_str("a").unwrap();
217 writer.write_newline().unwrap();
218
219 writer.write_indent(1).unwrap();
220 writer.write_str("b").unwrap();
221 writer.write_newline().unwrap();
222
223 writer.write_indent(2).unwrap();
224 writer.write_str("c").unwrap();
225
226 assert_eq!(writer.finish(), "a\n b\n c");
227 }
228
229 #[test]
230 fn test_write_array_header() {
231 let opts = EncodeOptions::default();
232 let mut writer = Writer::new(opts);
233
234 writer
235 .write_array_header(Some("items"), 3, None, 0)
236 .unwrap();
237 assert_eq!(writer.finish(), "items[3]:");
238
239 let opts = EncodeOptions::default();
240 let mut writer = Writer::new(opts);
241 let fields = vec!["id".to_string(), "name".to_string()];
242
243 writer
244 .write_array_header(Some("users"), 2, Some(&fields), 0)
245 .unwrap();
246 assert_eq!(writer.finish(), "users[2]{id,name}:");
247 }
248
249 #[test]
250 fn test_write_array_header_with_length_marker() {
251 let opts = EncodeOptions::new().with_length_marker('#');
252 let mut writer = Writer::new(opts);
253
254 writer
255 .write_array_header(Some("items"), 3, None, 0)
256 .unwrap();
257 assert_eq!(writer.finish(), "items[#3]:");
258 }
259
260 #[test]
261 fn test_write_array_header_with_pipe_delimiter() {
262 let opts = EncodeOptions::new().with_delimiter(Delimiter::Pipe);
263 let mut writer = Writer::new(opts);
264
265 writer
266 .write_array_header(Some("items"), 3, None, 0)
267 .unwrap();
268 assert_eq!(writer.finish(), "items[3|]:");
269
270 let opts = EncodeOptions::new().with_delimiter(Delimiter::Pipe);
271 let mut writer = Writer::new(opts);
272 let fields = vec!["id".to_string(), "name".to_string()];
273
274 writer
275 .write_array_header(Some("users"), 2, Some(&fields), 0)
276 .unwrap();
277 assert_eq!(writer.finish(), "users[2|]{id|name}:");
278 }
279
280 #[test]
281 fn test_write_key_with_special_chars() {
282 let opts = EncodeOptions::default();
283 let mut writer = Writer::new(opts);
284
285 writer.write_key("normal_key").unwrap();
286 assert_eq!(writer.finish(), "normal_key");
287
288 let opts = EncodeOptions::default();
289 let mut writer = Writer::new(opts);
290
291 writer.write_key("key:with:colons").unwrap();
292 assert_eq!(writer.finish(), "\"key:with:colons\"");
293 }
294
295 #[test]
296 fn test_write_quoted_string() {
297 let opts = EncodeOptions::default();
298 let mut writer = Writer::new(opts);
299
300 writer.write_quoted_string("hello world").unwrap();
301 assert_eq!(writer.finish(), "\"hello world\"");
302
303 let opts = EncodeOptions::default();
304 let mut writer = Writer::new(opts);
305
306 writer.write_quoted_string("say \"hi\"").unwrap();
307 assert_eq!(writer.finish(), r#""say \"hi\"""#);
308 }
309
310 #[test]
311 fn test_needs_quoting() {
312 let opts = EncodeOptions::default();
313 let writer = Writer::new(opts);
314 let ctx = QuotingContext::ObjectValue;
315
316 assert!(!writer.needs_quoting("hello", ctx));
317 assert!(writer.needs_quoting("hello,world", ctx));
318 assert!(writer.needs_quoting("true", ctx));
319 assert!(writer.needs_quoting("false", ctx));
320 assert!(writer.needs_quoting("null", ctx));
321 assert!(writer.needs_quoting("123", ctx));
322 assert!(writer.needs_quoting("", ctx));
323 assert!(writer.needs_quoting("hello:world", ctx));
324 }
325
326 #[test]
327 fn test_write_empty_array() {
328 let opts = EncodeOptions::default();
329 let mut writer = Writer::new(opts);
330
331 writer.write_empty_array_with_key(Some("items")).unwrap();
332 assert_eq!(writer.finish(), "items[0]:");
333 }
334}