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 self.write_str(&length.to_string())?;
92
93 if self.options.delimiter != Delimiter::Comma {
95 self.write_delimiter()?;
96 }
97
98 self.write_char(']')?;
99
100 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(
117 &mut self,
118 key: Option<&str>,
119 depth: usize,
120 ) -> ToonResult<()> {
121 if let Some(k) = key {
122 if depth > 0 {
123 self.write_indent(depth)?;
124 }
125 self.write_key(k)?;
126 }
127 self.write_char('[')?;
128 self.write_str("0")?;
129
130 if self.options.delimiter != Delimiter::Comma {
131 self.write_delimiter()?;
132 }
133
134 self.write_char(']')?;
135 self.write_char(':')
136 }
137
138 pub fn needs_quoting(&self, s: &str, context: QuotingContext) -> bool {
139 let delim_char = match context {
141 QuotingContext::ObjectValue => self.get_document_delimiter_char(),
142 QuotingContext::ArrayValue => self.get_active_delimiter_char(),
143 };
144 needs_quoting(s, delim_char)
145 }
146
147 pub fn write_quoted_string(&mut self, s: &str) -> ToonResult<()> {
148 self.write_str("e_string(s))
149 }
150
151 pub fn write_value(&mut self, s: &str, context: QuotingContext) -> ToonResult<()> {
152 if self.needs_quoting(s, context) {
153 self.write_quoted_string(s)
154 } else {
155 self.write_str(s)
156 }
157 }
158
159 pub fn push_active_delimiter(&mut self, delim: Delimiter) {
162 self.active_delimiters.push(delim);
163 }
164 pub fn pop_active_delimiter(&mut self) {
166 if self.active_delimiters.len() > 1 {
167 self.active_delimiters.pop();
168 }
169 }
170 fn get_active_delimiter_char(&self) -> char {
171 self.active_delimiters
172 .last()
173 .unwrap_or(&self.options.delimiter)
174 .as_char()
175 }
176
177 fn get_document_delimiter_char(&self) -> char {
178 self.options.delimiter.as_char()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_writer_basic() {
188 let opts = EncodeOptions::default();
189 let mut writer = Writer::new(opts);
190
191 writer.write_str("hello").unwrap();
192 writer.write_str(" ").unwrap();
193 writer.write_str("world").unwrap();
194
195 assert_eq!(writer.finish(), "hello world");
196 }
197
198 #[test]
199 fn test_write_delimiter() {
200 let mut opts = EncodeOptions::default();
201 let mut writer = Writer::new(opts.clone());
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 opts = opts.with_delimiter(Delimiter::Pipe);
210 let mut writer = Writer::new(opts);
211
212 writer.write_str("a").unwrap();
213 writer.write_delimiter().unwrap();
214 writer.write_str("b").unwrap();
215
216 assert_eq!(writer.finish(), "a|b");
217 }
218
219 #[test]
220 fn test_write_indent() {
221 let opts = EncodeOptions::default();
222 let mut writer = Writer::new(opts);
223
224 writer.write_indent(0).unwrap();
225 writer.write_str("a").unwrap();
226 writer.write_newline().unwrap();
227
228 writer.write_indent(1).unwrap();
229 writer.write_str("b").unwrap();
230 writer.write_newline().unwrap();
231
232 writer.write_indent(2).unwrap();
233 writer.write_str("c").unwrap();
234
235 assert_eq!(writer.finish(), "a\n b\n c");
236 }
237
238 #[test]
239 fn test_write_array_header() {
240 let opts = EncodeOptions::default();
241 let mut writer = Writer::new(opts);
242
243 writer
244 .write_array_header(Some("items"), 3, None, 0)
245 .unwrap();
246 assert_eq!(writer.finish(), "items[3]:");
247
248 let opts = EncodeOptions::default();
249 let mut writer = Writer::new(opts);
250 let fields = vec!["id".to_string(), "name".to_string()];
251
252 writer
253 .write_array_header(Some("users"), 2, Some(&fields), 0)
254 .unwrap();
255 assert_eq!(writer.finish(), "users[2]{id,name}:");
256 }
257
258 #[test]
259 fn test_write_array_header_with_pipe_delimiter() {
260 let opts = EncodeOptions::new().with_delimiter(Delimiter::Pipe);
261 let mut writer = Writer::new(opts);
262
263 writer
264 .write_array_header(Some("items"), 3, None, 0)
265 .unwrap();
266 assert_eq!(writer.finish(), "items[3|]:");
267
268 let opts = EncodeOptions::new().with_delimiter(Delimiter::Pipe);
269 let mut writer = Writer::new(opts);
270 let fields = vec!["id".to_string(), "name".to_string()];
271
272 writer
273 .write_array_header(Some("users"), 2, Some(&fields), 0)
274 .unwrap();
275 assert_eq!(writer.finish(), "users[2|]{id|name}:");
276 }
277
278 #[test]
279 fn test_write_key_with_special_chars() {
280 let opts = EncodeOptions::default();
281 let mut writer = Writer::new(opts);
282
283 writer.write_key("normal_key").unwrap();
284 assert_eq!(writer.finish(), "normal_key");
285
286 let opts = EncodeOptions::default();
287 let mut writer = Writer::new(opts);
288
289 writer.write_key("key:with:colons").unwrap();
290 assert_eq!(writer.finish(), "\"key:with:colons\"");
291 }
292
293 #[test]
294 fn test_write_quoted_string() {
295 let opts = EncodeOptions::default();
296 let mut writer = Writer::new(opts);
297
298 writer.write_quoted_string("hello world").unwrap();
299 assert_eq!(writer.finish(), "\"hello world\"");
300
301 let opts = EncodeOptions::default();
302 let mut writer = Writer::new(opts);
303
304 writer.write_quoted_string("say \"hi\"").unwrap();
305 assert_eq!(writer.finish(), r#""say \"hi\"""#);
306 }
307
308 #[test]
309 fn test_needs_quoting() {
310 let opts = EncodeOptions::default();
311 let writer = Writer::new(opts);
312 let ctx = QuotingContext::ObjectValue;
313
314 assert!(!writer.needs_quoting("hello", ctx));
315 assert!(writer.needs_quoting("hello,world", ctx));
316 assert!(writer.needs_quoting("true", ctx));
317 assert!(writer.needs_quoting("false", ctx));
318 assert!(writer.needs_quoting("null", ctx));
319 assert!(writer.needs_quoting("123", ctx));
320 assert!(writer.needs_quoting("", ctx));
321 assert!(writer.needs_quoting("hello:world", ctx));
322 }
323
324 #[test]
325 fn test_write_empty_array() {
326 let opts = EncodeOptions::default();
327 let mut writer = Writer::new(opts);
328
329 writer.write_empty_array_with_key(Some("items"), 0).unwrap();
330 assert_eq!(writer.finish(), "items[0]:");
331 }
332}