rson_core/
formatter.rs

1//! RSON formatter for converting `RsonValue` back to text.
2//!
3//! This module provides functionality to format RSON values as human-readable text,
4//! with configurable formatting options for indentation, spacing, and style.
5
6use crate::{RsonValue, RsonError, RsonResult};
7use core::fmt::Write;
8
9#[cfg(not(feature = "std"))]
10use alloc::{string::String, vec::Vec};
11
12/// Configuration options for RSON formatting.
13#[derive(Debug, Clone)]
14pub struct FormatOptions {
15    /// Number of spaces per indentation level
16    pub indent_size: usize,
17    /// Whether to use compact mode (minimal whitespace)
18    pub compact: bool,
19    /// Whether to include trailing commas
20    pub trailing_commas: bool,
21    /// Maximum line length before wrapping
22    pub max_line_length: usize,
23    /// Whether to sort map keys
24    pub sort_keys: bool,
25}
26
27impl Default for FormatOptions {
28    fn default() -> Self {
29        Self {
30            indent_size: 2,
31            compact: false,
32            trailing_commas: true,
33            max_line_length: 80,
34            sort_keys: false,
35        }
36    }
37}
38
39impl FormatOptions {
40    /// Create compact formatting options.
41    pub fn compact() -> Self {
42        Self {
43            compact: true,
44            trailing_commas: false,
45            ..Default::default()
46        }
47    }
48    
49    /// Create pretty formatting options.
50    pub fn pretty() -> Self {
51        Self {
52            compact: false,
53            trailing_commas: true,
54            indent_size: 2,
55            ..Default::default()
56        }
57    }
58}
59
60/// RSON formatter.
61pub struct Formatter<'a> {
62    options: &'a FormatOptions,
63    output: String,
64    indent_level: usize,
65}
66
67impl<'a> Formatter<'a> {
68    /// Create a new formatter with the given options.
69    pub fn new(options: &'a FormatOptions) -> Self {
70        Self {
71            options,
72            output: String::new(),
73            indent_level: 0,
74        }
75    }
76    
77    /// Format a value and return the result.
78    pub fn format(mut self, value: &RsonValue) -> RsonResult<String> {
79        self.format_value(value)?;
80        Ok(self.output)
81    }
82    
83    /// Format a value to the internal buffer.
84    fn format_value(&mut self, value: &RsonValue) -> RsonResult<()> {
85        match value {
86            RsonValue::Null => self.write_str("null"),
87            RsonValue::Bool(b) => self.write_str(&b.to_string()),
88            RsonValue::Int(i) => self.write_str(&i.to_string()),
89            RsonValue::Float(f) => self.write_str(&f.to_string()),
90            RsonValue::String(s) => self.format_string(s),
91            RsonValue::Char(c) => self.format_char(*c),
92            RsonValue::Array(arr) => self.format_array(arr),
93            RsonValue::Map(map) => self.format_map(map),
94            RsonValue::Struct { name, fields } => self.format_struct(name, fields),
95            RsonValue::Tuple(values) => self.format_tuple(values),
96            RsonValue::Enum { name, variant, value } => self.format_enum(name, variant, value.as_ref().map(|v| &**v)),
97            RsonValue::Option(opt) => self.format_option(opt.as_ref().map(|v| &**v)),
98        }
99    }
100    
101    /// Write a string to the output.
102    fn write_str(&mut self, s: &str) -> RsonResult<()> {
103        self.output.push_str(s);
104        Ok(())
105    }
106    
107    /// Write a character to the output.
108    fn write_char(&mut self, c: char) -> RsonResult<()> {
109        self.output.push(c);
110        Ok(())
111    }
112    
113    /// Write indentation.
114    fn write_indent(&mut self) -> RsonResult<()> {
115        if !self.options.compact {
116            for _ in 0..(self.indent_level * self.options.indent_size) {
117                self.write_char(' ')?;
118            }
119        }
120        Ok(())
121    }
122    
123    /// Write a newline (unless in compact mode).
124    fn write_newline(&mut self) -> RsonResult<()> {
125        if !self.options.compact {
126            self.write_char('\n')?;
127        }
128        Ok(())
129    }
130    
131    /// Write a space (unless in compact mode).
132    fn write_space(&mut self) -> RsonResult<()> {
133        if !self.options.compact {
134            self.write_char(' ')?;
135        }
136        Ok(())
137    }
138    
139    /// Format a string value with proper escaping.
140    fn format_string(&mut self, s: &str) -> RsonResult<()> {
141        self.write_char('"')?;
142        for c in s.chars() {
143            match c {
144                '"' => self.write_str("\\\"")?,
145                '\\' => self.write_str("\\\\")?,
146                '\n' => self.write_str("\\n")?,
147                '\r' => self.write_str("\\r")?,
148                '\t' => self.write_str("\\t")?,
149                '\0' => self.write_str("\\0")?,
150                c if c.is_control() => {
151                    write!(self.output, "\\u{:04x}", c as u32)
152                        .map_err(|_| RsonError::custom("Failed to write Unicode escape"))?;
153                }
154                c => self.write_char(c)?,
155            }
156        }
157        self.write_char('"')?;
158        Ok(())
159    }
160    
161    /// Format a character value.
162    fn format_char(&mut self, c: char) -> RsonResult<()> {
163        self.write_char('\'')?;
164        match c {
165            '\'' => self.write_str("\\'")?,
166            '\\' => self.write_str("\\\\")?,
167            '\n' => self.write_str("\\n")?,
168            '\r' => self.write_str("\\r")?,
169            '\t' => self.write_str("\\t")?,
170            '\0' => self.write_str("\\0")?,
171            c if c.is_control() => {
172                write!(self.output, "\\u{:04x}", c as u32)
173                    .map_err(|_| RsonError::custom("Failed to write Unicode escape"))?;
174            }
175            c => self.write_char(c)?,
176        }
177        self.write_char('\'')?;
178        Ok(())
179    }
180    
181    /// Format an array.
182    fn format_array(&mut self, arr: &[RsonValue]) -> RsonResult<()> {
183        self.write_char('[')?;
184        
185        if arr.is_empty() {
186            self.write_char(']')?;
187            return Ok(());
188        }
189        
190        let multiline = !self.options.compact && self.should_be_multiline_array(arr);
191        
192        if multiline {
193            self.write_newline()?;
194            self.indent_level += 1;
195        }
196        
197        for (i, item) in arr.iter().enumerate() {
198            if i > 0 {
199                self.write_char(',')?;
200                if multiline {
201                    self.write_newline()?;
202                } else {
203                    self.write_space()?;
204                }
205            }
206            
207            if multiline {
208                self.write_indent()?;
209            }
210            
211            self.format_value(item)?;
212        }
213        
214        if self.options.trailing_commas && !arr.is_empty() {
215            self.write_char(',')?;
216        }
217        
218        if multiline {
219            self.write_newline()?;
220            self.indent_level -= 1;
221            self.write_indent()?;
222        }
223        
224        self.write_char(']')?;
225        Ok(())
226    }
227    
228    /// Format a map.
229    fn format_map(&mut self, map: &indexmap::IndexMap<String, RsonValue>) -> RsonResult<()> {
230        self.write_char('{')?;
231        
232        if map.is_empty() {
233            self.write_char('}')?;
234            return Ok(());
235        }
236        
237        let multiline = !self.options.compact && self.should_be_multiline_map(map);
238        
239        if multiline {
240            self.write_newline()?;
241            self.indent_level += 1;
242        }
243        
244        let mut entries: Vec<_> = map.iter().collect();
245        if self.options.sort_keys {
246            entries.sort_by_key(|(key, _)| *key);
247        }
248        
249        for (i, (key, value)) in entries.iter().enumerate() {
250            if i > 0 {
251                self.write_char(',')?;
252                if multiline {
253                    self.write_newline()?;
254                } else {
255                    self.write_space()?;
256                }
257            }
258            
259            if multiline {
260                self.write_indent()?;
261            }
262            
263            self.format_map_key(key)?;
264            self.write_char(':')?;
265            self.write_space()?;
266            self.format_value(value)?;
267        }
268        
269        if self.options.trailing_commas && !map.is_empty() {
270            self.write_char(',')?;
271        }
272        
273        if multiline {
274            self.write_newline()?;
275            self.indent_level -= 1;
276            self.write_indent()?;
277        }
278        
279        self.write_char('}')?;
280        Ok(())
281    }
282    
283    /// Format a map key (quoted if necessary).
284    fn format_map_key(&mut self, key: &str) -> RsonResult<()> {
285        if is_valid_identifier(key) {
286            self.write_str(key)?;
287        } else {
288            self.format_string(key)?;
289        }
290        Ok(())
291    }
292    
293    /// Format a struct.
294    fn format_struct(&mut self, name: &str, fields: &indexmap::IndexMap<String, RsonValue>) -> RsonResult<()> {
295        self.write_str(name)?;
296        self.write_char('(')?;
297        
298        if fields.is_empty() {
299            self.write_char(')')?;
300            return Ok(());
301        }
302        
303        let multiline = !self.options.compact && self.should_be_multiline_struct(fields);
304        
305        if multiline {
306            self.write_newline()?;
307            self.indent_level += 1;
308        }
309        
310        for (i, (field_name, value)) in fields.iter().enumerate() {
311            if i > 0 {
312                self.write_char(',')?;
313                if multiline {
314                    self.write_newline()?;
315                } else {
316                    self.write_space()?;
317                }
318            }
319            
320            if multiline {
321                self.write_indent()?;
322            }
323            
324            self.write_str(field_name)?;
325            self.write_char(':')?;
326            self.write_space()?;
327            self.format_value(value)?;
328        }
329        
330        if self.options.trailing_commas && !fields.is_empty() {
331            self.write_char(',')?;
332        }
333        
334        if multiline {
335            self.write_newline()?;
336            self.indent_level -= 1;
337            self.write_indent()?;
338        }
339        
340        self.write_char(')')?;
341        Ok(())
342    }
343    
344    /// Format a tuple.
345    fn format_tuple(&mut self, values: &[RsonValue]) -> RsonResult<()> {
346        self.write_char('(')?;
347        
348        for (i, value) in values.iter().enumerate() {
349            if i > 0 {
350                self.write_char(',')?;
351                self.write_space()?;
352            }
353            self.format_value(value)?;
354        }
355        
356        // Always add trailing comma for single-element tuples to distinguish from parentheses
357        if values.len() == 1 || (self.options.trailing_commas && !values.is_empty()) {
358            self.write_char(',')?;
359        }
360        
361        self.write_char(')')?;
362        Ok(())
363    }
364    
365    /// Format an enum.
366    fn format_enum(&mut self, name: &str, variant: &str, value: Option<&RsonValue>) -> RsonResult<()> {
367        self.write_str(name)?;
368        self.write_str("::")?;
369        self.write_str(variant)?;
370        
371        if let Some(val) = value {
372            self.write_char('(')?;
373            self.format_value(val)?;
374            self.write_char(')')?;
375        }
376        
377        Ok(())
378    }
379    
380    /// Format an Option.
381    fn format_option(&mut self, value: Option<&RsonValue>) -> RsonResult<()> {
382        match value {
383            Some(val) => {
384                self.write_str("Some(")?;
385                self.format_value(val)?;
386                self.write_char(')')?;
387            }
388            None => self.write_str("None")?,
389        }
390        Ok(())
391    }
392    
393    /// Check if an array should be formatted as multiline.
394    fn should_be_multiline_array(&self, arr: &[RsonValue]) -> bool {
395        if self.options.compact {
396            return false;
397        }
398        
399        // Simple heuristic: multiline if more than 3 elements or contains complex structures
400        arr.len() > 3 || arr.iter().any(|v| matches!(v, 
401            RsonValue::Array(_) | 
402            RsonValue::Map(_) | 
403            RsonValue::Struct { .. }
404        ))
405    }
406    
407    /// Check if a map should be formatted as multiline.
408    fn should_be_multiline_map(&self, map: &indexmap::IndexMap<String, RsonValue>) -> bool {
409        if self.options.compact {
410            return false;
411        }
412        
413        // Multiline if more than 1 field or contains complex structures
414        map.len() > 1 || map.values().any(|v| matches!(v,
415            RsonValue::Array(_) | 
416            RsonValue::Map(_) | 
417            RsonValue::Struct { .. }
418        ))
419    }
420    
421    /// Check if a struct should be formatted as multiline.
422    fn should_be_multiline_struct(&self, fields: &indexmap::IndexMap<String, RsonValue>) -> bool {
423        if self.options.compact {
424            return false;
425        }
426        
427        // Multiline if more than 2 fields or contains complex structures
428        fields.len() > 2 || fields.values().any(|v| matches!(v,
429            RsonValue::Array(_) | 
430            RsonValue::Map(_) | 
431            RsonValue::Struct { .. }
432        ))
433    }
434}
435
436/// Check if a string is a valid identifier (can be unquoted).
437fn is_valid_identifier(s: &str) -> bool {
438    if s.is_empty() {
439        return false;
440    }
441    
442    // Check if it's a reserved keyword
443    match s {
444        "true" | "false" | "null" | "Some" | "None" => return false,
445        _ => {}
446    }
447    
448    let mut chars = s.chars();
449    let first = chars.next().unwrap();
450    
451    // First character must be letter or underscore
452    if !first.is_alphabetic() && first != '_' {
453        return false;
454    }
455    
456    // Remaining characters must be alphanumeric or underscore
457    chars.all(|c| c.is_alphanumeric() || c == '_')
458}
459
460/// Format an RSON value with the given options.
461pub fn format_rson(value: &RsonValue, options: &FormatOptions) -> RsonResult<String> {
462    let formatter = Formatter::new(options);
463    formatter.format(value)
464}
465
466/// Format an RSON value with default pretty formatting.
467pub fn format_pretty(value: &RsonValue) -> RsonResult<String> {
468    format_rson(value, &FormatOptions::pretty())
469}
470
471/// Format an RSON value with compact formatting.
472pub fn format_compact(value: &RsonValue) -> RsonResult<String> {
473    format_rson(value, &FormatOptions::compact())
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479    use crate::RsonValue;
480    use indexmap::IndexMap;
481
482    #[test]
483    fn test_format_primitives() {
484        assert_eq!(format_compact(&RsonValue::Null).unwrap(), "null");
485        assert_eq!(format_compact(&RsonValue::Bool(true)).unwrap(), "true");
486        assert_eq!(format_compact(&RsonValue::Bool(false)).unwrap(), "false");
487        assert_eq!(format_compact(&RsonValue::Int(42)).unwrap(), "42");
488        assert_eq!(format_compact(&RsonValue::Float(3.14)).unwrap(), "3.14");
489        assert_eq!(format_compact(&RsonValue::String("hello".to_string())).unwrap(), r#""hello""#);
490        assert_eq!(format_compact(&RsonValue::Char('a')).unwrap(), "'a'");
491    }
492
493    #[test]
494    fn test_format_array() {
495        let arr = RsonValue::Array(vec![
496            RsonValue::Int(1),
497            RsonValue::Int(2),
498            RsonValue::Int(3),
499        ]);
500        
501        assert_eq!(format_compact(&arr).unwrap(), "[1,2,3]");
502        
503        let pretty = format_pretty(&arr).unwrap();
504        assert!(pretty.contains("["));
505        assert!(pretty.contains("1,"));
506        assert!(pretty.contains("2,"));
507        assert!(pretty.contains("3,"));
508        assert!(pretty.contains("]"));
509    }
510
511    #[test]
512    fn test_format_map() {
513        let mut map = IndexMap::new();
514        map.insert("name".to_string(), RsonValue::String("Alice".to_string()));
515        map.insert("age".to_string(), RsonValue::Int(30));
516        
517        let value = RsonValue::Map(map);
518        let formatted = format_compact(&value).unwrap();
519        
520        assert!(formatted.contains("name:\"Alice\""));
521        assert!(formatted.contains("age:30"));
522    }
523
524    #[test]
525    fn test_format_struct() {
526        let mut fields = IndexMap::new();
527        fields.insert("x".to_string(), RsonValue::Int(10));
528        fields.insert("y".to_string(), RsonValue::Int(20));
529        
530        let value = RsonValue::Struct {
531            name: "Point".to_string(),
532            fields,
533        };
534        
535        let formatted = format_compact(&value).unwrap();
536        assert_eq!(formatted, "Point(x:10,y:20)");
537    }
538
539    #[test]
540    fn test_format_enum() {
541        let enum_val = RsonValue::Enum {
542            name: "Color".to_string(),
543            variant: "Red".to_string(),
544            value: None,
545        };
546        
547        assert_eq!(format_compact(&enum_val).unwrap(), "Color::Red");
548        
549        let enum_with_value = RsonValue::Enum {
550            name: "Result".to_string(),
551            variant: "Ok".to_string(),
552            value: Some(Box::new(RsonValue::String("success".to_string()))),
553        };
554        
555        assert_eq!(format_compact(&enum_with_value).unwrap(), r#"Result::Ok("success")"#);
556    }
557
558    #[test]
559    fn test_format_option() {
560        assert_eq!(format_compact(&RsonValue::Option(None)).unwrap(), "None");
561        assert_eq!(
562            format_compact(&RsonValue::Option(Some(Box::new(RsonValue::Int(42))))).unwrap(),
563            "Some(42)"
564        );
565    }
566
567    #[test]
568    fn test_string_escaping() {
569        let value = RsonValue::String("hello\nworld\"test".to_string());
570        let formatted = format_compact(&value).unwrap();
571        assert_eq!(formatted, r#""hello\nworld\"test""#);
572    }
573
574    #[test]
575    fn test_identifier_validation() {
576        assert!(is_valid_identifier("hello"));
577        assert!(is_valid_identifier("_private"));
578        assert!(is_valid_identifier("field1"));
579        assert!(!is_valid_identifier("123invalid"));
580        assert!(!is_valid_identifier("true"));
581        assert!(!is_valid_identifier(""));
582        assert!(!is_valid_identifier("hello-world"));
583    }
584}