facet_diff_core/layout/
flavor.rs

1//! Diff output flavors (Rust, JSON, XML).
2//!
3//! Each flavor knows how to present struct fields and format values
4//! according to its format's conventions.
5
6use std::borrow::Cow;
7use std::fmt::Write;
8
9use facet_core::{Def, Field, PrimitiveType, Type};
10use facet_reflect::Peek;
11
12/// How a field should be presented in the diff output.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum FieldPresentation {
15    /// Show as an inline attribute/field on the struct line.
16    /// - Rust: `x: 10`
17    /// - JSON: `"x": 10`
18    /// - XML: `x="10"` (as attribute on opening tag)
19    Attribute {
20        /// The field name (possibly renamed)
21        name: Cow<'static, str>,
22    },
23
24    /// Show as a nested child element.
25    /// - XML: `<title>...</title>` as child element
26    /// - Rust/JSON: same as Attribute (nested structs are inline)
27    Child {
28        /// The element/field name
29        name: Cow<'static, str>,
30    },
31
32    /// Show as text content inside the parent.
33    /// - XML: `<p>this text</p>`
34    /// - Rust/JSON: same as Attribute
35    TextContent,
36
37    /// Show as multiple child elements (for sequences).
38    /// - XML: `<Item/><Item/>` as siblings
39    /// - Rust/JSON: same as Attribute (sequences are `[...]`)
40    Children {
41        /// The name for each item element
42        item_name: Cow<'static, str>,
43    },
44}
45
46/// A diff output flavor that knows how to format values and present fields.
47pub trait DiffFlavor {
48    /// Format a scalar/leaf value into a writer.
49    ///
50    /// The output should NOT include surrounding quotes for strings -
51    /// the renderer will add appropriate syntax based on context.
52    fn format_value(&self, peek: Peek<'_, '_>, w: &mut dyn Write) -> std::fmt::Result;
53
54    /// Determine how a field should be presented.
55    fn field_presentation(&self, field: &Field) -> FieldPresentation;
56
57    /// Opening syntax for a struct/object.
58    /// - Rust: `Point {`
59    /// - JSON: `{`
60    /// - XML: `<Point`
61    fn struct_open(&self, name: &str) -> Cow<'static, str>;
62
63    /// Closing syntax for a struct/object.
64    /// - Rust: `}`
65    /// - JSON: `}`
66    /// - XML: `/>` (self-closing) or `</Point>`
67    fn struct_close(&self, name: &str, self_closing: bool) -> Cow<'static, str>;
68
69    /// Separator between fields.
70    /// - Rust: `, `
71    /// - JSON: `, `
72    /// - XML: ` ` (space between attributes)
73    fn field_separator(&self) -> &'static str;
74
75    /// Trailing comma/separator (no trailing space).
76    /// Used at end of lines when fields are broken across lines.
77    /// - Rust: `,`
78    /// - JSON: `,`
79    /// - XML: `` (empty - no trailing separator)
80    fn trailing_separator(&self) -> &'static str {
81        ","
82    }
83
84    /// Opening syntax for a sequence/array.
85    /// - Rust: `[`
86    /// - JSON: `[`
87    /// - XML: (wrapper element, handled differently)
88    fn seq_open(&self) -> Cow<'static, str>;
89
90    /// Closing syntax for a sequence/array.
91    /// - Rust: `]`
92    /// - JSON: `]`
93    /// - XML: (wrapper element, handled differently)
94    fn seq_close(&self) -> Cow<'static, str>;
95
96    /// Separator between sequence items.
97    /// - Rust: `, `
98    /// - JSON: `,`
99    /// - XML: (newlines/whitespace)
100    fn item_separator(&self) -> &'static str;
101
102    /// Format a sequence item value, optionally wrapping in element tags.
103    /// - Rust: `0` (no wrapping)
104    /// - JSON: `0` (no wrapping)
105    /// - XML: `<i32>0</i32>` (wrapped in element)
106    fn format_seq_item<'a>(&self, _item_type: &str, value: &'a str) -> Cow<'a, str> {
107        // Default: no wrapping, just return the value
108        Cow::Borrowed(value)
109    }
110
111    /// Opening for a sequence that is a struct field value.
112    /// - Rust: `fieldname: [`
113    /// - JSON: `"fieldname": [`
114    /// - XML: `<fieldname>` (wrapper element, not attribute!)
115    fn format_seq_field_open(&self, field_name: &str) -> String {
116        // Default: use field prefix + seq_open
117        format!(
118            "{}{}",
119            self.format_field_prefix(field_name),
120            self.seq_open()
121        )
122    }
123
124    /// Closing for a sequence that is a struct field value.
125    /// - Rust: `]`
126    /// - JSON: `]`
127    /// - XML: `</fieldname>`
128    fn format_seq_field_close(&self, _field_name: &str) -> Cow<'static, str> {
129        // Default: just seq_close
130        self.seq_close()
131    }
132
133    /// Format a comment (for collapsed items).
134    /// - Rust: `/* ...5 more */`
135    /// - JSON: `// ...5 more`
136    /// - XML: `<!-- ...5 more -->`
137    fn comment(&self, text: &str) -> String;
138
139    /// Format a field assignment (name and value).
140    /// - Rust: `name: value`
141    /// - JSON: `"name": value`
142    /// - XML: `name="value"`
143    fn format_field(&self, name: &str, value: &str) -> String;
144
145    /// Format just the field name with assignment operator.
146    /// - Rust: `name: `
147    /// - JSON: `"name": `
148    /// - XML: `name="`
149    fn format_field_prefix(&self, name: &str) -> String;
150
151    /// Suffix after the value (if any).
152    /// - Rust: `` (empty)
153    /// - JSON: `` (empty)
154    /// - XML: `"` (closing quote)
155    fn format_field_suffix(&self) -> &'static str;
156
157    /// Close the opening tag when there are children.
158    /// - Rust: `` (empty - no separate closing for opening tag)
159    /// - JSON: `` (empty)
160    /// - XML: `>` (close the opening tag before children)
161    fn struct_open_close(&self) -> &'static str {
162        ""
163    }
164
165    /// Optional type name comment to show after struct_open.
166    /// Rendered in muted color for readability.
167    /// - Rust: None (type name is in struct_open)
168    /// - JSON: Some("/* Point */")
169    /// - XML: None
170    fn type_comment(&self, _name: &str) -> Option<String> {
171        None
172    }
173
174    /// Opening wrapper for a child element (nested struct field).
175    /// - Rust: `field_name: ` (field prefix)
176    /// - JSON: `"field_name": ` (field prefix)
177    /// - XML: `` (empty - no wrapper, or could be `<field_name>\n`)
178    fn format_child_open(&self, name: &str) -> Cow<'static, str> {
179        // Default: use field prefix (works for Rust/JSON)
180        Cow::Owned(self.format_field_prefix(name))
181    }
182
183    /// Closing wrapper for a child element (nested struct field).
184    /// - Rust: `` (empty)
185    /// - JSON: `` (empty)
186    /// - XML: `` (empty, or `</field_name>` if wrapping)
187    fn format_child_close(&self, _name: &str) -> Cow<'static, str> {
188        Cow::Borrowed("")
189    }
190}
191
192/// Rust-style output flavor.
193///
194/// Produces output like: `Point { x: 10, y: 20 }`
195#[derive(Debug, Clone, Default)]
196pub struct RustFlavor;
197
198impl DiffFlavor for RustFlavor {
199    fn format_value(&self, peek: Peek<'_, '_>, w: &mut dyn Write) -> std::fmt::Result {
200        format_value_quoted(peek, w)
201    }
202
203    fn field_presentation(&self, field: &Field) -> FieldPresentation {
204        // Rust flavor: all fields are attributes (key: value)
205        FieldPresentation::Attribute {
206            name: Cow::Borrowed(field.name),
207        }
208    }
209
210    fn struct_open(&self, name: &str) -> Cow<'static, str> {
211        Cow::Owned(format!("{} {{", name))
212    }
213
214    fn struct_close(&self, _name: &str, _self_closing: bool) -> Cow<'static, str> {
215        Cow::Borrowed("}")
216    }
217
218    fn field_separator(&self) -> &'static str {
219        ", "
220    }
221
222    fn seq_open(&self) -> Cow<'static, str> {
223        Cow::Borrowed("[")
224    }
225
226    fn seq_close(&self) -> Cow<'static, str> {
227        Cow::Borrowed("]")
228    }
229
230    fn item_separator(&self) -> &'static str {
231        ", "
232    }
233
234    fn comment(&self, text: &str) -> String {
235        format!("/* {} */", text)
236    }
237
238    fn format_field(&self, name: &str, value: &str) -> String {
239        format!("{}: {}", name, value)
240    }
241
242    fn format_field_prefix(&self, name: &str) -> String {
243        format!("{}: ", name)
244    }
245
246    fn format_field_suffix(&self) -> &'static str {
247        ""
248    }
249}
250
251/// JSON-style output flavor (JSONC with comments for type names).
252///
253/// Produces output like: `{ // Point\n  "x": 10, "y": 20\n}`
254#[derive(Debug, Clone, Default)]
255pub struct JsonFlavor;
256
257impl DiffFlavor for JsonFlavor {
258    fn format_value(&self, peek: Peek<'_, '_>, w: &mut dyn Write) -> std::fmt::Result {
259        format_value_quoted(peek, w)
260    }
261
262    fn field_presentation(&self, field: &Field) -> FieldPresentation {
263        // JSON flavor: all fields are attributes ("key": value)
264        FieldPresentation::Attribute {
265            name: Cow::Borrowed(field.name),
266        }
267    }
268
269    fn struct_open(&self, _name: &str) -> Cow<'static, str> {
270        Cow::Borrowed("{")
271    }
272
273    fn type_comment(&self, name: &str) -> Option<String> {
274        Some(format!("/* {} */", name))
275    }
276
277    fn struct_close(&self, _name: &str, _self_closing: bool) -> Cow<'static, str> {
278        Cow::Borrowed("}")
279    }
280
281    fn field_separator(&self) -> &'static str {
282        ", "
283    }
284
285    fn seq_open(&self) -> Cow<'static, str> {
286        Cow::Borrowed("[")
287    }
288
289    fn seq_close(&self) -> Cow<'static, str> {
290        Cow::Borrowed("]")
291    }
292
293    fn item_separator(&self) -> &'static str {
294        ", "
295    }
296
297    fn comment(&self, text: &str) -> String {
298        format!("// {}", text)
299    }
300
301    fn format_field(&self, name: &str, value: &str) -> String {
302        format!("\"{}\": {}", name, value)
303    }
304
305    fn format_field_prefix(&self, name: &str) -> String {
306        format!("\"{}\": ", name)
307    }
308
309    fn format_field_suffix(&self) -> &'static str {
310        ""
311    }
312}
313
314/// XML-style output flavor.
315///
316/// Produces output like: `<Point x="10" y="20"/>`
317///
318/// Respects `#[facet(xml::attribute)]`, `#[facet(xml::element)]`, etc.
319#[derive(Debug, Clone, Default)]
320pub struct XmlFlavor;
321
322impl DiffFlavor for XmlFlavor {
323    fn format_value(&self, peek: Peek<'_, '_>, w: &mut dyn Write) -> std::fmt::Result {
324        format_value_raw(peek, w)
325    }
326
327    fn field_presentation(&self, field: &Field) -> FieldPresentation {
328        // Check for XML-specific attributes
329        //
330        // NOTE: We detect XML attributes by namespace string "xml" (e.g., `field.has_attr(Some("xml"), "attribute")`).
331        // This works because the namespace is defined in the `define_attr_grammar!` macro in facet-xml
332        // with `ns "xml"`, NOT by the import alias. So even if someone writes `use facet_xml as html;`
333        // and uses `#[facet(html::attribute)]`, the namespace stored in the attribute is still "xml".
334        // This should be tested to confirm, but not now.
335        if field.has_attr(Some("xml"), "attribute") {
336            FieldPresentation::Attribute {
337                name: Cow::Borrowed(field.name),
338            }
339        } else if field.has_attr(Some("xml"), "elements") {
340            FieldPresentation::Children {
341                item_name: Cow::Borrowed(field.name),
342            }
343        } else if field.has_attr(Some("xml"), "text") {
344            FieldPresentation::TextContent
345        } else if field.has_attr(Some("xml"), "element") {
346            FieldPresentation::Child {
347                name: Cow::Borrowed(field.name),
348            }
349        } else {
350            // Default: treat as child element (XML's default for non-attributed fields)
351            // In XML, fields without explicit annotation typically become child elements
352            FieldPresentation::Child {
353                name: Cow::Borrowed(field.name),
354            }
355        }
356    }
357
358    fn struct_open(&self, name: &str) -> Cow<'static, str> {
359        Cow::Owned(format!("<{}", name))
360    }
361
362    fn struct_close(&self, name: &str, self_closing: bool) -> Cow<'static, str> {
363        if self_closing {
364            Cow::Borrowed("/>")
365        } else {
366            Cow::Owned(format!("</{}>", name))
367        }
368    }
369
370    fn field_separator(&self) -> &'static str {
371        " "
372    }
373
374    fn seq_open(&self) -> Cow<'static, str> {
375        Cow::Borrowed("<items>")
376    }
377
378    fn seq_close(&self) -> Cow<'static, str> {
379        Cow::Borrowed("</items>")
380    }
381
382    fn item_separator(&self) -> &'static str {
383        " "
384    }
385
386    fn format_seq_item<'a>(&self, item_type: &str, value: &'a str) -> Cow<'a, str> {
387        // Wrap each item in an element tag: <i32>0</i32>
388        Cow::Owned(format!("<{}>{}</{}>", item_type, value, item_type))
389    }
390
391    fn comment(&self, text: &str) -> String {
392        format!("<!-- {} -->", text)
393    }
394
395    fn format_field(&self, name: &str, value: &str) -> String {
396        format!("{}=\"{}\"", name, value)
397    }
398
399    fn format_field_prefix(&self, name: &str) -> String {
400        format!("{}=\"", name)
401    }
402
403    fn format_field_suffix(&self) -> &'static str {
404        "\""
405    }
406
407    fn struct_open_close(&self) -> &'static str {
408        ">"
409    }
410
411    fn format_child_open(&self, _name: &str) -> Cow<'static, str> {
412        // XML: nested elements don't use attribute-style prefix
413        // The nested element tag is self-describing
414        Cow::Borrowed("")
415    }
416
417    fn format_child_close(&self, _name: &str) -> Cow<'static, str> {
418        Cow::Borrowed("")
419    }
420
421    fn trailing_separator(&self) -> &'static str {
422        // XML doesn't use trailing commas/separators
423        ""
424    }
425
426    fn format_seq_field_open(&self, field_name: &str) -> String {
427        // XML: sequence field becomes a wrapper element, not an attribute
428        format!("<{}>", field_name)
429    }
430
431    fn format_seq_field_close(&self, field_name: &str) -> Cow<'static, str> {
432        // XML: close the wrapper element
433        Cow::Owned(format!("</{}>", field_name))
434    }
435}
436
437/// Value formatting with quotes for strings (Rust/JSON style).
438fn format_value_quoted(peek: Peek<'_, '_>, w: &mut dyn Write) -> std::fmt::Result {
439    use facet_core::{PointerType, TextualType};
440
441    let shape = peek.shape();
442
443    match (shape.def, shape.ty) {
444        // Strings: write with quotes
445        (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
446            write!(w, "\"{}\"", peek.get::<str>().unwrap())
447        }
448        // String type (owned)
449        (Def::Scalar, _) if shape.id == <String as facet_core::Facet>::SHAPE.id => {
450            write!(w, "\"{}\"", peek.get::<String>().unwrap())
451        }
452        // Reference to str (&str) - check if target is str
453        (_, Type::Pointer(PointerType::Reference(ptr)))
454            if matches!(
455                ptr.target.ty,
456                Type::Primitive(PrimitiveType::Textual(TextualType::Str))
457            ) =>
458        {
459            // Use Display which will show the string content
460            write!(w, "\"{}\"", peek)
461        }
462        // Booleans
463        (Def::Scalar, Type::Primitive(PrimitiveType::Boolean)) => {
464            let b = peek.get::<bool>().unwrap();
465            write!(w, "{}", if *b { "true" } else { "false" })
466        }
467        // Chars: show with single quotes for Rust
468        (Def::Scalar, Type::Primitive(PrimitiveType::Textual(TextualType::Char))) => {
469            write!(w, "'{}'", peek.get::<char>().unwrap())
470        }
471        // Everything else: use Display if available, else Debug
472        _ => {
473            if shape.is_display() {
474                write!(w, "{}", peek)
475            } else if shape.is_debug() {
476                write!(w, "{:?}", peek)
477            } else {
478                write!(w, "<{}>", shape.type_identifier)
479            }
480        }
481    }
482}
483
484/// Value formatting without quotes (XML style - quotes come from attribute syntax).
485fn format_value_raw(peek: Peek<'_, '_>, w: &mut dyn Write) -> std::fmt::Result {
486    use facet_core::{DynValueKind, TextualType};
487
488    let shape = peek.shape();
489
490    match (shape.def, shape.ty) {
491        // Strings: write raw content (no quotes)
492        (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
493            write!(w, "{}", peek.get::<str>().unwrap())
494        }
495        // String type (owned)
496        (Def::Scalar, _) if shape.id == <String as facet_core::Facet>::SHAPE.id => {
497            write!(w, "{}", peek.get::<String>().unwrap())
498        }
499        // Booleans
500        (Def::Scalar, Type::Primitive(PrimitiveType::Boolean)) => {
501            let b = peek.get::<bool>().unwrap();
502            write!(w, "{}", if *b { "true" } else { "false" })
503        }
504        // Chars: show as-is
505        (Def::Scalar, Type::Primitive(PrimitiveType::Textual(TextualType::Char))) => {
506            write!(w, "{}", peek.get::<char>().unwrap())
507        }
508        // Dynamic values: handle based on their kind
509        (Def::DynamicValue(_), _) => {
510            // Write string without quotes for XML
511            if let Ok(dv) = peek.into_dynamic_value()
512                && dv.kind() == DynValueKind::String
513                && let Some(s) = dv.as_str()
514            {
515                return write!(w, "{}", s);
516            }
517            // Fall back to Display for other dynamic values
518            if shape.is_display() {
519                write!(w, "{}", peek)
520            } else if shape.is_debug() {
521                write!(w, "{:?}", peek)
522            } else {
523                write!(w, "<{}>", shape.type_identifier)
524            }
525        }
526        // Everything else: use Display if available, else Debug
527        _ => {
528            if shape.is_display() {
529                write!(w, "{}", peek)
530            } else if shape.is_debug() {
531                write!(w, "{:?}", peek)
532            } else {
533                write!(w, "<{}>", shape.type_identifier)
534            }
535        }
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    use facet::Facet;
543    use facet_core::{Shape, Type, UserType};
544
545    // Helper to get field from a struct shape
546    fn get_field<'a>(shape: &'a Shape, name: &str) -> &'a Field {
547        if let Type::User(UserType::Struct(st)) = shape.ty {
548            st.fields.iter().find(|f| f.name == name).unwrap()
549        } else {
550            panic!("expected struct type")
551        }
552    }
553
554    #[test]
555    fn test_rust_flavor_field_presentation() {
556        #[derive(Facet)]
557        struct Point {
558            x: i32,
559            y: i32,
560        }
561
562        let shape = <Point as Facet>::SHAPE;
563        let flavor = RustFlavor;
564
565        let x_field = get_field(shape, "x");
566        let y_field = get_field(shape, "y");
567
568        // Rust flavor: all fields are attributes
569        assert_eq!(
570            flavor.field_presentation(x_field),
571            FieldPresentation::Attribute {
572                name: Cow::Borrowed("x")
573            }
574        );
575        assert_eq!(
576            flavor.field_presentation(y_field),
577            FieldPresentation::Attribute {
578                name: Cow::Borrowed("y")
579            }
580        );
581    }
582
583    #[test]
584    fn test_json_flavor_field_presentation() {
585        #[derive(Facet)]
586        struct Point {
587            x: i32,
588            y: i32,
589        }
590
591        let shape = <Point as Facet>::SHAPE;
592        let flavor = JsonFlavor;
593
594        let x_field = get_field(shape, "x");
595
596        // JSON flavor: all fields are attributes
597        assert_eq!(
598            flavor.field_presentation(x_field),
599            FieldPresentation::Attribute {
600                name: Cow::Borrowed("x")
601            }
602        );
603    }
604
605    #[test]
606    fn test_xml_flavor_field_presentation_default() {
607        // Without XML attributes, fields default to Child
608        #[derive(Facet)]
609        struct Book {
610            title: String,
611            author: String,
612        }
613
614        let shape = <Book as Facet>::SHAPE;
615        let flavor = XmlFlavor;
616
617        let title_field = get_field(shape, "title");
618
619        // XML default: child element
620        assert_eq!(
621            flavor.field_presentation(title_field),
622            FieldPresentation::Child {
623                name: Cow::Borrowed("title")
624            }
625        );
626    }
627
628    #[test]
629    fn test_xml_flavor_field_presentation_with_attrs() {
630        use facet_xml as xml;
631
632        #[derive(Facet)]
633        struct Element {
634            #[facet(xml::attribute)]
635            id: String,
636            #[facet(xml::element)]
637            title: String,
638            #[facet(xml::text)]
639            content: String,
640            #[facet(xml::elements)]
641            items: Vec<String>,
642        }
643
644        let shape = <Element as Facet>::SHAPE;
645        let flavor = XmlFlavor;
646
647        let id_field = get_field(shape, "id");
648        let title_field = get_field(shape, "title");
649        let content_field = get_field(shape, "content");
650        let items_field = get_field(shape, "items");
651
652        assert_eq!(
653            flavor.field_presentation(id_field),
654            FieldPresentation::Attribute {
655                name: Cow::Borrowed("id")
656            }
657        );
658
659        assert_eq!(
660            flavor.field_presentation(title_field),
661            FieldPresentation::Child {
662                name: Cow::Borrowed("title")
663            }
664        );
665
666        assert_eq!(
667            flavor.field_presentation(content_field),
668            FieldPresentation::TextContent
669        );
670
671        assert_eq!(
672            flavor.field_presentation(items_field),
673            FieldPresentation::Children {
674                item_name: Cow::Borrowed("items")
675            }
676        );
677    }
678
679    fn format_to_string<F: DiffFlavor>(flavor: &F, peek: Peek<'_, '_>) -> String {
680        let mut buf = String::new();
681        flavor.format_value(peek, &mut buf).unwrap();
682        buf
683    }
684
685    #[test]
686    fn test_format_value_integers() {
687        let value = 42i32;
688        let peek = Peek::new(&value);
689
690        assert_eq!(format_to_string(&RustFlavor, peek), "42");
691        assert_eq!(format_to_string(&JsonFlavor, peek), "42");
692        assert_eq!(format_to_string(&XmlFlavor, peek), "42");
693    }
694
695    #[test]
696    fn test_format_value_strings() {
697        let value = "hello";
698        let peek = Peek::new(&value);
699
700        // Rust/JSON add quotes around strings, XML doesn't (quotes come from attr syntax)
701        assert_eq!(format_to_string(&RustFlavor, peek), "\"hello\"");
702        assert_eq!(format_to_string(&JsonFlavor, peek), "\"hello\"");
703        assert_eq!(format_to_string(&XmlFlavor, peek), "hello");
704    }
705
706    #[test]
707    fn test_format_value_booleans() {
708        let t = true;
709        let f = false;
710
711        assert_eq!(format_to_string(&RustFlavor, Peek::new(&t)), "true");
712        assert_eq!(format_to_string(&RustFlavor, Peek::new(&f)), "false");
713        assert_eq!(format_to_string(&JsonFlavor, Peek::new(&t)), "true");
714        assert_eq!(format_to_string(&JsonFlavor, Peek::new(&f)), "false");
715        assert_eq!(format_to_string(&XmlFlavor, Peek::new(&t)), "true");
716        assert_eq!(format_to_string(&XmlFlavor, Peek::new(&f)), "false");
717    }
718
719    #[test]
720    fn test_syntax_methods() {
721        let rust = RustFlavor;
722        let json = JsonFlavor;
723        let xml = XmlFlavor;
724
725        // struct_open
726        assert_eq!(rust.struct_open("Point"), "Point {");
727        assert_eq!(json.struct_open("Point"), "{");
728        assert_eq!(xml.struct_open("Point"), "<Point");
729
730        // type_comment (rendered separately in muted color)
731        assert_eq!(rust.type_comment("Point"), None);
732        assert_eq!(json.type_comment("Point"), Some("/* Point */".to_string()));
733        assert_eq!(xml.type_comment("Point"), None);
734
735        // struct_close (non-self-closing)
736        assert_eq!(rust.struct_close("Point", false), "}");
737        assert_eq!(json.struct_close("Point", false), "}");
738        assert_eq!(xml.struct_close("Point", false), "</Point>");
739
740        // struct_close (self-closing)
741        assert_eq!(rust.struct_close("Point", true), "}");
742        assert_eq!(json.struct_close("Point", true), "}");
743        assert_eq!(xml.struct_close("Point", true), "/>");
744
745        // field_separator
746        assert_eq!(rust.field_separator(), ", ");
747        assert_eq!(json.field_separator(), ", ");
748        assert_eq!(xml.field_separator(), " ");
749
750        // seq_open/close
751        assert_eq!(rust.seq_open(), "[");
752        assert_eq!(rust.seq_close(), "]");
753        assert_eq!(json.seq_open(), "[");
754        assert_eq!(json.seq_close(), "]");
755        assert_eq!(xml.seq_open(), "<items>");
756        assert_eq!(xml.seq_close(), "</items>");
757
758        // comment
759        assert_eq!(rust.comment("5 more"), "/* 5 more */");
760        assert_eq!(json.comment("5 more"), "// 5 more");
761        assert_eq!(xml.comment("5 more"), "<!-- 5 more -->");
762
763        // format_field
764        assert_eq!(rust.format_field("x", "10"), "x: 10");
765        assert_eq!(json.format_field("x", "10"), "\"x\": 10");
766        assert_eq!(xml.format_field("x", "10"), "x=\"10\"");
767    }
768}