Skip to main content

libconfig/
ser.rs

1//! Serializer for libconfig format
2
3use serde::{Serialize, ser};
4
5use crate::error::{Error, ErrorKind, Result};
6
7/// Returns true if `name` is a valid setting name per the grammar:
8/// `[A-Za-z*][-A-Za-z0-9_*]*`
9pub(crate) fn is_valid_name(name: &str) -> bool {
10    let mut chars = name.chars();
11    match chars.next() {
12        Some(c) if c.is_ascii_alphabetic() || c == '*' => {}
13        _ => return false,
14    }
15    chars.all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '*'))
16}
17
18fn validate_name(name: &str) -> Result<()> {
19    if is_valid_name(name) {
20        Ok(())
21    } else {
22        Err(ErrorKind::InvalidSettingName(name.to_string()).into())
23    }
24}
25
26/// Appends `s` to `out` as a quoted, escaped libconfig string.
27///
28/// Uses the named escapes from the manual (section 2.9): `\"`, `\\`, `\n`,
29/// `\r`, `\t`, `\f`, `\a`, `\b`, `\v`. Control characters without a named
30/// escape are written as `\xNN`.
31pub(crate) fn escape_str_into(out: &mut String, s: &str) {
32    out.push('"');
33    for ch in s.chars() {
34        match ch {
35            '"' => out.push_str("\\\""),
36            '\\' => out.push_str("\\\\"),
37            '\n' => out.push_str("\\n"),
38            '\r' => out.push_str("\\r"),
39            '\t' => out.push_str("\\t"),
40            '\x0C' => out.push_str("\\f"),
41            '\x08' => out.push_str("\\b"),
42            '\x0B' => out.push_str("\\v"),
43            '\x07' => out.push_str("\\a"),
44            c if (c as u32) < 0x20 => {
45                out.push_str(&format!("\\x{:02x}", c as u32));
46            }
47            _ => out.push(ch),
48        }
49    }
50    out.push('"');
51}
52
53/// Output formatting options, mirroring the writer options of the C API
54/// (`config_set_options`, `config_set_float_precision`,
55/// `config_set_tab_width` — manual chapter 3). Every combination produces
56/// grammar-valid output; the options only choose among equivalent spellings.
57///
58/// The defaults keep this crate's canonical output: `=` assignments,
59/// semicolon terminators, opening braces on the same line, 2-space
60/// indentation, and floats in the shortest decimal form that round-trips
61/// exactly. (The C library's writer defaults differ cosmetically: it uses
62/// `:` for groups, puts `{` on its own line, and writes floats with 6
63/// fixed digits.)
64///
65/// # Examples
66///
67/// ```
68/// use libconfig::{Config, WriteOptions};
69///
70/// let config = Config::from_str("port = 80; db = { host = \"h\"; };").unwrap();
71/// let opts = WriteOptions::new()
72///     .colon_assignment_for_groups(true)
73///     .tab_width(4);
74/// let text = config.to_string_with_options(&opts).unwrap();
75/// assert!(text.contains("db :"));
76/// assert!(text.contains("    host"));
77/// ```
78#[derive(Debug, Clone, PartialEq)]
79#[must_use]
80pub struct WriteOptions {
81    float_precision: Option<u8>,
82    scientific_notation: bool,
83    tab_width: u8,
84    semicolon_separators: bool,
85    colon_assignment_for_groups: bool,
86    colon_assignment_for_non_groups: bool,
87    open_brace_on_separate_line: bool,
88}
89
90impl Default for WriteOptions {
91    fn default() -> Self {
92        WriteOptions {
93            float_precision: None,
94            scientific_notation: false,
95            tab_width: 2,
96            semicolon_separators: true,
97            colon_assignment_for_groups: false,
98            colon_assignment_for_non_groups: false,
99            open_brace_on_separate_line: false,
100        }
101    }
102}
103
104impl WriteOptions {
105    /// Creates the default options (see the type-level documentation).
106    pub fn new() -> Self {
107        WriteOptions::default()
108    }
109
110    /// Writes floats with a fixed number of digits after the decimal point
111    /// (the C API's `config_set_float_precision`; clamped to 17). The
112    /// default is the shortest representation that round-trips exactly.
113    /// A decimal point is always kept so values re-read as floats, even
114    /// with a precision of 0.
115    pub fn float_precision(mut self, digits: u8) -> Self {
116        self.float_precision = Some(digits.min(17));
117        self
118    }
119
120    /// When enabled, floats are written in exponent notation (`2.5e3`),
121    /// like the C API's `CONFIG_OPTION_ALLOW_SCIENTIFIC_NOTATION`.
122    /// Default: off.
123    pub fn scientific_notation(mut self, enabled: bool) -> Self {
124        self.scientific_notation = enabled;
125        self
126    }
127
128    /// Spaces of indentation per nesting level, or a single tab character
129    /// per level when 0 (the C API's `config_set_tab_width`; clamped to 15).
130    /// Default: 2.
131    pub fn tab_width(mut self, width: u8) -> Self {
132        self.tab_width = width.min(15);
133        self
134    }
135
136    /// Whether settings are terminated with `;` (the grammar makes the
137    /// terminator optional). Default: on.
138    pub fn semicolon_separators(mut self, enabled: bool) -> Self {
139        self.semicolon_separators = enabled;
140        self
141    }
142
143    /// Writes `name : { ... }` instead of `name = { ... }` for group-valued
144    /// settings (`=` and `:` are interchangeable per the grammar).
145    /// Default: off (the C library defaults to on).
146    pub fn colon_assignment_for_groups(mut self, enabled: bool) -> Self {
147        self.colon_assignment_for_groups = enabled;
148        self
149    }
150
151    /// Writes `name : value` instead of `name = value` for non-group
152    /// settings. Default: off.
153    pub fn colon_assignment_for_non_groups(mut self, enabled: bool) -> Self {
154        self.colon_assignment_for_non_groups = enabled;
155        self
156    }
157
158    /// Writes the opening `{` of a group on its own line. Default: off
159    /// (the C library defaults to on).
160    pub fn open_brace_on_separate_line(mut self, enabled: bool) -> Self {
161        self.open_brace_on_separate_line = enabled;
162        self
163    }
164}
165
166/// Appends `v` to `out` in libconfig float syntax. A decimal point or
167/// exponent is always present so the value round-trips as a float. Errors
168/// on non-finite values, which have no representation in the grammar.
169pub(crate) fn write_f64(out: &mut String, v: f64, options: &WriteOptions) -> Result<()> {
170    if !v.is_finite() {
171        return Err(ErrorKind::NonFiniteFloat.into());
172    }
173    let s = match (options.float_precision, options.scientific_notation) {
174        (None, false) => v.to_string(),
175        (None, true) => format!("{:e}", v),
176        (Some(p), false) => format!("{:.*}", p as usize, v),
177        (Some(p), true) => format!("{:.*e}", p as usize, v),
178    };
179    out.push_str(&s);
180    if !s.contains('.') && !s.contains('e') && !s.contains('E') {
181        out.push_str(".0");
182    }
183    Ok(())
184}
185
186/// A structure for serializing Rust values into libconfig format
187pub struct Serializer {
188    output: String,
189    indent_level: usize,
190    options: WriteOptions,
191    /// True until the first value is serialized. A map or struct at the root
192    /// is written as an implicit setting list (no braces), matching the
193    /// grammar: braces only delimit *nested* groups.
194    at_root: bool,
195}
196
197impl Serializer {
198    /// Creates a serializer with an empty output buffer and default options.
199    pub fn new() -> Self {
200        Serializer::with_options(WriteOptions::default())
201    }
202
203    /// Creates a serializer using the given [`WriteOptions`].
204    pub fn with_options(options: WriteOptions) -> Self {
205        Serializer {
206            output: String::new(),
207            indent_level: 0,
208            options,
209            at_root: true,
210        }
211    }
212
213    /// Consumes the serializer and returns the produced libconfig text.
214    pub fn into_output(self) -> String {
215        self.output
216    }
217
218    fn indent(&mut self) {
219        self.indent_level += 1;
220    }
221
222    fn dedent(&mut self) {
223        self.indent_level = self.indent_level.saturating_sub(1);
224    }
225
226    fn write_indent(&mut self) {
227        if self.options.tab_width == 0 {
228            for _ in 0..self.indent_level {
229                self.output.push('\t');
230            }
231        } else {
232            for _ in 0..(self.indent_level * self.options.tab_width as usize) {
233                self.output.push(' ');
234            }
235        }
236    }
237}
238
239impl Default for Serializer {
240    fn default() -> Self {
241        Serializer::new()
242    }
243}
244
245/// Serialize the given value into a libconfig string.
246///
247/// # Errors
248///
249/// Returns an error if the value cannot be represented in libconfig (e.g.
250/// non-finite floats, `u64` values beyond `i64::MAX`, invalid setting
251/// names, or a bare `None`).
252pub fn to_string<T>(value: &T) -> Result<String>
253where
254    T: ?Sized + Serialize,
255{
256    to_string_with_options(value, &WriteOptions::default())
257}
258
259/// Serialize the given value into a libconfig string using the given
260/// [`WriteOptions`].
261///
262/// # Errors
263///
264/// As for [`to_string`].
265pub fn to_string_with_options<T>(value: &T, options: &WriteOptions) -> Result<String>
266where
267    T: ?Sized + Serialize,
268{
269    let mut serializer = Serializer::with_options(options.clone());
270    value.serialize(&mut serializer)?;
271    Ok(serializer.output)
272}
273
274/// Serialize the given value as libconfig text into the given writer.
275///
276/// # Errors
277///
278/// Returns an error if serialization fails (see [`to_string`]) or if the
279/// writer returns an I/O error ([`crate::ErrorKind::Io`]).
280pub fn to_writer<W, T>(mut writer: W, value: &T) -> Result<()>
281where
282    W: std::io::Write,
283    T: ?Sized + Serialize,
284{
285    let s = to_string(value)?;
286    writer
287        .write_all(s.as_bytes())
288        .map_err(|e| ErrorKind::Io(e.to_string()))?;
289    Ok(())
290}
291
292/// Probe serializer that reports whether a value is a bare `Option::None`.
293struct NoneProbe;
294
295fn value_is_none<T: ?Sized + Serialize>(value: &T) -> bool {
296    value.serialize(NoneProbe).unwrap_or(false)
297}
298
299/// Probe serializer that reports whether a value serializes as a group
300/// (`{ ... }`), which is what the colon-assignment and brace-placement
301/// options key on. Sets the flag and halts without serializing content.
302struct GroupProbe<'a> {
303    is_group: &'a mut bool,
304}
305
306fn value_is_group<T: ?Sized + Serialize>(value: &T) -> bool {
307    let mut is_group = false;
308    let _ = value.serialize(GroupProbe {
309        is_group: &mut is_group,
310    });
311    is_group
312}
313
314macro_rules! group_probe_false {
315    ($($method:ident: $ty:ty,)*) => {
316        $(fn $method(self, _v: $ty) -> Result<()> { Ok(()) })*
317    };
318}
319
320impl ser::Serializer for GroupProbe<'_> {
321    type Ok = ();
322    type Error = Error;
323
324    type SerializeSeq = ser::Impossible<(), Error>;
325    type SerializeTuple = ser::Impossible<(), Error>;
326    type SerializeTupleStruct = ser::Impossible<(), Error>;
327    type SerializeTupleVariant = ser::Impossible<(), Error>;
328    type SerializeMap = ser::Impossible<(), Error>;
329    type SerializeStruct = ser::Impossible<(), Error>;
330    type SerializeStructVariant = ser::Impossible<(), Error>;
331
332    group_probe_false! {
333        serialize_bool: bool, serialize_i8: i8, serialize_i16: i16,
334        serialize_i32: i32, serialize_i64: i64, serialize_u8: u8,
335        serialize_u16: u16, serialize_u32: u32, serialize_u64: u64,
336        serialize_f32: f32, serialize_f64: f64, serialize_char: char,
337        serialize_str: &str, serialize_bytes: &[u8],
338    }
339
340    fn serialize_none(self) -> Result<()> {
341        Ok(())
342    }
343
344    fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
345        // Transparent: probe the inner value
346        value.serialize(GroupProbe {
347            is_group: self.is_group,
348        })
349    }
350
351    fn serialize_unit(self) -> Result<()> {
352        Ok(())
353    }
354
355    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
356        Ok(())
357    }
358
359    fn serialize_unit_variant(
360        self,
361        _name: &'static str,
362        _variant_index: u32,
363        _variant: &'static str,
364    ) -> Result<()> {
365        Ok(())
366    }
367
368    fn serialize_newtype_struct<T: ?Sized + Serialize>(
369        self,
370        _name: &'static str,
371        value: &T,
372    ) -> Result<()> {
373        value.serialize(GroupProbe {
374            is_group: self.is_group,
375        })
376    }
377
378    fn serialize_newtype_variant<T: ?Sized + Serialize>(
379        self,
380        _name: &'static str,
381        _variant_index: u32,
382        _variant: &'static str,
383        _value: &T,
384    ) -> Result<()> {
385        // Data-carrying variants are written as single-setting groups
386        *self.is_group = true;
387        Ok(())
388    }
389
390    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
391        Err(ErrorKind::Message("probe".to_string()).into())
392    }
393
394    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
395        Err(ErrorKind::Message("probe".to_string()).into())
396    }
397
398    fn serialize_tuple_struct(
399        self,
400        _name: &'static str,
401        _len: usize,
402    ) -> Result<Self::SerializeTupleStruct> {
403        Err(ErrorKind::Message("probe".to_string()).into())
404    }
405
406    fn serialize_tuple_variant(
407        self,
408        _name: &'static str,
409        _variant_index: u32,
410        _variant: &'static str,
411        _len: usize,
412    ) -> Result<Self::SerializeTupleVariant> {
413        *self.is_group = true;
414        Err(ErrorKind::Message("probe".to_string()).into())
415    }
416
417    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
418        *self.is_group = true;
419        Err(ErrorKind::Message("probe".to_string()).into())
420    }
421
422    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
423        *self.is_group = true;
424        Err(ErrorKind::Message("probe".to_string()).into())
425    }
426
427    fn serialize_struct_variant(
428        self,
429        _name: &'static str,
430        _variant_index: u32,
431        _variant: &'static str,
432        _len: usize,
433    ) -> Result<Self::SerializeStructVariant> {
434        *self.is_group = true;
435        Err(ErrorKind::Message("probe".to_string()).into())
436    }
437}
438
439macro_rules! probe_false {
440    ($($method:ident: $ty:ty,)*) => {
441        $(fn $method(self, _v: $ty) -> Result<bool> { Ok(false) })*
442    };
443}
444
445impl ser::Serializer for NoneProbe {
446    type Ok = bool;
447    type Error = Error;
448
449    type SerializeSeq = ser::Impossible<bool, Error>;
450    type SerializeTuple = ser::Impossible<bool, Error>;
451    type SerializeTupleStruct = ser::Impossible<bool, Error>;
452    type SerializeTupleVariant = ser::Impossible<bool, Error>;
453    type SerializeMap = ser::Impossible<bool, Error>;
454    type SerializeStruct = ser::Impossible<bool, Error>;
455    type SerializeStructVariant = ser::Impossible<bool, Error>;
456
457    fn serialize_none(self) -> Result<bool> {
458        Ok(true)
459    }
460
461    fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<bool> {
462        Ok(false)
463    }
464
465    probe_false! {
466        serialize_bool: bool, serialize_i8: i8, serialize_i16: i16,
467        serialize_i32: i32, serialize_i64: i64, serialize_u8: u8,
468        serialize_u16: u16, serialize_u32: u32, serialize_u64: u64,
469        serialize_f32: f32, serialize_f64: f64, serialize_char: char,
470        serialize_str: &str, serialize_bytes: &[u8],
471    }
472
473    fn serialize_unit(self) -> Result<bool> {
474        Ok(false)
475    }
476
477    fn serialize_unit_struct(self, _name: &'static str) -> Result<bool> {
478        Ok(false)
479    }
480
481    fn serialize_unit_variant(
482        self,
483        _name: &'static str,
484        _variant_index: u32,
485        _variant: &'static str,
486    ) -> Result<bool> {
487        Ok(false)
488    }
489
490    fn serialize_newtype_struct<T: ?Sized + Serialize>(
491        self,
492        _name: &'static str,
493        _value: &T,
494    ) -> Result<bool> {
495        Ok(false)
496    }
497
498    fn serialize_newtype_variant<T: ?Sized + Serialize>(
499        self,
500        _name: &'static str,
501        _variant_index: u32,
502        _variant: &'static str,
503        _value: &T,
504    ) -> Result<bool> {
505        Ok(false)
506    }
507
508    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
509        Err(ErrorKind::Message("probe".to_string()).into())
510    }
511
512    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
513        Err(ErrorKind::Message("probe".to_string()).into())
514    }
515
516    fn serialize_tuple_struct(
517        self,
518        _name: &'static str,
519        _len: usize,
520    ) -> Result<Self::SerializeTupleStruct> {
521        Err(ErrorKind::Message("probe".to_string()).into())
522    }
523
524    fn serialize_tuple_variant(
525        self,
526        _name: &'static str,
527        _variant_index: u32,
528        _variant: &'static str,
529        _len: usize,
530    ) -> Result<Self::SerializeTupleVariant> {
531        Err(ErrorKind::Message("probe".to_string()).into())
532    }
533
534    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
535        Err(ErrorKind::Message("probe".to_string()).into())
536    }
537
538    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
539        Err(ErrorKind::Message("probe".to_string()).into())
540    }
541
542    fn serialize_struct_variant(
543        self,
544        _name: &'static str,
545        _variant_index: u32,
546        _variant: &'static str,
547        _len: usize,
548    ) -> Result<Self::SerializeStructVariant> {
549        Err(ErrorKind::Message("probe".to_string()).into())
550    }
551}
552
553impl<'a> ser::Serializer for &'a mut Serializer {
554    type Ok = ();
555    type Error = Error;
556
557    type SerializeSeq = SeqSerializer<'a>;
558    type SerializeTuple = Self;
559    type SerializeTupleStruct = Self;
560    type SerializeTupleVariant = TupleVariantSerializer<'a>;
561    type SerializeMap = MapSerializer<'a>;
562    type SerializeStruct = MapSerializer<'a>;
563    type SerializeStructVariant = StructVariantSerializer<'a>;
564
565    fn serialize_bool(self, v: bool) -> Result<()> {
566        self.output += if v { "true" } else { "false" };
567        Ok(())
568    }
569
570    fn serialize_i8(self, v: i8) -> Result<()> {
571        self.serialize_i32(i32::from(v))
572    }
573
574    fn serialize_i16(self, v: i16) -> Result<()> {
575        self.serialize_i32(i32::from(v))
576    }
577
578    fn serialize_i32(self, v: i32) -> Result<()> {
579        self.output += &v.to_string();
580        Ok(())
581    }
582
583    fn serialize_i64(self, v: i64) -> Result<()> {
584        self.output += &v.to_string();
585        self.output += "L";
586        Ok(())
587    }
588
589    fn serialize_u8(self, v: u8) -> Result<()> {
590        self.serialize_u32(u32::from(v))
591    }
592
593    fn serialize_u16(self, v: u16) -> Result<()> {
594        self.serialize_u32(u32::from(v))
595    }
596
597    fn serialize_u32(self, v: u32) -> Result<()> {
598        self.output += &v.to_string();
599        // A value exceeding 32-bit range is an integer64 (manual section
600        // 2.6), and 'L' is the manual's marker for 64-bit values — write it
601        // explicitly, as serialize_u64/serialize_i64 do
602        if v > i32::MAX as u32 {
603            self.output += "L";
604        }
605        Ok(())
606    }
607
608    fn serialize_u64(self, v: u64) -> Result<()> {
609        // libconfig's integer64 is a signed long long; larger values have no
610        // representation and would not parse back.
611        if v > i64::MAX as u64 {
612            return Err(ErrorKind::IntegerOutOfRange.into());
613        }
614        self.output += &v.to_string();
615        self.output += "L";
616        Ok(())
617    }
618
619    fn serialize_f32(self, v: f32) -> Result<()> {
620        self.serialize_f64(f64::from(v))
621    }
622
623    fn serialize_f64(self, v: f64) -> Result<()> {
624        write_f64(&mut self.output, v, &self.options)
625    }
626
627    fn serialize_char(self, v: char) -> Result<()> {
628        self.serialize_str(&v.to_string())
629    }
630
631    fn serialize_str(self, v: &str) -> Result<()> {
632        escape_str_into(&mut self.output, v);
633        Ok(())
634    }
635
636    fn serialize_bytes(self, v: &[u8]) -> Result<()> {
637        use ser::SerializeSeq;
638        let mut seq = self.serialize_seq(Some(v.len()))?;
639        for byte in v {
640            seq.serialize_element(byte)?;
641        }
642        seq.end()
643    }
644
645    fn serialize_none(self) -> Result<()> {
646        Err(ErrorKind::TypeNotSupported("Option::None".to_string()).into())
647    }
648
649    fn serialize_some<T>(self, value: &T) -> Result<()>
650    where
651        T: ?Sized + Serialize,
652    {
653        value.serialize(self)
654    }
655
656    fn serialize_unit(self) -> Result<()> {
657        Err(ErrorKind::UnitNotSupported.into())
658    }
659
660    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
661        self.serialize_unit()
662    }
663
664    fn serialize_unit_variant(
665        self,
666        _name: &'static str,
667        _variant_index: u32,
668        variant: &'static str,
669    ) -> Result<()> {
670        self.serialize_str(variant)
671    }
672
673    fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<()>
674    where
675        T: ?Sized + Serialize,
676    {
677        value.serialize(self)
678    }
679
680    fn serialize_newtype_variant<T>(
681        self,
682        _name: &'static str,
683        _variant_index: u32,
684        variant: &'static str,
685        value: &T,
686    ) -> Result<()>
687    where
688        T: ?Sized + Serialize,
689    {
690        // Data-carrying variants are written as a single-setting group so the
691        // output is a valid value in any position and can round-trip
692        validate_name(variant)?;
693        self.at_root = false;
694        self.output += "{ ";
695        self.output += variant;
696        self.output += " = ";
697        value.serialize(&mut *self)?;
698        self.output += "; }";
699        Ok(())
700    }
701
702    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
703        self.at_root = false;
704        Ok(SeqSerializer {
705            elements: Vec::with_capacity(len.unwrap_or(0)),
706            ser: self,
707        })
708    }
709
710    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
711        self.at_root = false;
712        self.output.push('(');
713        Ok(self)
714    }
715
716    fn serialize_tuple_struct(
717        self,
718        _name: &'static str,
719        _len: usize,
720    ) -> Result<Self::SerializeTupleStruct> {
721        self.at_root = false;
722        self.output.push('(');
723        Ok(self)
724    }
725
726    fn serialize_tuple_variant(
727        self,
728        _name: &'static str,
729        _variant_index: u32,
730        variant: &'static str,
731        _len: usize,
732    ) -> Result<Self::SerializeTupleVariant> {
733        validate_name(variant)?;
734        self.at_root = false;
735        self.output += "{ ";
736        self.output += variant;
737        self.output += " = (";
738        Ok(TupleVariantSerializer { ser: self })
739    }
740
741    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
742        let root = std::mem::replace(&mut self.at_root, false);
743        if !root {
744            if self.options.open_brace_on_separate_line {
745                self.output.push('\n');
746                self.write_indent();
747            }
748            self.output.push('{');
749            self.indent();
750        }
751        Ok(MapSerializer {
752            ser: self,
753            root,
754            first: true,
755            pending_key: None,
756        })
757    }
758
759    fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
760        self.serialize_map(Some(len))
761    }
762
763    fn serialize_struct_variant(
764        self,
765        _name: &'static str,
766        _variant_index: u32,
767        variant: &'static str,
768        _len: usize,
769    ) -> Result<Self::SerializeStructVariant> {
770        validate_name(variant)?;
771        self.at_root = false;
772        self.output += "{ ";
773        self.output += variant;
774        self.output += " = {";
775        self.indent();
776        Ok(StructVariantSerializer { ser: self })
777    }
778}
779
780/// Serializes maps and structs. At the document root, settings are written
781/// as an implicit list (`a = 1;\nb = 2;`) because the libconfig grammar has
782/// no braces around the top level; nested groups are brace-delimited.
783#[doc(hidden)]
784pub struct MapSerializer<'a> {
785    ser: &'a mut Serializer,
786    root: bool,
787    first: bool,
788    pending_key: Option<String>,
789}
790
791impl MapSerializer<'_> {
792    fn begin_field(&mut self) {
793        let sep = self.ser.options.semicolon_separators;
794        if self.root {
795            if !self.first {
796                if sep {
797                    self.ser.output.push(';');
798                }
799                self.ser.output.push('\n');
800            }
801        } else {
802            if !self.first && sep {
803                self.ser.output.push(';');
804            }
805            self.ser.output.push('\n');
806            self.ser.write_indent();
807        }
808        self.first = false;
809    }
810
811    /// Writes `name` and the assignment token, honoring the colon-assignment
812    /// and brace-placement options.
813    fn write_assignment(&mut self, key: &str, value_is_group: bool) {
814        self.ser.output += key;
815        let colon = if value_is_group {
816            self.ser.options.colon_assignment_for_groups
817        } else {
818            self.ser.options.colon_assignment_for_non_groups
819        };
820        self.ser.output += if colon { " :" } else { " =" };
821        // When the brace goes on its own line, the group starts with a
822        // newline; a trailing space would dangle at the end of the line
823        if !(value_is_group && self.ser.options.open_brace_on_separate_line) {
824            self.ser.output.push(' ');
825        }
826    }
827
828    fn finish(self) -> Result<()> {
829        let sep = self.ser.options.semicolon_separators;
830        if self.root {
831            if !self.first && sep {
832                self.ser.output.push(';');
833            }
834        } else {
835            self.ser.dedent();
836            if !self.first && sep {
837                self.ser.output.push(';');
838            }
839            self.ser.output.push('\n');
840            self.ser.write_indent();
841            self.ser.output.push('}');
842        }
843        Ok(())
844    }
845}
846
847impl ser::SerializeMap for MapSerializer<'_> {
848    type Ok = ();
849    type Error = Error;
850
851    fn serialize_key<T>(&mut self, key: &T) -> Result<()>
852    where
853        T: ?Sized + Serialize,
854    {
855        // Validate and buffer the key; it is only written once the value is
856        // known not to be None
857        let mut key_capture = KeyCapture { key: String::new() };
858        key.serialize(&mut key_capture)?;
859        self.pending_key = Some(key_capture.key);
860        Ok(())
861    }
862
863    fn serialize_value<T>(&mut self, value: &T) -> Result<()>
864    where
865        T: ?Sized + Serialize,
866    {
867        let key = self.pending_key.take().ok_or_else(|| {
868            Error::from(ErrorKind::Message(
869                "serialize_value called without serialize_key".to_string(),
870            ))
871        })?;
872        // None means "omit the setting": an absent setting deserializes
873        // back to None, so the round-trip is symmetric
874        if value_is_none(value) {
875            return Ok(());
876        }
877        self.begin_field();
878        self.write_assignment(&key, value_is_group(value));
879        value.serialize(&mut *self.ser)
880    }
881
882    fn end(self) -> Result<()> {
883        self.finish()
884    }
885}
886
887impl ser::SerializeStruct for MapSerializer<'_> {
888    type Ok = ();
889    type Error = Error;
890
891    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
892    where
893        T: ?Sized + Serialize,
894    {
895        validate_name(key)?;
896        if value_is_none(value) {
897            return Ok(());
898        }
899        self.begin_field();
900        self.write_assignment(key, value_is_group(value));
901        value.serialize(&mut *self.ser)
902    }
903
904    fn end(self) -> Result<()> {
905        self.finish()
906    }
907}
908
909/// The kind of a serialized sequence element, used to decide whether the
910/// sequence can be written as an array (homogeneous scalars) or must be
911/// written as a list.
912#[derive(Clone, Copy, PartialEq)]
913enum ElemKind {
914    Bool,
915    Integer,
916    Integer64,
917    Float,
918    Str,
919    NonScalar,
920}
921
922impl ElemKind {
923    fn classify(rendered: &str) -> ElemKind {
924        match rendered.as_bytes().first() {
925            Some(b'"') => ElemKind::Str,
926            Some(b'{' | b'(' | b'[' | b'\n') => ElemKind::NonScalar,
927            Some(b't' | b'f') => ElemKind::Bool,
928            // Floats always carry a decimal point or an exponent (the
929            // exponent form appears when scientific notation is enabled)
930            _ if rendered.contains('.') || rendered.contains('e') || rendered.contains('E') => {
931                ElemKind::Float
932            }
933            _ if rendered.ends_with('L') => ElemKind::Integer64,
934            _ => ElemKind::Integer,
935        }
936    }
937
938    /// Integer and Integer64 count as the same scalar type (the manual makes
939    /// them interchangeable when no overflow occurs).
940    fn same_scalar_type(self, other: ElemKind) -> bool {
941        let fold = |k| match k {
942            ElemKind::Integer64 => ElemKind::Integer,
943            k => k,
944        };
945        fold(self) == fold(other)
946    }
947}
948
949/// Buffers sequence elements and decides on `end()` whether to emit an array
950/// `[...]` (all elements scalars of one type, per the grammar) or a list
951/// `(...)` (anything else).
952#[doc(hidden)]
953pub struct SeqSerializer<'a> {
954    ser: &'a mut Serializer,
955    elements: Vec<(ElemKind, String)>,
956}
957
958impl ser::SerializeSeq for SeqSerializer<'_> {
959    type Ok = ();
960    type Error = Error;
961
962    fn serialize_element<T>(&mut self, value: &T) -> Result<()>
963    where
964        T: ?Sized + Serialize,
965    {
966        let mut tmp = Serializer {
967            output: String::new(),
968            indent_level: self.ser.indent_level,
969            options: self.ser.options.clone(),
970            at_root: false,
971        };
972        value.serialize(&mut tmp)?;
973        let kind = ElemKind::classify(&tmp.output);
974        self.elements.push((kind, tmp.output));
975        Ok(())
976    }
977
978    fn end(self) -> Result<()> {
979        let as_array = self.elements.iter().all(|(k, _)| *k != ElemKind::NonScalar)
980            && self
981                .elements
982                .windows(2)
983                .all(|w| w[0].0.same_scalar_type(w[1].0));
984
985        if as_array {
986            // Mixed 32/64-bit integers: promote everything to 64-bit so the
987            // array stays homogeneous for the C library as well
988            let promote = self.elements.iter().any(|(k, _)| *k == ElemKind::Integer64)
989                && self.elements.iter().any(|(k, _)| *k == ElemKind::Integer);
990            self.ser.output.push('[');
991            for (i, (kind, rendered)) in self.elements.iter().enumerate() {
992                if i > 0 {
993                    self.ser.output.push_str(", ");
994                }
995                self.ser.output.push_str(rendered);
996                if promote && *kind == ElemKind::Integer {
997                    self.ser.output.push('L');
998                }
999            }
1000            self.ser.output.push(']');
1001        } else {
1002            self.ser.output.push('(');
1003            for (i, (_, rendered)) in self.elements.iter().enumerate() {
1004                if i > 0 {
1005                    self.ser.output.push_str(", ");
1006                }
1007                self.ser.output.push_str(rendered);
1008            }
1009            self.ser.output.push(')');
1010        }
1011        Ok(())
1012    }
1013}
1014
1015/// Writes a tuple variant as `{ Variant = (a, b, ...); }`
1016#[doc(hidden)]
1017pub struct TupleVariantSerializer<'a> {
1018    ser: &'a mut Serializer,
1019}
1020
1021impl ser::SerializeTupleVariant for TupleVariantSerializer<'_> {
1022    type Ok = ();
1023    type Error = Error;
1024
1025    fn serialize_field<T>(&mut self, value: &T) -> Result<()>
1026    where
1027        T: ?Sized + Serialize,
1028    {
1029        if !self.ser.output.ends_with('(') {
1030            self.ser.output += ", ";
1031        }
1032        value.serialize(&mut *self.ser)
1033    }
1034
1035    fn end(self) -> Result<()> {
1036        self.ser.output += "); }";
1037        Ok(())
1038    }
1039}
1040
1041/// Writes a struct variant as `{ Variant = { ... }; }`
1042#[doc(hidden)]
1043pub struct StructVariantSerializer<'a> {
1044    ser: &'a mut Serializer,
1045}
1046
1047impl ser::SerializeStructVariant for StructVariantSerializer<'_> {
1048    type Ok = ();
1049    type Error = Error;
1050
1051    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
1052    where
1053        T: ?Sized + Serialize,
1054    {
1055        validate_name(key)?;
1056        if !self.ser.output.ends_with('{') {
1057            self.ser.output.push(';');
1058        }
1059        self.ser.output.push('\n');
1060        self.ser.write_indent();
1061        self.ser.output += key;
1062        self.ser.output += " = ";
1063        value.serialize(&mut *self.ser)
1064    }
1065
1066    fn end(self) -> Result<()> {
1067        self.ser.dedent();
1068        if !self.ser.output.ends_with('{') {
1069            self.ser.output.push(';');
1070        }
1071        self.ser.output.push('\n');
1072        self.ser.write_indent();
1073        self.ser.output += "}; }";
1074        Ok(())
1075    }
1076}
1077
1078impl ser::SerializeTuple for &mut Serializer {
1079    type Ok = ();
1080    type Error = Error;
1081
1082    fn serialize_element<T>(&mut self, value: &T) -> Result<()>
1083    where
1084        T: ?Sized + Serialize,
1085    {
1086        if !self.output.ends_with('(') {
1087            self.output += ", ";
1088        }
1089        value.serialize(&mut **self)
1090    }
1091
1092    fn end(self) -> Result<()> {
1093        self.output.push(')');
1094        Ok(())
1095    }
1096}
1097
1098impl ser::SerializeTupleStruct for &mut Serializer {
1099    type Ok = ();
1100    type Error = Error;
1101
1102    fn serialize_field<T>(&mut self, value: &T) -> Result<()>
1103    where
1104        T: ?Sized + Serialize,
1105    {
1106        if !self.output.ends_with('(') {
1107            self.output += ", ";
1108        }
1109        value.serialize(&mut **self)
1110    }
1111
1112    fn end(self) -> Result<()> {
1113        self.output.push(')');
1114        Ok(())
1115    }
1116}
1117
1118// Helper serializer to capture map keys as bare identifier strings
1119pub(crate) struct KeyCapture {
1120    pub(crate) key: String,
1121}
1122
1123impl ser::Serializer for &mut KeyCapture {
1124    type Ok = ();
1125    type Error = Error;
1126
1127    type SerializeSeq = ser::Impossible<(), Error>;
1128    type SerializeTuple = ser::Impossible<(), Error>;
1129    type SerializeTupleStruct = ser::Impossible<(), Error>;
1130    type SerializeTupleVariant = ser::Impossible<(), Error>;
1131    type SerializeMap = ser::Impossible<(), Error>;
1132    type SerializeStruct = ser::Impossible<(), Error>;
1133    type SerializeStructVariant = ser::Impossible<(), Error>;
1134
1135    fn serialize_str(self, v: &str) -> Result<()> {
1136        validate_name(v)?;
1137        self.key = v.to_string();
1138        Ok(())
1139    }
1140
1141    fn serialize_unit_variant(
1142        self,
1143        _name: &'static str,
1144        _variant_index: u32,
1145        variant: &'static str,
1146    ) -> Result<()> {
1147        validate_name(variant)?;
1148        self.key = variant.to_string();
1149        Ok(())
1150    }
1151
1152    fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<()>
1153    where
1154        T: ?Sized + Serialize,
1155    {
1156        value.serialize(self)
1157    }
1158
1159    fn serialize_bool(self, _v: bool) -> Result<()> {
1160        Err(ErrorKind::KeyMustBeAString.into())
1161    }
1162
1163    fn serialize_i8(self, _v: i8) -> Result<()> {
1164        Err(ErrorKind::KeyMustBeAString.into())
1165    }
1166
1167    fn serialize_i16(self, _v: i16) -> Result<()> {
1168        Err(ErrorKind::KeyMustBeAString.into())
1169    }
1170
1171    fn serialize_i32(self, _v: i32) -> Result<()> {
1172        Err(ErrorKind::KeyMustBeAString.into())
1173    }
1174
1175    fn serialize_i64(self, _v: i64) -> Result<()> {
1176        Err(ErrorKind::KeyMustBeAString.into())
1177    }
1178
1179    fn serialize_u8(self, _v: u8) -> Result<()> {
1180        Err(ErrorKind::KeyMustBeAString.into())
1181    }
1182
1183    fn serialize_u16(self, _v: u16) -> Result<()> {
1184        Err(ErrorKind::KeyMustBeAString.into())
1185    }
1186
1187    fn serialize_u32(self, _v: u32) -> Result<()> {
1188        Err(ErrorKind::KeyMustBeAString.into())
1189    }
1190
1191    fn serialize_u64(self, _v: u64) -> Result<()> {
1192        Err(ErrorKind::KeyMustBeAString.into())
1193    }
1194
1195    fn serialize_f32(self, _v: f32) -> Result<()> {
1196        Err(ErrorKind::KeyMustBeAString.into())
1197    }
1198
1199    fn serialize_f64(self, _v: f64) -> Result<()> {
1200        Err(ErrorKind::KeyMustBeAString.into())
1201    }
1202
1203    fn serialize_char(self, _v: char) -> Result<()> {
1204        Err(ErrorKind::KeyMustBeAString.into())
1205    }
1206
1207    fn serialize_bytes(self, _v: &[u8]) -> Result<()> {
1208        Err(ErrorKind::KeyMustBeAString.into())
1209    }
1210
1211    fn serialize_none(self) -> Result<()> {
1212        Err(ErrorKind::KeyMustBeAString.into())
1213    }
1214
1215    fn serialize_some<T>(self, _value: &T) -> Result<()>
1216    where
1217        T: ?Sized + Serialize,
1218    {
1219        Err(ErrorKind::KeyMustBeAString.into())
1220    }
1221
1222    fn serialize_unit(self) -> Result<()> {
1223        Err(ErrorKind::KeyMustBeAString.into())
1224    }
1225
1226    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
1227        Err(ErrorKind::KeyMustBeAString.into())
1228    }
1229
1230    fn serialize_newtype_variant<T>(
1231        self,
1232        _name: &'static str,
1233        _variant_index: u32,
1234        _variant: &'static str,
1235        _value: &T,
1236    ) -> Result<()>
1237    where
1238        T: ?Sized + Serialize,
1239    {
1240        Err(ErrorKind::KeyMustBeAString.into())
1241    }
1242
1243    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
1244        Err(ErrorKind::KeyMustBeAString.into())
1245    }
1246
1247    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
1248        Err(ErrorKind::KeyMustBeAString.into())
1249    }
1250
1251    fn serialize_tuple_struct(
1252        self,
1253        _name: &'static str,
1254        _len: usize,
1255    ) -> Result<Self::SerializeTupleStruct> {
1256        Err(ErrorKind::KeyMustBeAString.into())
1257    }
1258
1259    fn serialize_tuple_variant(
1260        self,
1261        _name: &'static str,
1262        _variant_index: u32,
1263        _variant: &'static str,
1264        _len: usize,
1265    ) -> Result<Self::SerializeTupleVariant> {
1266        Err(ErrorKind::KeyMustBeAString.into())
1267    }
1268
1269    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
1270        Err(ErrorKind::KeyMustBeAString.into())
1271    }
1272
1273    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
1274        Err(ErrorKind::KeyMustBeAString.into())
1275    }
1276
1277    fn serialize_struct_variant(
1278        self,
1279        _name: &'static str,
1280        _variant_index: u32,
1281        _variant: &'static str,
1282        _len: usize,
1283    ) -> Result<Self::SerializeStructVariant> {
1284        Err(ErrorKind::KeyMustBeAString.into())
1285    }
1286}
1287
1288#[cfg(test)]
1289mod tests {
1290    use super::*;
1291    use serde::Serialize;
1292
1293    #[test]
1294    fn test_serialize_primitives() {
1295        assert_eq!(to_string(&42u32).unwrap(), "42");
1296        assert_eq!(to_string(&42i64).unwrap(), "42L");
1297        assert_eq!(to_string(&2.25f64).unwrap(), "2.25");
1298        assert_eq!(to_string(&true).unwrap(), "true");
1299        assert_eq!(to_string(&"hello".to_string()).unwrap(), "\"hello\"");
1300    }
1301
1302    #[test]
1303    fn test_serialize_array() {
1304        let arr = vec![1, 2, 3];
1305        assert_eq!(to_string(&arr).unwrap(), "[1, 2, 3]");
1306    }
1307
1308    #[test]
1309    fn test_serialize_struct() {
1310        #[derive(Serialize)]
1311        struct Test {
1312            a: i32,
1313            b: String,
1314        }
1315
1316        let test = Test {
1317            a: 42,
1318            b: "hello".to_string(),
1319        };
1320        let result = to_string(&test).unwrap();
1321        assert!(result.contains("a = 42"));
1322        assert!(result.contains("b = \"hello\""));
1323    }
1324
1325    #[test]
1326    fn test_serialize_i8_no_l_suffix() {
1327        assert_eq!(to_string(&42i8).unwrap(), "42");
1328        assert_eq!(to_string(&42i16).unwrap(), "42");
1329        assert_eq!(to_string(&42i32).unwrap(), "42");
1330        assert_eq!(to_string(&42i64).unwrap(), "42L");
1331    }
1332
1333    #[test]
1334    fn test_serialize_u64_has_l_suffix() {
1335        assert_eq!(to_string(&42u8).unwrap(), "42");
1336        assert_eq!(to_string(&42u16).unwrap(), "42");
1337        assert_eq!(to_string(&42u32).unwrap(), "42");
1338        assert_eq!(to_string(&42u64).unwrap(), "42L");
1339    }
1340
1341    #[test]
1342    fn test_serialize_u32_beyond_i32_gets_l_suffix() {
1343        // These are 64-bit values per the manual; the explicit suffix keeps
1344        // pre-1.8 C parsers from silently wrapping them
1345        assert_eq!(to_string(&(i32::MAX as u32)).unwrap(), "2147483647");
1346        assert_eq!(to_string(&(i32::MAX as u32 + 1)).unwrap(), "2147483648L");
1347        assert_eq!(to_string(&u32::MAX).unwrap(), "4294967295L");
1348    }
1349
1350    #[test]
1351    fn test_serialize_float_always_has_decimal() {
1352        // 1.0f64.to_string() is "1" in Rust - we must add ".0"
1353        assert_eq!(to_string(&1.0f64).unwrap(), "1.0");
1354        assert_eq!(to_string(&0.0f64).unwrap(), "0.0");
1355        // Already has decimal
1356        assert_eq!(to_string(&2.25f64).unwrap(), "2.25");
1357    }
1358
1359    #[test]
1360    fn test_serialize_non_finite_floats_error() {
1361        // No representation in the libconfig grammar
1362        assert!(matches!(
1363            to_string(&f64::INFINITY),
1364            Err(e) if *e.kind() == ErrorKind::NonFiniteFloat
1365        ));
1366        assert!(matches!(
1367            to_string(&f64::NEG_INFINITY),
1368            Err(e) if *e.kind() == ErrorKind::NonFiniteFloat
1369        ));
1370        assert!(matches!(to_string(&f64::NAN), Err(e) if *e.kind() == ErrorKind::NonFiniteFloat));
1371        assert!(matches!(to_string(&f32::NAN), Err(e) if *e.kind() == ErrorKind::NonFiniteFloat));
1372    }
1373
1374    #[test]
1375    fn test_serialize_u64_out_of_range_errors() {
1376        // integer64 is a signed long long; anything larger cannot round-trip
1377        assert_eq!(to_string(&(i64::MAX as u64)).unwrap(), "9223372036854775807L");
1378        assert!(matches!(
1379            to_string(&(i64::MAX as u64 + 1)),
1380            Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange
1381        ));
1382        assert!(matches!(to_string(&u64::MAX), Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange));
1383    }
1384
1385    #[test]
1386    fn test_serialize_invalid_map_key_errors() {
1387        use indexmap::IndexMap;
1388        for bad in ["my key", "2bad", "", "quoted\"", "dot.ted"] {
1389            let mut map = IndexMap::new();
1390            map.insert(bad.to_string(), 1i32);
1391            assert!(
1392                matches!(to_string(&map), Err(e) if matches!(e.kind(), ErrorKind::InvalidSettingName(_))),
1393                "key {bad:?} should be rejected"
1394            );
1395        }
1396        // Valid names, including '*' and '-' per the grammar
1397        let mut map = IndexMap::new();
1398        map.insert("*wild-card_2*".to_string(), 1i32);
1399        assert!(to_string(&map).is_ok());
1400    }
1401
1402    #[test]
1403    fn test_serialize_control_chars_escaped() {
1404        // Named escapes per the manual (section 2.9); \xNN for the rest
1405        assert_eq!(to_string(&"a\x01b").unwrap(), "\"a\\x01b\"");
1406        assert_eq!(to_string(&"bell\x07").unwrap(), "\"bell\\a\"");
1407        assert_eq!(to_string(&"tab\tnl\n").unwrap(), "\"tab\\tnl\\n\"");
1408        // Escaped output parses back to the original string
1409        let s = "\x01\x02\x07\x08\x0b\x1f ok";
1410        let out = to_string(&s.to_string()).unwrap();
1411        let back: String = crate::from_str(&out).unwrap();
1412        assert_eq!(back, s);
1413    }
1414
1415    #[test]
1416    fn test_serialize_seq_of_groups_becomes_list() {
1417        #[derive(Serialize)]
1418        struct P {
1419            x: i32,
1420        }
1421        // Arrays may only hold scalars, so non-scalar sequences are lists
1422        let out = to_string(&vec![P { x: 1 }, P { x: 2 }]).unwrap();
1423        assert!(out.starts_with('('), "expected list, got: {out}");
1424        assert!(out.ends_with(')'));
1425
1426        let nested = to_string(&vec![vec![1, 2], vec![3]]).unwrap();
1427        assert_eq!(nested, "([1, 2], [3])");
1428
1429        // Scalar sequences stay arrays
1430        assert_eq!(to_string(&vec![1, 2, 3]).unwrap(), "[1, 2, 3]");
1431        assert_eq!(to_string(&vec!["a", "b"]).unwrap(), "[\"a\", \"b\"]");
1432    }
1433
1434    #[test]
1435    fn test_serialize_mixed_int_widths_promote_to_i64() {
1436        use crate::Value;
1437        let v = Value::Array(vec![Value::Integer(1), Value::Integer64(5_000_000_000)]);
1438        assert_eq!(to_string(&v).unwrap(), "[1L, 5000000000L]");
1439    }
1440
1441    #[test]
1442    fn test_write_options_assignment_and_layout() {
1443        use crate::{Config, WriteOptions};
1444
1445        let config = Config::from_str("port = 80;\ndb = {\n  host = \"h\";\n};").unwrap();
1446
1447        // C-writer style: colon for groups, brace on its own line
1448        let opts = WriteOptions::new()
1449            .colon_assignment_for_groups(true)
1450            .open_brace_on_separate_line(true);
1451        let text = config.to_string_with_options(&opts).unwrap();
1452        assert_eq!(text, "port = 80;\ndb :\n{\n  host = \"h\";\n};");
1453        // Every option combination must stay grammar-valid
1454        assert_eq!(Config::from_str(&text).unwrap(), config);
1455
1456        // Colon for non-groups too
1457        let opts = WriteOptions::new()
1458            .colon_assignment_for_groups(true)
1459            .colon_assignment_for_non_groups(true);
1460        let text = config.to_string_with_options(&opts).unwrap();
1461        assert!(text.contains("port : 80"));
1462        assert_eq!(Config::from_str(&text).unwrap(), config);
1463
1464        // No semicolons (the grammar's empty terminator)
1465        let opts = WriteOptions::new().semicolon_separators(false);
1466        let text = config.to_string_with_options(&opts).unwrap();
1467        assert!(!text.contains(';'), "got: {text}");
1468        assert_eq!(Config::from_str(&text).unwrap(), config);
1469
1470        // Tab width 4, and 0 = one tab per level
1471        let opts = WriteOptions::new().tab_width(4);
1472        let text = config.to_string_with_options(&opts).unwrap();
1473        assert!(text.contains("\n    host"), "got: {text}");
1474        let opts = WriteOptions::new().tab_width(0);
1475        let text = config.to_string_with_options(&opts).unwrap();
1476        assert!(text.contains("\n\thost"), "got: {text}");
1477        assert_eq!(Config::from_str(&text).unwrap(), config);
1478
1479        // Defaults are unchanged from Config::to_string
1480        assert_eq!(
1481            config.to_string_with_options(&WriteOptions::default()).unwrap(),
1482            config.to_string().unwrap()
1483        );
1484    }
1485
1486    #[test]
1487    fn test_write_options_floats() {
1488        use crate::{Value, WriteOptions};
1489
1490        // Fixed precision (config_set_float_precision)
1491        let opts = WriteOptions::new().float_precision(3);
1492        assert_eq!(to_string_with_options(&2.5f64, &opts).unwrap(), "2.500");
1493        // Precision 0 keeps the decimal point so the value re-reads as a float
1494        let opts = WriteOptions::new().float_precision(0);
1495        assert_eq!(to_string_with_options(&3.0f64, &opts).unwrap(), "3.0");
1496
1497        // Scientific notation
1498        let opts = WriteOptions::new().scientific_notation(true);
1499        let text = to_string_with_options(&2500.0f64, &opts).unwrap();
1500        assert_eq!(text, "2.5e3");
1501        assert_eq!(crate::from_str::<f64>(&text).unwrap(), 2500.0);
1502
1503        // Scientific floats in arrays still classify as floats
1504        let text = to_string_with_options(&vec![100000.0f64, 2.5], &opts).unwrap();
1505        assert_eq!(text, "[1e5, 2.5e0]");
1506        let v: Value = crate::from_str(&format!("x = {};", text)).unwrap();
1507        assert!(v["x"].is_array());
1508        assert_eq!(v["x"].get_index(0).unwrap().as_f64(), Some(1e5));
1509
1510        // Non-finite still rejected under every option combination
1511        assert!(to_string_with_options(&f64::NAN, &opts).is_err());
1512    }
1513
1514    #[test]
1515    fn test_serialize_enum_variants() {
1516        #[derive(Serialize)]
1517        enum E {
1518            Unit,
1519            Newtype(i32),
1520            Tuple(i32, bool),
1521            Struct { x: i32 },
1522        }
1523
1524        assert_eq!(to_string(&E::Unit).unwrap(), "\"Unit\"");
1525        assert_eq!(to_string(&E::Newtype(5)).unwrap(), "{ Newtype = 5; }");
1526        assert_eq!(to_string(&E::Tuple(7, true)).unwrap(), "{ Tuple = (7, true); }");
1527        let s = to_string(&E::Struct { x: 9 }).unwrap();
1528        assert!(s.starts_with("{ Struct = {"), "got: {s}");
1529        assert!(s.ends_with("}; }"), "got: {s}");
1530    }
1531
1532    #[test]
1533    fn test_serialize_tuple_as_list() {
1534        let tup = (1, 2, 3);
1535        assert_eq!(to_string(&tup).unwrap(), "(1, 2, 3)");
1536    }
1537
1538    #[test]
1539    fn test_serialize_map_bare_keys() {
1540        use indexmap::IndexMap;
1541        let mut map = IndexMap::new();
1542        map.insert("key".to_string(), 42i32);
1543        let result = to_string(&map).unwrap();
1544        // Keys should be bare identifiers, not quoted
1545        assert!(result.contains("key = 42"));
1546        assert!(!result.contains("\"key\""));
1547    }
1548}