Skip to main content

serde_saphyr/ser/
mod.rs

1//! Single-pass YAML serializer with optional anchors for Rc/Arc/Weak,
2//! order preservation (uses the iterator order of your types), simple
3//! style controls (block strings & flow containers), and special
4//! float handling for NaN/±Inf. No intermediate YAML DOM is built.
5//!
6//! Usage example:
7//!
8//! use serde::Serialize;
9//! use std::rc::Rc;
10//! use serde_saphyr::{to_string, RcAnchor, LitStr, FlowSeq};
11//!
12//! #[derive(Serialize)]
13//! struct Cfg {
14//!     name: String,
15//!     ports: FlowSeq<Vec<u16>>,   // render `[8080, 8081]`
16//!     note: LitStr<'static>,      // render as `|` block
17//!     data: RcAnchor<Vec<i32>>,   // first sight => &a1
18//!     alias: RcAnchor<Vec<i32>>,  // later sight => *a1
19//! }
20//!
21//! fn main() {
22//!     let shared = Rc::new(vec![1,2,3]);
23//!     let cfg = Cfg {
24//!         name: "demo".into(),
25//!         ports: FlowSeq(vec![8080, 8081]),
26//!         note: LitStr("line 1\nline 2"),
27//!         data: RcAnchor(shared.clone()),
28//!         alias: RcAnchor(shared),
29//!     };
30//!     println!("{}", to_string(&cfg).unwrap());
31//! }
32
33pub mod error;
34pub mod options;
35pub(crate) mod quoting;
36mod wrapping;
37mod zmij_format;
38
39use serde::ser::Error as _;
40use serde::ser::{
41    self, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant,
42    SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer,
43};
44use std::cell::RefCell;
45use std::collections::HashMap;
46use std::fmt::{self, Write};
47use std::rc::Rc;
48use std::sync::{Arc, Mutex};
49
50use self::options::{CommentPosition, FOLDED_WRAP_CHARS, MIN_FOLD_CHARS, SerializerOptions};
51use crate::long_strings::{NAME_FOLD_STR, NAME_LIT_STR};
52use crate::{
53    ArcAnchor, ArcRecursion, ArcRecursive, ArcWeakAnchor, Commented, FlowMap, FlowSeq, RcAnchor,
54    RcRecursion, RcRecursive, RcWeakAnchor, SpaceAfter,
55};
56use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
57use nohash_hasher::BuildNoHashHasher;
58
59// ------------------------------------------------------------
60// Public API
61// ------------------------------------------------------------
62
63pub use self::error::Error;
64use self::quoting::{is_plain_safe, is_plain_value_safe};
65
66/// Result alias.
67pub type Result<T> = std::result::Result<T, Error>;
68
69pub use crate::long_strings::{FoldStr, FoldString, LitStr, LitString};
70
71// ------------------------------------------------------------
72// Internal wrappers -> shape the stream Serde produces so our
73// serializer can intercept them in a single pass.
74// ------------------------------------------------------------
75
76// Recursive weak payloads: defer locking/borrowing until actual value serialization.
77struct RcRecursivePayload<'a, T>(&'a Rc<RefCell<Option<T>>>);
78struct ArcRecursivePayload<'a, T>(&'a Arc<Mutex<Option<T>>>);
79
80impl<T: Serialize> Serialize for RcRecursivePayload<'_, T> {
81    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
82        let borrowed = self.0.borrow();
83        if let Some(value) = borrowed.as_ref() {
84            value.serialize(s)
85        } else {
86            s.serialize_unit()
87        }
88    }
89}
90
91impl<T: Serialize> Serialize for ArcRecursivePayload<'_, T> {
92    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
93        let guard = self
94            .0
95            .lock()
96            .map_err(|_| ser::Error::custom("recursive Arc anchor mutex poisoned"))?;
97        if let Some(value) = guard.as_ref() {
98            value.serialize(s)
99        } else {
100            s.serialize_unit()
101        }
102    }
103}
104
105// Flow hints and block-string hints: we use newtype-struct names.
106const NAME_TUPLE_ANCHOR: &str = "__yaml_anchor";
107const NAME_TUPLE_WEAK: &str = "__yaml_weak_anchor";
108const NAME_FLOW_SEQ: &str = "__yaml_flow_seq";
109const NAME_FLOW_MAP: &str = "__yaml_flow_map";
110const NAME_TUPLE_COMMENTED: &str = "__yaml_commented";
111const NAME_SPACE_AFTER: &str = "__yaml_space_after";
112
113// Top-level newtype wrappers for strong/weak simply wrap the real payloads.
114impl<T: Serialize> Serialize for RcAnchor<T> {
115    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
116        // delegate to tuple-struct the serializer knows how to intercept
117        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_ANCHOR, 2)?;
118        let ptr = Rc::as_ptr(&self.0) as usize;
119        ts.serialize_field(&ptr)?;
120        ts.serialize_field(&*self.0)?;
121        ts.end()
122    }
123}
124impl<T: Serialize> Serialize for ArcAnchor<T> {
125    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
126        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_ANCHOR, 2)?;
127        let ptr = Arc::as_ptr(&self.0) as usize;
128        ts.serialize_field(&ptr)?;
129        ts.serialize_field(&*self.0)?;
130        ts.end()
131    }
132}
133impl<T: Serialize> Serialize for RcRecursive<T> {
134    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
135        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_ANCHOR, 2)?;
136        let ptr = Rc::as_ptr(&self.0) as usize;
137        ts.serialize_field(&ptr)?;
138        let borrowed = self.0.borrow();
139        let value = borrowed
140            .as_ref()
141            .ok_or_else(|| ser::Error::custom("recursive Rc anchor not initialized"))?;
142        ts.serialize_field(value)?;
143        ts.end()
144    }
145}
146impl<T: Serialize> Serialize for ArcRecursive<T> {
147    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
148        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_ANCHOR, 2)?;
149        let ptr = Arc::as_ptr(&self.0) as usize;
150        ts.serialize_field(&ptr)?;
151        let guard = self
152            .0
153            .lock()
154            .map_err(|_| ser::Error::custom("recursive Arc anchor mutex poisoned"))?;
155        let value = guard
156            .as_ref()
157            .ok_or_else(|| ser::Error::custom("recursive Arc anchor not initialized"))?;
158        ts.serialize_field(value)?;
159        ts.end()
160    }
161}
162impl<T: Serialize> Serialize for RcWeakAnchor<T> {
163    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
164        let up = self.0.upgrade();
165        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_WEAK, 3)?;
166        let ptr = self.0.as_ptr() as usize;
167        ts.serialize_field(&ptr)?;
168        ts.serialize_field(&up.is_some())?;
169        if let Some(rc) = up {
170            ts.serialize_field(&*rc)?;
171        } else {
172            ts.serialize_field(&())?; // ignored by our serializer
173        }
174        ts.end()
175    }
176}
177impl<T: Serialize> Serialize for ArcWeakAnchor<T> {
178    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
179        let up = self.0.upgrade();
180        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_WEAK, 3)?;
181        let ptr = self.0.as_ptr() as usize;
182        ts.serialize_field(&ptr)?;
183        ts.serialize_field(&up.is_some())?;
184        if let Some(arc) = up {
185            ts.serialize_field(&*arc)?;
186        } else {
187            ts.serialize_field(&())?;
188        }
189        ts.end()
190    }
191}
192impl<T: Serialize> Serialize for RcRecursion<T> {
193    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
194        let up = self.0.upgrade();
195        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_WEAK, 3)?;
196        let ptr = self.0.as_ptr() as usize;
197        ts.serialize_field(&ptr)?;
198        ts.serialize_field(&up.is_some())?;
199        if let Some(rc) = up {
200            ts.serialize_field(&RcRecursivePayload(&rc))?;
201        } else {
202            ts.serialize_field(&())?;
203        }
204        ts.end()
205    }
206}
207impl<T: Serialize> Serialize for ArcRecursion<T> {
208    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
209        let up = self.0.upgrade();
210        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_WEAK, 3)?;
211        let ptr = self.0.as_ptr() as usize;
212        ts.serialize_field(&ptr)?;
213        ts.serialize_field(&up.is_some())?;
214        if let Some(arc) = up {
215            ts.serialize_field(&ArcRecursivePayload(&arc))?;
216        } else {
217            ts.serialize_field(&())?;
218        }
219        ts.end()
220    }
221}
222
223// Hints for flow / block strings.
224impl<T: Serialize> Serialize for FlowSeq<T> {
225    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
226        s.serialize_newtype_struct(NAME_FLOW_SEQ, &self.0)
227    }
228}
229impl<T: Serialize> Serialize for FlowMap<T> {
230    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
231        s.serialize_newtype_struct(NAME_FLOW_MAP, &self.0)
232    }
233}
234impl<T: Serialize> Serialize for SpaceAfter<T> {
235    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
236        s.serialize_newtype_struct(NAME_SPACE_AFTER, &self.0)
237    }
238}
239
240impl<T: Serialize> Serialize for Commented<T> {
241    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
242        // Represent as a special tuple-struct with two fields: (comment, value)
243        // so the serializer can stage the comment before serializing the value.
244        let mut ts = s.serialize_tuple_struct(NAME_TUPLE_COMMENTED, 2)?;
245        ts.serialize_field(&self.1)?; // comment first
246        ts.serialize_field(&self.0)?; // then value
247        ts.end()
248    }
249}
250
251// ------------------------------------------------------------
252// Core serializer
253// ------------------------------------------------------------
254
255#[derive(Clone, Copy, PartialEq, Eq)]
256enum PendingFlow {
257    AnySeq,
258    AnyMap,
259}
260#[derive(Clone, Copy, PartialEq, Eq)]
261enum StrStyle {
262    Literal, // |
263    Folded,  // >
264}
265
266// Numeric anchor id used internally.
267type AnchorId = u32;
268
269/// Core YAML serializer used by `to_string`, `to_fmt_writer`, and `to_io_writer` (and their `_with_options` variants).
270///
271/// This type implements `serde::Serializer` and writes YAML to a `fmt::Write`.
272/// It manages indentation, flow/block styles, and YAML anchors/aliases.
273///
274/// This type is also re-exported from the crate root as [`serde_saphyr::Serializer`].
275///
276/// ## Example
277///
278/// ```rust
279/// use serde::Serialize;
280///
281/// #[derive(Serialize)]
282/// struct Foo {
283///     a: i32,
284///     b: bool,
285/// }
286///
287/// let mut out = String::new();
288/// let mut ser = serde_saphyr::Serializer::new(&mut out);
289/// Foo { a: 1, b: true }.serialize(&mut ser)?;
290///
291/// assert!(out.contains("a: 1"));
292/// # Ok::<(), serde_saphyr::ser::Error>(())
293/// ```
294pub struct YamlSerializer<'a, W: Write> {
295    /// Destination writer where YAML text is emitted.
296    out: &'a mut W,
297    /// Spaces per indentation level for block-style collections.
298    indent_step: usize,
299    /// Threshold for downgrading block-string wrappers to plain scalars.
300    min_fold_chars: usize,
301    /// Wrap width for folded block scalars ('>').
302    folded_wrap_col: usize,
303    /// Current nesting depth (used for indentation).
304    depth: usize,
305    /// Whether the cursor is at the start of a line.
306    at_line_start: bool,
307
308    // Anchors:
309    /// Map from pointer identity to anchor id.
310    anchors: HashMap<usize, AnchorId, BuildNoHashHasher<usize>>,
311    /// Next numeric id to use when generating anchor names (1-based).
312    next_anchor_id: AnchorId,
313    /// If set, the next scalar/complex node to be emitted will be prefixed with this `&anchor`.
314    pending_anchor_id: Option<AnchorId>,
315    /// Optional custom anchor-name generator supplied by the caller.
316    anchor_gen: Option<fn(usize) -> String>,
317    /// Cache of custom anchor names when generator is present (index = id-1).
318    custom_anchor_names: Option<Vec<String>>,
319
320    // Style flags:
321    /// Pending flow-style hint captured from wrapper types.
322    pending_flow: Option<PendingFlow>,
323    /// Number of nested flow containers we are currently inside (>0 means in-flow).
324    in_flow: usize,
325    /// Pending block-string style hint (literal `|` or folded `>`).
326    pending_str_style: Option<StrStyle>,
327    /// Whether the pending block-string style was selected automatically (prefer_block_scalars)
328    /// as opposed to being requested explicitly by wrapper types (LitStr/FoldStr variants).
329    pending_str_from_auto: bool,
330    /// Pending inline comment to be appended after the next scalar (block style only).
331    pending_inline_comment: Option<String>,
332    /// Placement mode for [`crate::Commented`] wrappers in block style.
333    comment_position: CommentPosition,
334    /// If true, emit YAML tags for simple enums that serialize to a single scalar.
335    tagged_enums: bool,
336    /// If true, empty maps are emitted as {} and lists as []
337    empty_as_braces: bool,
338    /// If true, emit list items with a more compact indentation style under mapping keys.
339    compact_list_indent: bool,
340    /// If true, automatically prefer YAML block scalars for plain strings:
341    ///  - Strings containing newlines use literal style `|`.
342    ///  - Single-line strings longer than `folded_wrap_col` use folded style `>`.
343    prefer_block_scalars: bool,
344    /// When the previous token was a list item dash ("- ") and the next node is a mapping,
345    /// emit the first key inline on the same line ("- key: value").
346    pending_inline_map: bool,
347    /// After writing a mapping key and ':', defer writing the following space until we know
348    /// whether the value is a scalar (space) or a complex node (newline with no space).
349    pending_space_after_colon: bool,
350    /// If the previous sequence element after a dash turned out to be a mapping (inline first key),
351    /// indent subsequent dashes by one level to satisfy tests expecting "\n  -".
352    inline_map_after_dash: bool,
353    /// Whether the last serialized value was a block collection (map or sequence).
354    last_value_was_block: bool,
355    /// If a sequence element starts with a dash on this depth, capture that depth so
356    /// struct-variant mappings emitted immediately after can indent their fields correctly.
357    after_dash_depth: Option<usize>,
358    /// Current block map indentation depth (for aligning sequences under a map key).
359    current_map_depth: Option<usize>,
360    /// If true, quote all string scalars. Uses single quotes by default, but switches to
361    /// double quotes when the string contains escape sequences or single quotes.
362    quote_all: bool,
363
364    /// When enabled, emit YAML 1.2 directive and use YAML 1.2-friendly heuristics.
365    yaml_12: bool,
366    /// Whether we have started emitting the current document.
367    doc_started: bool,
368}
369
370impl<'a, W: Write> YamlSerializer<'a, W> {
371    /// Construct a `YamlSerializer` that writes to `out`.
372    /// Called by `to_writer`/`to_string` entry points.
373    pub fn new(out: &'a mut W) -> Self {
374        Self {
375            out,
376            indent_step: 2,
377            min_fold_chars: MIN_FOLD_CHARS,
378            folded_wrap_col: FOLDED_WRAP_CHARS,
379            depth: 0,
380            at_line_start: true,
381            anchors: HashMap::with_hasher(BuildNoHashHasher::default()),
382            next_anchor_id: 1,
383            pending_anchor_id: None,
384            anchor_gen: None,
385            custom_anchor_names: None,
386            pending_flow: None,
387            in_flow: 0,
388            pending_str_style: None,
389            pending_str_from_auto: false,
390            pending_inline_comment: None,
391            comment_position: CommentPosition::Inline,
392            tagged_enums: false,
393            empty_as_braces: true,
394            compact_list_indent: false,
395            prefer_block_scalars: true,
396            pending_inline_map: false,
397            pending_space_after_colon: false,
398            inline_map_after_dash: false,
399            last_value_was_block: false,
400            after_dash_depth: None,
401            current_map_depth: None,
402            quote_all: false,
403            yaml_12: false,
404            doc_started: false,
405        }
406    }
407    /// Construct a `YamlSerializer` with a specific indentation step.
408    /// Typically used internally by tests or convenience wrappers.
409    pub fn with_indent(out: &'a mut W, indent_step: usize) -> Self {
410        let mut s = Self::new(out);
411        s.indent_step = indent_step;
412        s
413    }
414    /// Construct a `YamlSerializer` from user-supplied [`SerializerOptions`].
415    /// Used by `to_writer_with_options`.
416    #[allow(deprecated)]
417    pub fn with_options(out: &'a mut W, options: &mut SerializerOptions) -> Self {
418        let mut s = Self::new(out);
419        s.indent_step = options.indent_step;
420        s.min_fold_chars = options.min_fold_chars;
421        s.folded_wrap_col = options.folded_wrap_chars;
422        s.anchor_gen = options.anchor_generator.take();
423        s.tagged_enums = options.tagged_enums;
424        s.empty_as_braces = options.empty_as_braces;
425        s.compact_list_indent = options.compact_list_indent;
426        s.prefer_block_scalars = options.prefer_block_scalars;
427        s.quote_all = options.quote_all;
428        s.comment_position = options.comment_position;
429        s.yaml_12 = options.yaml_12;
430        s
431    }
432
433    // -------- helpers --------
434
435    /// Determines if a string requires double quotes when `quote_all` is enabled.
436    /// Returns true if the string contains single quotes, backslashes, or control characters
437    /// that need escape processing.
438    #[inline]
439    fn needs_double_quotes(s: &str) -> bool {
440        s.chars().any(|c| {
441            c == '\''       // single quote present - cannot use single-quoted style
442                || c == '\\' // backslash - needs escape processing
443                || c.is_control() // control chars (includes \n, \t, \r, etc.) need escaping
444        })
445    }
446
447    /// Write a single-quoted string. Single quotes inside the string are escaped by doubling them.
448    fn write_single_quoted(&mut self, s: &str) -> Result<()> {
449        self.out.write_char('\'')?;
450        for ch in s.chars() {
451            if ch == '\'' {
452                self.out.write_str("''")?; // escape single quote by doubling
453            } else {
454                self.out.write_char(ch)?;
455            }
456        }
457        self.out.write_char('\'')?;
458        Ok(())
459    }
460
461    /// Called at the end of emitting a scalar in block style: appends a pending inline
462    /// comment (if any) and then emits a newline. In flow style, comments are suppressed.
463    #[inline]
464    fn write_end_of_scalar(&mut self) -> Result<()> {
465        if self.in_flow == 0 {
466            if let Some(c) = self.pending_inline_comment.take() {
467                self.out.write_str(" # ")?;
468                self.out.write_str(&c)?;
469            }
470            self.newline()?;
471        }
472        Ok(())
473    }
474
475    #[inline]
476    fn sanitize_comment_text(comment: &str) -> String {
477        comment
478            .chars()
479            .map(|ch| match ch {
480                '\n' | '\r' | '\u{85}' | '\u{2028}' | '\u{2029}' => ' ',
481                _ => ch,
482            })
483            .collect()
484    }
485
486    fn write_above_comment(&mut self, comment: &str) -> Result<Option<usize>> {
487        if self.in_flow > 0 || comment.is_empty() {
488            return Ok(None);
489        }
490
491        let target_depth = if self.pending_space_after_colon {
492            let base = self.current_map_depth.unwrap_or(self.depth);
493            self.pending_space_after_colon = false;
494            if !self.at_line_start {
495                self.newline()?;
496            }
497            base + 1
498        } else if !self.at_line_start {
499            let base = self.after_dash_depth.unwrap_or(self.depth);
500            self.pending_inline_map = false;
501            self.newline()?;
502            base + 1
503        } else {
504            self.depth
505        };
506
507        self.write_indent(target_depth)?;
508        self.out.write_str("# ")?;
509        self.out.write_str(comment)?;
510        self.newline()?;
511        Ok(Some(target_depth))
512    }
513
514    /// Allocate (or get existing) anchor id for a pointer identity.
515    /// Returns `(id, is_new)`.
516    #[inline]
517    fn alloc_anchor_for(&mut self, ptr: usize) -> (AnchorId, bool) {
518        match self.anchors.entry(ptr) {
519            std::collections::hash_map::Entry::Occupied(e) => (*e.get(), false),
520            std::collections::hash_map::Entry::Vacant(v) => {
521                let id = self.next_anchor_id;
522                self.next_anchor_id = self.next_anchor_id.saturating_add(1);
523                if let Some(generator) = self.anchor_gen {
524                    let name = generator(id as usize);
525                    self.custom_anchor_names
526                        .get_or_insert_with(Vec::new)
527                        .push(name);
528                }
529                v.insert(id);
530                (id, true)
531            }
532        }
533    }
534
535    /// Resolve an anchor name for `id` and write it.
536    #[inline]
537    fn write_anchor_name(&mut self, id: AnchorId) -> Result<()> {
538        if let Some(names) = &self.custom_anchor_names {
539            // ids are 1-based; vec is 0-based
540            let idx = id as usize - 1;
541            if let Some(name) = names.get(idx) {
542                self.out.write_str(name)?;
543            } else {
544                // Fallback if generator vector is out of sync
545                write!(self.out, "a{}", id)?;
546            }
547        } else {
548            write!(self.out, "a{}", id)?;
549        }
550        Ok(())
551    }
552
553    /// If a mapping key has just been written (':' emitted) and we determined the value is a scalar,
554    /// insert a single space before the scalar and clear the pending flag.
555    #[inline]
556    fn write_space_if_pending(&mut self) -> Result<()> {
557        if self.pending_space_after_colon {
558            self.out.write_char(' ')?;
559            self.pending_space_after_colon = false;
560        }
561        // When a scalar value is serialized, it should reset the block-sibling flag.
562        // Most scalar emitters call this method.
563        self.last_value_was_block = false;
564        Ok(())
565    }
566
567    /// Ensure indentation is written if we are at the start of a line.
568    /// Internal: called by most emitters before writing tokens.
569    #[inline]
570    fn write_indent(&mut self, depth: usize) -> Result<()> {
571        if self.at_line_start {
572            if !self.doc_started {
573                self.doc_started = true;
574                if self.yaml_12 {
575                    self.out.write_str("%YAML 1.2\n")?;
576                    // Still at start of a line after the directive.
577                    self.at_line_start = true;
578                }
579            }
580            for _k in 0..self.indent_step * depth {
581                self.out.write_char(' ')?;
582            }
583            self.at_line_start = false;
584        }
585        Ok(())
586    }
587
588    /// Emit a newline and mark the next write position as line start.
589    /// Internal utility used after finishing a top-level token.
590    #[inline]
591    fn newline(&mut self) -> Result<()> {
592        self.out.write_char('\n')?;
593        self.at_line_start = true;
594        Ok(())
595    }
596
597    /// Write a folded block string body, wrapping to `folded_wrap_col` characters.
598    /// Delegates to the standalone function in `wrapping` module.
599    fn write_folded_block(&mut self, s: &str, indent: usize) -> Result<()> {
600        self::wrapping::write_folded_block(
601            self.out,
602            s,
603            indent,
604            self.indent_step,
605            self.folded_wrap_col,
606        )?;
607        self.at_line_start = true;
608        Ok(())
609    }
610
611    /// Write a scalar either as plain or as double-quoted with minimal escapes.
612    /// Called by most `serialize_*` primitive methods.
613    fn write_plain_or_quoted(&mut self, s: &str) -> Result<()> {
614        if self.quote_all {
615            // In quote_all mode: prefer single quotes, use double quotes when needed
616            if Self::needs_double_quotes(s) {
617                self.write_quoted(s)
618            } else {
619                self.write_single_quoted(s)
620            }
621        } else if is_plain_safe(s) {
622            self.out.write_str(s)?;
623            Ok(())
624        } else {
625            self.write_quoted(s)
626        }
627    }
628
629    /// Write a double-quoted string with necessary escapes.
630    fn write_quoted(&mut self, s: &str) -> Result<()> {
631        self.out.write_char('"')?;
632        for ch in s.chars() {
633            match ch {
634                '\\' => self.out.write_str("\\\\")?,
635                '"' => self.out.write_str("\\\"")?,
636                // YAML named escapes for common control characters
637                '\0' => self.out.write_str("\\0")?,
638                '\u{7}' => self.out.write_str("\\a")?,
639                '\u{8}' => self.out.write_str("\\b")?,
640                '\t' => self.out.write_str("\\t")?,
641                '\n' => self.out.write_str("\\n")?,
642                '\u{b}' => self.out.write_str("\\v")?,
643                '\u{c}' => self.out.write_str("\\f")?,
644                '\r' => self.out.write_str("\\r")?,
645                '\u{1b}' => self.out.write_str("\\e")?,
646                // Unicode BOM should use the standard \u escape rather than Rust's \u{...}
647                '\u{FEFF}' => self.out.write_str("\\uFEFF")?,
648                // YAML named escapes for Unicode separators
649                '\u{0085}' => self.out.write_str("\\N")?,
650                '\u{2028}' => self.out.write_str("\\L")?,
651                '\u{2029}' => self.out.write_str("\\P")?,
652                c if (c as u32) <= 0xFF
653                    && (c.is_control() || (0x7F..=0x9F).contains(&(c as u32))) =>
654                {
655                    write!(self.out, "\\x{:02X}", c as u32)?
656                }
657                c if (c as u32) <= 0xFFFF
658                    && (c.is_control() || (0x7F..=0x9F).contains(&(c as u32))) =>
659                {
660                    write!(self.out, "\\u{:04X}", c as u32)?
661                }
662                c => self.out.write_char(c)?,
663            }
664        }
665        self.out.write_char('"')?;
666        Ok(())
667    }
668
669    /// Like `write_plain_or_quoted`, but intended for VALUE position where ':' is allowed.
670    #[inline]
671    fn write_plain_or_quoted_value(&mut self, s: &str) -> Result<()> {
672        if self.quote_all {
673            // In quote_all mode: prefer single quotes, use double quotes when needed
674            if Self::needs_double_quotes(s) {
675                self.write_quoted(s)
676            } else {
677                self.write_single_quoted(s)
678            }
679        } else if is_plain_value_safe(s, self.yaml_12, self.in_flow > 0) {
680            self.out.write_str(s)?;
681            Ok(())
682        } else {
683            // Force quoted style for problematic value tokens (commas/brackets, bool/num-like, etc.).
684            self.write_quoted(s)
685        }
686    }
687
688    /// Serialize a tagged scalar of the form `!!Type value` using plain or quoted style for
689    /// the value depending on its content.
690    fn serialize_tagged_scalar(&mut self, enum_name: &str, variant: &str) -> Result<()> {
691        self.write_scalar_prefix_if_anchor()?;
692        if self.at_line_start {
693            self.write_indent(self.depth)?;
694        }
695        self.out.write_str("!!")?;
696        self.out.write_str(enum_name)?;
697        self.out.write_char(' ')?;
698        self.write_plain_or_quoted_value(variant)?;
699        self.write_end_of_scalar()
700    }
701
702    /// If an anchor is pending for the next scalar, emit `&name ` prefix.
703    /// Used for in-flow scalars.
704    #[inline]
705    fn write_scalar_prefix_if_anchor(&mut self) -> Result<()> {
706        if let Some(id) = self.pending_anchor_id.take() {
707            if self.at_line_start {
708                self.write_indent(self.depth)?;
709            }
710            self.out.write_char('&')?;
711            self.write_anchor_name(id)?;
712            self.out.write_char(' ')?;
713        }
714        Ok(())
715    }
716
717    /// If an anchor is pending for the next complex node (seq/map),
718    /// emit it on its own line before the node.
719    #[inline]
720    fn write_anchor_for_complex_node(&mut self) -> Result<()> {
721        if let Some(id) = self.pending_anchor_id.take() {
722            if self.at_line_start {
723                self.write_indent(self.depth)?;
724            }
725            self.write_space_if_pending()?;
726            self.out.write_char('&')?;
727            self.write_anchor_name(id)?;
728            self.newline()?;
729        }
730        Ok(())
731    }
732
733    /// Emit an alias `*name`. Adds a newline in block style.
734    /// Used when a previously defined anchor is referenced again.
735    #[inline]
736    fn write_alias_id(&mut self, id: AnchorId) -> Result<()> {
737        if self.at_line_start {
738            self.write_indent(self.depth)?;
739        }
740        self.write_space_if_pending()?;
741        self.out.write_char('*')?;
742        self.write_anchor_name(id)?;
743        // Use the shared end-of-scalar path so pending inline comments are appended in block style
744        self.write_end_of_scalar()?;
745        Ok(())
746    }
747
748    /// Determine whether the next sequence should be emitted in flow style.
749    /// Consumes any pending flow hint.
750    #[inline]
751    fn take_flow_for_seq(&mut self) -> bool {
752        if self.in_flow > 0 {
753            true
754        } else {
755            matches!(self.pending_flow.take(), Some(PendingFlow::AnySeq))
756        }
757    }
758    /// Determine whether the next mapping should be emitted in flow style.
759    /// Consumes any pending flow hint.
760    #[inline]
761    fn take_flow_for_map(&mut self) -> bool {
762        if self.in_flow > 0 {
763            true
764        } else {
765            matches!(self.pending_flow.take(), Some(PendingFlow::AnyMap))
766        }
767    }
768
769    /// Temporarily mark that we are inside a flow container while running `f`.
770    /// Ensures proper comma insertion and line handling for nested flow nodes.
771    #[inline]
772    fn with_in_flow<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
773        self.in_flow += 1;
774        let r = f(self);
775        self.in_flow -= 1;
776        r
777    }
778}
779
780// ------------------------------------------------------------
781// Impl Serializer for YamlSerializer
782// ------------------------------------------------------------
783
784impl<'a, 'b, W: Write> Serializer for &'a mut YamlSerializer<'b, W> {
785    type Ok = ();
786    type Error = Error;
787
788    type SerializeSeq = SeqSer<'a, 'b, W>;
789    type SerializeTuple = SeqSer<'a, 'b, W>;
790    type SerializeTupleStruct = TupleSer<'a, 'b, W>;
791    type SerializeTupleVariant = TupleVariantSer<'a, 'b, W>;
792    type SerializeMap = MapSer<'a, 'b, W>;
793    type SerializeStruct = MapSer<'a, 'b, W>;
794    type SerializeStructVariant = StructVariantSer<'a, 'b, W>;
795
796    // -------- Scalars --------
797
798    fn serialize_bool(self, v: bool) -> Result<()> {
799        self.write_space_if_pending()?;
800        self.write_scalar_prefix_if_anchor()?;
801        if self.at_line_start {
802            self.write_indent(self.depth)?;
803        }
804        self.out.write_str(if v { "true" } else { "false" })?;
805        self.write_end_of_scalar()?;
806        Ok(())
807    }
808
809    fn serialize_i8(self, v: i8) -> Result<()> {
810        self.serialize_i64(v as i64)
811    }
812    fn serialize_i16(self, v: i16) -> Result<()> {
813        self.serialize_i64(v as i64)
814    }
815    fn serialize_i32(self, v: i32) -> Result<()> {
816        self.serialize_i64(v as i64)
817    }
818    fn serialize_i64(self, v: i64) -> Result<()> {
819        self.write_space_if_pending()?;
820        self.write_scalar_prefix_if_anchor()?;
821        if self.at_line_start {
822            self.write_indent(self.depth)?;
823        }
824        write!(self.out, "{}", v)?;
825        self.write_end_of_scalar()?;
826        Ok(())
827    }
828
829    fn serialize_i128(self, v: i128) -> Result<()> {
830        self.write_space_if_pending()?;
831        self.write_scalar_prefix_if_anchor()?;
832        if self.at_line_start {
833            self.write_indent(self.depth)?;
834        }
835        write!(self.out, "{}", v)?;
836        self.write_end_of_scalar()?;
837        Ok(())
838    }
839
840    fn serialize_u8(self, v: u8) -> Result<()> {
841        self.serialize_u64(v as u64)
842    }
843    fn serialize_u16(self, v: u16) -> Result<()> {
844        self.serialize_u64(v as u64)
845    }
846    fn serialize_u32(self, v: u32) -> Result<()> {
847        self.serialize_u64(v as u64)
848    }
849    fn serialize_u64(self, v: u64) -> Result<()> {
850        self.write_space_if_pending()?;
851        self.write_scalar_prefix_if_anchor()?;
852        if self.at_line_start {
853            self.write_indent(self.depth)?;
854        }
855        write!(self.out, "{}", v)?;
856        self.write_end_of_scalar()?;
857        Ok(())
858    }
859
860    fn serialize_u128(self, v: u128) -> Result<()> {
861        self.write_space_if_pending()?;
862        self.write_scalar_prefix_if_anchor()?;
863        if self.at_line_start {
864            self.write_indent(self.depth)?;
865        }
866        write!(self.out, "{}", v)?;
867        self.write_end_of_scalar()?;
868        Ok(())
869    }
870
871    fn serialize_f32(self, v: f32) -> Result<()> {
872        self.write_space_if_pending()?;
873        self.write_scalar_prefix_if_anchor()?;
874        if self.at_line_start {
875            self.write_indent(self.depth)?;
876        }
877        zmij_format::write_float_string(self.out, v)?;
878        self.write_end_of_scalar()
879    }
880
881    fn serialize_f64(self, v: f64) -> Result<()> {
882        self.write_space_if_pending()?;
883        self.write_scalar_prefix_if_anchor()?;
884        if self.at_line_start {
885            self.write_indent(self.depth)?;
886        }
887        zmij_format::write_float_string(self.out, v)?;
888        self.write_end_of_scalar()
889    }
890
891    fn serialize_char(self, v: char) -> Result<()> {
892        self.write_space_if_pending()?;
893        let mut buf = [0u8; 4];
894        self.serialize_str(v.encode_utf8(&mut buf))
895    }
896
897    fn serialize_str(self, v: &str) -> Result<()> {
898        #[inline]
899        fn block_indent_indicator_digit(indent_n: usize) -> Result<char> {
900            char::from_digit(indent_n as u32, 10).ok_or_else(|| {
901                Error::custom("indentation indicator must be a single digit (1..=9)")
902            })
903        }
904
905        // If no explicit style pending, auto-select block style.
906        //
907        // Controlled by `prefer_block_scalars`:
908        //  - multiline + long (by folded_wrap_col) → literal (|)
909        //  - otherwise, multiline → literal (|) only when newlines are the only reason plain
910        //    style is unsafe
911        //  - single-line + long (by folded_wrap_col) → folded (>)
912        //
913        // Also skip block scalars when quote_all is enabled - use quoted strings instead.
914        if self.pending_str_style.is_none() && self.in_flow == 0 && !self.quote_all {
915            use self::quoting::is_plain_value_safe;
916
917            if v.contains('\n') {
918                if self.prefer_block_scalars {
919                    // If it's already multiline and long, emit literal block style for readability.
920                    let char_len = v.chars().count();
921                    if char_len > self.folded_wrap_col {
922                        self.pending_str_style = Some(StrStyle::Literal);
923                        self.pending_str_from_auto = true;
924                    } else {
925                        // If removing newlines makes it plain-safe, then the only problem was
926                        // newlines → allow literal block style. Otherwise, don't auto-select block
927                        // style so that quoting logic handles it (e.g., values ending with ':').
928                        let trimmed = v.trim_end_matches('\n');
929                        let normalized = trimmed.replace('\n', " ");
930                        if is_plain_value_safe(&normalized, self.yaml_12, false) {
931                            self.pending_str_style = Some(StrStyle::Literal);
932                            self.pending_str_from_auto = true;
933                        }
934                    }
935                }
936            } else if self.prefer_block_scalars {
937                // Single-line string. If it needs quoting as a value, don't auto-fold.
938                let needs_quoting = !is_plain_value_safe(v, self.yaml_12, false);
939                if !needs_quoting {
940                    // Measure in characters, not bytes.
941                    if v.chars().count() > self.folded_wrap_col {
942                        self.pending_str_style = Some(StrStyle::Folded);
943                        self.pending_str_from_auto = true;
944                    }
945                }
946            }
947        }
948        if let Some(style) = self.pending_str_style.take() {
949            // Emit block string. If we are a mapping value, YAML requires a space after ':'.
950            // Insert it now if pending.
951            //
952            // IMPORTANT: capture whether we were in a map-value position *before* clearing
953            // `pending_space_after_colon`, as that context influences indentation.
954            let was_map_value = self.pending_space_after_colon;
955            self.write_space_if_pending()?;
956            // Determine base indentation for the block scalar header/body.
957            //
958            // Important: `after_dash_depth` is only meaningful for the immediate node that
959            // follows a sequence dash ("- "). It must NOT affect nested scalars inside a
960            // mapping that happens to be a sequence element, otherwise block scalar bodies
961            // become under-indented (invalid YAML).
962            //
963            // For map values (we are mid-line after `key:`), prefer the mapping depth.
964            // Otherwise, if we are starting a new node right after a dash, use that depth.
965            let base = if was_map_value {
966                self.current_map_depth.unwrap_or(self.depth)
967            } else {
968                // Use after_dash_depth when available (we're after a sequence dash),
969                // regardless of at_line_start (which is false after writing "- ").
970                self.after_dash_depth.unwrap_or(self.depth)
971            };
972            if self.at_line_start {
973                self.write_indent(base)?;
974            }
975            // Compute the indentation indicator N for block scalars.
976            // N = indent_step * body_base = number of spaces the parser will strip.
977            // We must emit an explicit indicator when the first non-empty content line
978            // has leading whitespace, so the parser knows how much to strip.
979            let body_base = base + 1;
980            let indent_n = self.indent_step * body_base;
981
982            // Check if we need an explicit indentation indicator.
983            // Required when the first non-empty line has leading whitespace.
984            let content_trimmed = v.trim_end_matches('\n');
985            let first_line_spaces = self::wrapping::first_line_leading_spaces(content_trimmed);
986            let needs_indicator = first_line_spaces > 0;
987
988            // If N > 9, YAML parsers reject it. Fall back to quoting.
989            if needs_indicator && indent_n > 9 {
990                // Reset state and fall through to quoted string handling
991                self.pending_str_style = None;
992                self.pending_str_from_auto = false;
993                self.write_scalar_prefix_if_anchor()?;
994                self.write_plain_or_quoted_value(v)?;
995                self.write_end_of_scalar()?;
996                return Ok(());
997            }
998
999            match style {
1000                StrStyle::Literal => {
1001                    // Determine trailing newline count to select chomp indicator:
1002                    //  - 0 → "|-" (strip)
1003                    //  - 1 → "|" (clip)
1004                    //  - >=2 → "|+" (keep)
1005                    let content = v.trim_end_matches('\n');
1006                    let trailing_nl = v.len() - content.len();
1007
1008                    // Write block scalar header: | or |N with optional chomp indicator
1009                    self.out.write_char('|')?;
1010                    if needs_indicator {
1011                        // Write the indentation indicator digit
1012                        let digit = block_indent_indicator_digit(indent_n)?;
1013                        self.out.write_char(digit)?;
1014                    }
1015                    match trailing_nl {
1016                        0 => self.out.write_char('-')?,
1017                        1 => {} // clip is the default, no indicator needed
1018                        _ => self.out.write_char('+')?,
1019                    }
1020                    self.newline()?;
1021
1022                    // Emit body lines. For non-empty content, write each line exactly once.
1023                    // For keep chomping (>=2), append (trailing_nl - 1) visual empty lines.
1024                    // Special case: empty original content with at least one trailing newline
1025                    // should produce a single empty content line (tests expect this for "\n").
1026                    // Precompute body indent string once for the entire block
1027                    let mut indent_buf: String = String::new();
1028                    let spaces = self.indent_step * body_base;
1029                    if spaces > 0 {
1030                        indent_buf.reserve(spaces);
1031                        for _ in 0..spaces {
1032                            indent_buf.push(' ');
1033                        }
1034                    }
1035                    let indent_str = indent_buf.as_str();
1036
1037                    if content.is_empty() {
1038                        if trailing_nl >= 1 {
1039                            self.out.write_str(indent_str)?;
1040                            self.at_line_start = false;
1041                            // write a single empty content line
1042                            self.newline()?;
1043                        }
1044                    } else {
1045                        for line in content.split('\n') {
1046                            self.out.write_str(indent_str)?;
1047                            self.at_line_start = false;
1048                            self.out.write_str(line)?;
1049                            self.newline()?;
1050                        }
1051                        if trailing_nl >= 2 {
1052                            for _ in 0..(trailing_nl - 1) {
1053                                self.out.write_str(indent_str)?;
1054                                self.at_line_start = false;
1055                                self.newline()?;
1056                            }
1057                        }
1058                    }
1059                }
1060                StrStyle::Folded => {
1061                    // Write block scalar header: > or >N with optional chomp indicator
1062                    self.out.write_char('>')?;
1063                    if needs_indicator {
1064                        // Write the indentation indicator digit
1065                        let digit = block_indent_indicator_digit(indent_n)?;
1066                        self.out.write_char(digit)?;
1067                    }
1068                    if self.pending_str_from_auto {
1069                        // Auto-selected folded style: choose chomping based on trailing newlines
1070                        // to preserve exact content on round-trip.
1071                        let content = v.trim_end_matches('\n');
1072                        let trailing_nl = v.len() - content.len();
1073                        match trailing_nl {
1074                            0 => self.out.write_char('-')?,
1075                            1 => {} // clip is the default, no indicator needed
1076                            _ => self.out.write_char('+')?,
1077                        }
1078                    }
1079                    // Note: Explicit FoldStr/FoldString wrappers historically used plain '>'
1080                    // regardless of trailing newline; keep that behavior for compatibility.
1081                    self.newline()?;
1082                    self.write_folded_block(v, body_base)?;
1083                }
1084            }
1085            // reset auto flag after using pending style
1086            self.pending_str_from_auto = false;
1087            return Ok(());
1088        }
1089        self.write_space_if_pending()?;
1090        self.write_scalar_prefix_if_anchor()?;
1091        if self.at_line_start {
1092            self.write_indent(self.depth)?;
1093        }
1094        // Special-case: prefer single-quoted style for select 1-char punctuation to
1095        // match expected YAML output in tests ('.', '#', '-').
1096        if v.len() == 1
1097            && let Some(ch) = v.chars().next()
1098            && (ch == '.' || ch == '#' || ch == '-')
1099        {
1100            self.out.write_char('\'')?;
1101            self.out.write_char(ch)?;
1102            self.out.write_char('\'')?;
1103            self.write_end_of_scalar()?;
1104            return Ok(());
1105        }
1106        self.write_plain_or_quoted_value(v)?;
1107        self.write_end_of_scalar()?;
1108        Ok(())
1109    }
1110
1111    fn serialize_bytes(self, v: &[u8]) -> Result<()> {
1112        // Two behaviors are required by tests:
1113        // - Top-level &[u8] should serialize as a block sequence of integers.
1114        // - Fields using #[serde(with = "serde_bytes")] should serialize as a tagged !!binary
1115        //   base64 scalar inline after "key: ". The latter ends up calling serialize_bytes in
1116        //   value position (mid-line), whereas plain Vec<u8> without serde_bytes goes through
1117        //   serialize_seq instead. Distinguish by whether we are at the start of a line.
1118        if self.at_line_start {
1119            // Top-level or start-of-line: emit as sequence of numbers
1120            let mut seq = self.serialize_seq(Some(v.len()))?;
1121            for b in v {
1122                serde::ser::SerializeSeq::serialize_element(&mut seq, b)?;
1123            }
1124            return serde::ser::SerializeSeq::end(seq);
1125        }
1126
1127        // Inline value position: emit !!binary with base64.
1128        self.write_space_if_pending()?;
1129        self.write_scalar_prefix_if_anchor()?;
1130        // No indent needed mid-line; mirror serialize_str behavior.
1131        self.out.write_str("!!binary ")?;
1132        let mut s = String::new();
1133        B64.encode_string(v, &mut s);
1134        self.out.write_str(&s)?;
1135        self.write_end_of_scalar()?;
1136        Ok(())
1137    }
1138
1139    fn serialize_none(self) -> Result<()> {
1140        self.write_space_if_pending()?;
1141        self.last_value_was_block = false;
1142        if self.at_line_start {
1143            self.write_indent(self.depth)?;
1144        }
1145        self.out.write_str("null")?;
1146        self.write_end_of_scalar()?;
1147        Ok(())
1148    }
1149
1150    fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
1151        value.serialize(self)
1152    }
1153
1154    fn serialize_unit(self) -> Result<()> {
1155        self.write_space_if_pending()?;
1156        self.last_value_was_block = false;
1157        if self.at_line_start {
1158            self.write_indent(self.depth)?;
1159        }
1160        self.out.write_str("null")?;
1161        self.write_end_of_scalar()?;
1162        Ok(())
1163    }
1164
1165    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
1166        self.serialize_unit()
1167    }
1168
1169    fn serialize_unit_variant(
1170        self,
1171        name: &'static str,
1172        _variant_index: u32,
1173        variant: &'static str,
1174    ) -> Result<()> {
1175        // If we are in a mapping value position, insert the deferred space after ':'
1176        self.write_space_if_pending()?;
1177        if self.tagged_enums {
1178            self.serialize_tagged_scalar(name, variant)
1179        } else {
1180            self.serialize_str(variant)
1181        }
1182    }
1183
1184    fn serialize_newtype_struct<T: ?Sized + Serialize>(
1185        self,
1186        name: &'static str,
1187        value: &T,
1188    ) -> Result<()> {
1189        // Flow hints & block-string hints:
1190        match name {
1191            NAME_FLOW_SEQ => {
1192                self.pending_flow = Some(PendingFlow::AnySeq);
1193                return value.serialize(self);
1194            }
1195            NAME_FLOW_MAP => {
1196                self.pending_flow = Some(PendingFlow::AnyMap);
1197                return value.serialize(self);
1198            }
1199            NAME_LIT_STR => {
1200                // Always use literal block style for LitStr/LitString wrappers.
1201                // Choose chomping based on trailing newlines during actual emission.
1202                // Capture the inner string first.
1203                let mut cap = StrCapture::default();
1204                value.serialize(&mut cap)?;
1205                let s = cap.finish()?;
1206                self.pending_str_style = Some(StrStyle::Literal);
1207                return self.serialize_str(&s);
1208            }
1209            NAME_FOLD_STR => {
1210                let mut cap = StrCapture::default();
1211                value.serialize(&mut cap)?;
1212                let s = cap.finish()?;
1213                let is_multiline = s.contains('\n');
1214                if !is_multiline && s.len() < self.min_fold_chars {
1215                    return self.serialize_str(&s);
1216                }
1217                self.pending_str_style = Some(StrStyle::Folded);
1218                return self.serialize_str(&s);
1219            }
1220            NAME_SPACE_AFTER => {
1221                // Serialize the value, then emit an empty line after (only in block style).
1222                let result = value.serialize(&mut *self);
1223                if self.in_flow == 0 {
1224                    // Emit an extra blank line after the value
1225                    self.newline()?;
1226                }
1227                return result;
1228            }
1229            _ => {}
1230        }
1231        // default: ignore the name, serialize the inner as-is
1232        value.serialize(self)
1233    }
1234
1235    fn serialize_newtype_variant<T: ?Sized + Serialize>(
1236        self,
1237        _name: &'static str,
1238        _variant_index: u32,
1239        variant: &'static str,
1240        value: &T,
1241    ) -> Result<()> {
1242        // If we are the value of a mapping key, YAML forbids "key: Variant: value" inline.
1243        // Emit the variant mapping on the next line indented one level. Also, do not insert
1244        // a space after the colon when the value may itself be a mapping; instead, defer
1245        // space insertion to the value serializer via pending_space_after_colon.
1246        if self.pending_space_after_colon {
1247            // consume the pending space request and start a new line
1248            self.pending_space_after_colon = false;
1249            self.newline()?;
1250            // When used as a mapping value, indent relative to the parent mapping's base,
1251            // not the serializer's current depth (which may still be the outer level).
1252            let base = self.current_map_depth.unwrap_or(self.depth);
1253            self.write_indent(base + 1)?;
1254            self.write_plain_or_quoted(variant)?;
1255            // Write ':' without trailing space, then mark that a space may be needed
1256            // if the following value is a scalar.
1257            self.out.write_str(":")?;
1258            self.pending_space_after_colon = true;
1259            self.at_line_start = false;
1260            // Do not let any inline-after-dash hint leak into the variant's inner value.
1261            // After `Variant:`, the next node is in value position and must choose its own layout.
1262            self.pending_inline_map = false;
1263            // Ensure that if the value is another variant or a mapping/sequence,
1264            // it indents under this variant label rather than the parent map key.
1265            let prev_map_depth = self.current_map_depth.replace(base + 1);
1266            let res = value.serialize(&mut *self);
1267            self.current_map_depth = prev_map_depth;
1268            return res;
1269        }
1270        // Otherwise (top-level or sequence context).
1271        if self.at_line_start {
1272            self.write_indent(self.depth)?;
1273        }
1274        self.write_plain_or_quoted(variant)?;
1275        // Write ':' without a space and defer spacing/newline to the value serializer.
1276        self.out.write_str(":")?;
1277        self.pending_space_after_colon = true;
1278        self.at_line_start = false;
1279        // Do not let SeqSer's "inline first key after dash" hint leak into the variant's inner value.
1280        // Without this, a struct/map value can start as `Variant: a: 1`.
1281        self.pending_inline_map = false;
1282        // If this variant is inside a block sequence element (`- Variant:`), ensure the nested
1283        // value indents under the variant label rather than aligning with the list indentation.
1284        // SeqSer stores the dash's indentation depth in `after_dash_depth`.
1285        if let Some(d) = self.after_dash_depth.take() {
1286            let prev_map_depth = self.current_map_depth.replace(d + 1);
1287            let res = value.serialize(&mut *self);
1288            self.current_map_depth = prev_map_depth;
1289            res
1290        } else {
1291            value.serialize(&mut *self)
1292        }
1293    }
1294
1295    // -------- Collections --------
1296
1297    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
1298        let flow = self.take_flow_for_seq();
1299        if flow {
1300            self.write_scalar_prefix_if_anchor()?;
1301            // Ensure a space after a preceding colon when this sequence is a mapping value.
1302            self.write_space_if_pending()?;
1303            if self.at_line_start {
1304                self.write_indent(self.depth)?;
1305            }
1306            self.out.write_str("[")?;
1307            self.at_line_start = false;
1308            let depth_next = self.depth; // inline
1309            Ok(SeqSer {
1310                ser: self,
1311                depth: depth_next,
1312                flow: true,
1313                first: true,
1314            })
1315        } else {
1316            // Block sequence. Decide indentation based on whether this is after a map key or after a list dash.
1317            let was_inline_value = !self.at_line_start;
1318
1319            // If we are a value following a block sibling, force a newline now.
1320            // However, if a complex-node anchor is pending, we must keep `key: &aN` inline;
1321            // `write_anchor_for_complex_node` will handle emitting the anchor and newline.
1322            if self.pending_space_after_colon
1323                && self.last_value_was_block
1324                && self.pending_anchor_id.is_none()
1325            {
1326                self.pending_space_after_colon = false;
1327                if !self.at_line_start {
1328                    self.newline()?;
1329                }
1330                // Consume the sibling-block marker; it should not affect nested nodes.
1331                self.last_value_was_block = false;
1332            }
1333
1334            // For block sequences nested under another dash, keep the first inner dash inline.
1335            // Style expectations in tests prefer the compact form:
1336            // - - 1
1337            // instead of:
1338            // -
1339            //   - 1
1340            let inline_first = (!self.at_line_start)
1341                && self.after_dash_depth.is_some()
1342                && !self.pending_space_after_colon;
1343            // If we are a mapping value (space after colon was pending), we will handle
1344            // the newline later in SeqSer::serialize_element to keep empty sequences inline.
1345            self.write_anchor_for_complex_node()?;
1346            if inline_first {
1347                // Keep staged inline (pending_inline_map) so the child can inline its first dash.
1348                // Ensure we stay mid-line so the child can emit its first dash inline.
1349                self.at_line_start = false;
1350            } else if was_inline_value {
1351                // Mid-line start. If we are here due to a map value (after ':'), defer the newline
1352                // decision until the first element is emitted so that empty sequences can stay inline
1353                // as `key: []`. If we are here due to a list dash, keep inline.
1354                // Intentionally do not clear `pending_space_after_colon` and do not newline here.
1355            }
1356            // Indentation policy mirrors serialize_map:
1357            // - After a list dash inline_first: base is dash depth; indent one level deeper.
1358            // - As a value after a map key: base is current_map_depth (if set), indent one level deeper.
1359            // - Otherwise (top-level or already at line start): base is current depth.
1360            let base = if inline_first {
1361                self.after_dash_depth.unwrap_or(self.depth)
1362            } else if was_inline_value && self.current_map_depth.is_some() {
1363                self.current_map_depth.unwrap_or(self.depth)
1364            } else {
1365                self.depth
1366            };
1367            // For sequences used as a mapping value, indent them one level deeper so the dash is
1368            // nested under the parent key (consistent with serde_yaml's formatting). Keep block
1369            // sequences inline only when they immediately follow another dash.
1370            let depth_next = if inline_first {
1371                base + 1
1372            } else if was_inline_value {
1373                if self.compact_list_indent && self.current_map_depth.is_some() {
1374                    base
1375                } else {
1376                    base + 1
1377                }
1378            } else {
1379                base
1380            };
1381            // Starting a complex (block) sequence: drop any staged inline comment.
1382            self.pending_inline_comment = None;
1383            Ok(SeqSer {
1384                ser: self,
1385                depth: depth_next,
1386                flow: false,
1387                first: true,
1388            })
1389        }
1390    }
1391
1392    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
1393        self.serialize_seq(Some(len))
1394    }
1395
1396    fn serialize_tuple_struct(
1397        self,
1398        name: &'static str,
1399        _len: usize,
1400    ) -> Result<Self::SerializeTupleStruct> {
1401        if name == NAME_TUPLE_ANCHOR {
1402            Ok(TupleSer::anchor_strong(self))
1403        } else if name == NAME_TUPLE_WEAK {
1404            Ok(TupleSer::anchor_weak(self))
1405        } else if name == NAME_TUPLE_COMMENTED {
1406            Ok(TupleSer::commented(self))
1407        } else {
1408            // Treat as normal block sequence
1409            Ok(TupleSer::normal(self))
1410        }
1411    }
1412
1413    fn serialize_tuple_variant(
1414        self,
1415        _name: &'static str,
1416        _variant_index: u32,
1417        variant: &'static str,
1418        _len: usize,
1419    ) -> Result<Self::SerializeTupleVariant> {
1420        // If we are the value of a mapping key, YAML forbids keeping a nested mapping
1421        // on the same line (e.g., "key: Variant:"). Move the variant mapping to the next line
1422        // indented under the parent mapping's base depth.
1423        if self.pending_space_after_colon {
1424            self.pending_space_after_colon = false;
1425            self.newline()?;
1426            let base = self.current_map_depth.unwrap_or(self.depth) + 1;
1427            self.write_indent(base)?;
1428            self.write_plain_or_quoted(variant)?;
1429            self.out.write_str(":\n")?;
1430            self.at_line_start = true;
1431            let depth_next = base + 1;
1432            return Ok(TupleVariantSer {
1433                ser: self,
1434                depth: depth_next,
1435            });
1436        }
1437        // Otherwise (top-level or sequence context).
1438        if self.at_line_start {
1439            self.write_indent(self.depth)?;
1440        }
1441        self.write_plain_or_quoted(variant)?;
1442        self.out.write_str(":\n")?;
1443        self.at_line_start = true;
1444        let mut depth_next = self.depth + 1;
1445        if let Some(d) = self.after_dash_depth.take() {
1446            depth_next = d + 2;
1447            self.pending_inline_map = false;
1448        }
1449        Ok(TupleVariantSer {
1450            ser: self,
1451            depth: depth_next,
1452        })
1453    }
1454
1455    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
1456        let flow = self.take_flow_for_map();
1457        if flow {
1458            self.write_scalar_prefix_if_anchor()?;
1459            // Ensure a space after a preceding colon when this mapping is a value.
1460            self.write_space_if_pending()?;
1461            if self.at_line_start {
1462                self.write_indent(self.depth)?;
1463            }
1464            self.out.write_str("{")?;
1465            self.at_line_start = false;
1466            let depth_next = self.depth;
1467            Ok(MapSer {
1468                ser: self,
1469                depth: depth_next,
1470                flow: true,
1471                first: true,
1472                last_key_complex: false,
1473                align_after_dash: false,
1474                inline_value_start: false,
1475            })
1476        } else {
1477            let inline_first = self.pending_inline_map;
1478            // We only consider "value position" when immediately after a mapping colon.
1479            let was_inline_value = self.pending_space_after_colon;
1480            let mut forced_newline = false;
1481
1482            // If we are a value following a block sibling, force a newline now.
1483            // However, if a complex-node anchor is pending, we must keep `key: &aN` inline;
1484            // `write_anchor_for_complex_node` will handle emitting the anchor and newline.
1485            if was_inline_value && self.last_value_was_block && self.pending_anchor_id.is_none() {
1486                self.pending_space_after_colon = false;
1487                if !self.at_line_start {
1488                    self.newline()?;
1489                }
1490                forced_newline = true;
1491                // Consume the sibling-block marker; it should not affect nested nodes.
1492                self.last_value_was_block = false;
1493            }
1494
1495            self.write_anchor_for_complex_node()?;
1496            if inline_first {
1497                // Suppress newline after a list dash for inline map first key.
1498                self.pending_inline_map = false;
1499                // Mark that this sequence element is a mapping printed inline after a dash.
1500                self.inline_map_after_dash = true;
1501            } else if was_inline_value {
1502                // Map used as a value after "key: ". If emitting braces for empty maps,
1503                // keep this mapping on the same line so that an empty map renders as "{}".
1504                //
1505                // IMPORTANT: if the map is known to be non-empty (len > 0), we must NOT keep it
1506                // inline (otherwise we can end up emitting the first entry as `key: a: 1`).
1507                // When len is unknown, we keep the legacy behavior and let MapSer decide once the
1508                // first key arrives.
1509                let known_empty = matches!(_len, Some(0));
1510                let known_non_empty = matches!(_len, Some(n) if n > 0);
1511
1512                if !self.empty_as_braces || known_non_empty {
1513                    // Move the mapping body to the next line.
1514                    // If an anchor was emitted, we are already at the start of a new line.
1515                    self.pending_space_after_colon = false;
1516                    if !self.at_line_start {
1517                        self.newline()?;
1518                    }
1519                } else if !known_empty {
1520                    // len is unknown: keep it inline for now (so empty maps can still render as
1521                    // `key: {}`), and let MapSer break the line when the first key arrives.
1522                }
1523            }
1524            // Indentation rules:
1525            // - Top-level (at line start, not after dash): use current depth.
1526            // - After dash inline first key or as a value: indent one level deeper for subsequent lines.
1527            // Use the current mapping's depth as base only when we are in a VALUE position.
1528            // For complex KEYS (non-scalar), keep using the current serializer depth so that
1529            // subsequent key lines indent relative to the "? " line, not the parent map's base.
1530            let base = if inline_first {
1531                self.after_dash_depth.unwrap_or(self.depth)
1532            } else if was_inline_value && self.current_map_depth.is_some() {
1533                self.current_map_depth.unwrap_or(self.depth)
1534            } else {
1535                self.depth
1536            };
1537            let depth_next = if inline_first || was_inline_value {
1538                base + 1
1539            } else {
1540                base
1541            };
1542            let inline_value_start_flag = was_inline_value
1543                && self.empty_as_braces
1544                && _len.is_none()
1545                && !inline_first
1546                && !forced_newline;
1547            Ok(MapSer {
1548                ser: self,
1549                depth: depth_next,
1550                flow: false,
1551                first: true,
1552                last_key_complex: false,
1553                align_after_dash: inline_first,
1554                inline_value_start: inline_value_start_flag,
1555            })
1556        }
1557    }
1558
1559    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
1560        self.serialize_map(Some(_len))
1561    }
1562
1563    fn serialize_struct_variant(
1564        self,
1565        _name: &'static str,
1566        _variant_index: u32,
1567        variant: &'static str,
1568        _len: usize,
1569    ) -> Result<Self::SerializeStructVariant> {
1570        // If we are the value of a mapping key, YAML forbids keeping a nested mapping
1571        // on the same line (e.g., "key: Variant:"). Move the variant mapping to the next line
1572        // indented under the parent mapping's base depth.
1573        let _was_inline_value = !self.at_line_start;
1574        if self.pending_space_after_colon {
1575            // Value position after a map key: start the variant mapping on the next line.
1576            self.pending_space_after_colon = false;
1577            self.newline()?;
1578            // Indent the variant name one level under the parent mapping.
1579            let base = self.current_map_depth.unwrap_or(self.depth) + 1;
1580            self.write_indent(base)?;
1581            self.write_plain_or_quoted(variant)?;
1582            self.out.write_str(":\n")?;
1583            self.at_line_start = true;
1584            // Fields indent one more level under the variant label.
1585            let depth_next = base + 1;
1586            return Ok(StructVariantSer {
1587                ser: self,
1588                depth: depth_next,
1589            });
1590        }
1591        // Otherwise (top-level or sequence context), emit the variant name at current depth.
1592        if self.at_line_start {
1593            self.write_indent(self.depth)?;
1594        }
1595        self.write_plain_or_quoted(variant)?;
1596        self.out.write_str(":\n")?;
1597        self.at_line_start = true;
1598        // Default indentation for fields under a plain variant line.
1599        let mut depth_next = self.depth + 1;
1600        // If this variant follows a list dash, indent two levels under the dash (one for the element, one for the mapping).
1601        if let Some(d) = self.after_dash_depth.take() {
1602            depth_next = d + 2;
1603            self.pending_inline_map = false;
1604        }
1605        Ok(StructVariantSer {
1606            ser: self,
1607            depth: depth_next,
1608        })
1609    }
1610}
1611
1612// ------------------------------------------------------------
1613// Seq / Tuple serializers
1614// ------------------------------------------------------------
1615
1616/// Serializer for sequences and tuples.
1617///
1618/// Created by `YamlSerializer::serialize_seq`/`serialize_tuple`. Holds a mutable
1619/// reference to the parent serializer and formatting state for the sequence.
1620pub struct SeqSer<'a, 'b, W: Write> {
1621    /// Parent YAML serializer.
1622    ser: &'a mut YamlSerializer<'b, W>,
1623    /// Target indentation depth for block-style items.
1624    depth: usize,
1625    /// Whether the sequence is being written in flow style (`[a, b]`).
1626    flow: bool,
1627    /// Whether the next element is the first (comma handling in flow style).
1628    first: bool,
1629}
1630
1631impl<'a, 'b, W: Write> SerializeTuple for SeqSer<'a, 'b, W> {
1632    type Ok = ();
1633    type Error = Error;
1634
1635    fn serialize_element<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<()> {
1636        SerializeSeq::serialize_element(self, v)
1637    }
1638    fn end(self) -> Result<()> {
1639        SerializeSeq::end(self)
1640    }
1641}
1642
1643// Re-implement SerializeSeq for SeqSer with correct end.
1644impl<'a, 'b, W: Write> SerializeSeq for SeqSer<'a, 'b, W> {
1645    type Ok = ();
1646    type Error = Error;
1647
1648    fn serialize_element<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<()> {
1649        if self.flow {
1650            if !self.first {
1651                self.ser.out.write_str(", ")?;
1652            }
1653            self.ser.with_in_flow(|s| v.serialize(s))?;
1654        } else {
1655            // If we are the value of a mapping key, we deferred the newline until we knew the
1656            // sequence is non-empty. Insert it now before emitting the first dash.
1657            if self.first && self.ser.pending_space_after_colon {
1658                self.ser.pending_space_after_colon = false;
1659                if !self.ser.at_line_start {
1660                    self.ser.newline()?;
1661                }
1662            }
1663            // If previous element was an inline map after a dash, just clear the flag; do not change depth.
1664            if !self.first && self.ser.inline_map_after_dash {
1665                self.ser.inline_map_after_dash = false;
1666            }
1667            if self.first && (!self.ser.at_line_start || self.ser.pending_inline_map) {
1668                // Inline the first element of this nested sequence right after the outer dash
1669                // (either we are already mid-line, or the parent staged inline via pending_inline_map).
1670                // Do not write indentation here.
1671            } else {
1672                self.ser.write_indent(self.depth)?;
1673            }
1674            self.ser.out.write_str("- ")?;
1675            self.ser.at_line_start = false;
1676            if self.first && self.ser.inline_map_after_dash {
1677                // We consumed the inline-after-dash behavior for this child sequence.
1678                self.ser.inline_map_after_dash = false;
1679            }
1680            // Capture the dash's indentation depth for potential struct-variant that follows.
1681            self.ser.after_dash_depth = Some(self.depth);
1682            // Hint to emit first key/element of a following mapping/sequence inline on the same line.
1683            self.ser.pending_inline_map = true;
1684            v.serialize(&mut *self.ser)?;
1685        }
1686        self.first = false;
1687        Ok(())
1688    }
1689
1690    fn end(self) -> Result<()> {
1691        if self.flow {
1692            let me = self;
1693            me.ser.out.write_str("]")?;
1694            if me.ser.in_flow == 0 {
1695                me.ser.newline()?;
1696            }
1697        } else if self.first {
1698            // Empty block-style sequence.
1699            if self.ser.empty_as_braces {
1700                // If we were pending a space after a colon (map value position), write it now.
1701                if self.ser.pending_space_after_colon {
1702                    self.ser.out.write_str(" ")?;
1703                    self.ser.pending_space_after_colon = false;
1704                } else if self.ser.at_line_start {
1705                    // If at line start, indent appropriately.
1706                    self.ser.write_indent(self.depth)?;
1707                }
1708                self.ser.out.write_str("[]")?;
1709                self.ser.newline()?;
1710            } else {
1711                // Preserve legacy behavior: just emit a newline (empty body).
1712                // Clear map-value pending state so it does not leak into following elements.
1713                self.ser.pending_space_after_colon = false;
1714                self.ser.newline()?;
1715            }
1716        } else {
1717            // Block collection finished and it was not empty.
1718            self.ser.last_value_was_block = true;
1719            // Clear any dash/inline hints so they cannot affect the next sibling value
1720            // (e.g., a mapping field following a block sequence value).
1721            self.ser.pending_inline_map = false;
1722            self.ser.after_dash_depth = None;
1723            self.ser.inline_map_after_dash = false;
1724        }
1725        Ok(())
1726    }
1727}
1728
1729// Tuple-struct serializer (normal or anchor payload)
1730/// Serializer for tuple-structs.
1731///
1732/// Used for three shapes:
1733/// - Normal tuple-structs (treated like sequences in block style),
1734/// - Internal strong-anchor payloads (`__yaml_anchor`),
1735/// - Internal weak-anchor payloads (`__yaml_weak_anchor`).
1736pub struct TupleSer<'a, 'b, W: Write> {
1737    /// Parent YAML serializer.
1738    ser: &'a mut YamlSerializer<'b, W>,
1739    /// Variant describing how to interpret fields.
1740    kind: TupleKind,
1741    /// Current field index being serialized.
1742    idx: usize,
1743    /// For normal tuples: target indentation depth.
1744    /// For weak/strong: temporary storage (ptr id or state).
1745    depth_for_normal: usize,
1746
1747    // ---- Extra fields for refactoring/perf/correctness ----
1748    /// For strong anchors: if Some(id) then we must emit an alias instead of a definition at field #2.
1749    strong_alias_id: Option<AnchorId>,
1750    /// For weak anchors: whether the `present` flag was true.
1751    weak_present: bool,
1752    /// Skip serializing the 3rd field (value) in weak case if present==false.
1753    skip_third: bool,
1754    /// For weak anchors: hold alias id if value should be emitted as alias in field #3.
1755    weak_alias_id: Option<AnchorId>,
1756    /// For commented wrapper: captured comment text from field #0.
1757    comment_text: Option<String>,
1758}
1759enum TupleKind {
1760    Normal,       // treat as block seq
1761    AnchorStrong, // [ptr, value]
1762    AnchorWeak,   // [ptr, present, value]
1763    Commented,    // [comment, value]
1764}
1765impl<'a, 'b, W: Write> TupleSer<'a, 'b, W> {
1766    /// Create a tuple serializer for normal tuple-structs.
1767    fn normal(ser: &'a mut YamlSerializer<'b, W>) -> Self {
1768        let depth_next = ser.depth + 1;
1769        Self {
1770            ser,
1771            kind: TupleKind::Normal,
1772            idx: 0,
1773            depth_for_normal: depth_next,
1774            strong_alias_id: None,
1775            weak_present: false,
1776            skip_third: false,
1777            weak_alias_id: None,
1778            comment_text: None,
1779        }
1780    }
1781    /// Create a tuple serializer for internal strong-anchor payloads.
1782    fn anchor_strong(ser: &'a mut YamlSerializer<'b, W>) -> Self {
1783        Self {
1784            ser,
1785            kind: TupleKind::AnchorStrong,
1786            idx: 0,
1787            depth_for_normal: 0,
1788            strong_alias_id: None,
1789            weak_present: false,
1790            skip_third: false,
1791            weak_alias_id: None,
1792            comment_text: None,
1793        }
1794    }
1795    /// Create a tuple serializer for internal weak-anchor payloads.
1796    fn anchor_weak(ser: &'a mut YamlSerializer<'b, W>) -> Self {
1797        Self {
1798            ser,
1799            kind: TupleKind::AnchorWeak,
1800            idx: 0,
1801            depth_for_normal: 0,
1802            strong_alias_id: None,
1803            weak_present: false,
1804            skip_third: false,
1805            weak_alias_id: None,
1806            comment_text: None,
1807        }
1808    }
1809    /// Create a tuple serializer for internal commented wrapper.
1810    fn commented(ser: &'a mut YamlSerializer<'b, W>) -> Self {
1811        Self {
1812            ser,
1813            kind: TupleKind::Commented,
1814            idx: 0,
1815            depth_for_normal: 0,
1816            strong_alias_id: None,
1817            weak_present: false,
1818            skip_third: false,
1819            weak_alias_id: None,
1820            comment_text: None,
1821        }
1822    }
1823}
1824
1825impl<'a, 'b, W: Write> SerializeTupleStruct for TupleSer<'a, 'b, W> {
1826    type Ok = ();
1827    type Error = Error;
1828
1829    fn serialize_field<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<()> {
1830        match self.kind {
1831            TupleKind::Normal => {
1832                if self.idx == 0 {
1833                    self.ser.write_anchor_for_complex_node()?;
1834                    if !self.ser.at_line_start {
1835                        self.ser.newline()?;
1836                    }
1837                }
1838                self.ser.write_indent(self.ser.depth + 1)?;
1839                self.ser.out.write_str("- ")?;
1840                self.ser.at_line_start = false;
1841                value.serialize(&mut *self.ser)?;
1842            }
1843            TupleKind::AnchorStrong => {
1844                match self.idx {
1845                    0 => {
1846                        // capture ptr, decide define vs alias
1847                        let mut cap = UsizeCapture::default();
1848                        value.serialize(&mut cap)?;
1849                        let ptr = cap.finish()?;
1850                        let (id, fresh) = self.ser.alloc_anchor_for(ptr);
1851                        if fresh {
1852                            self.ser.pending_anchor_id = Some(id); // define before value
1853                            self.strong_alias_id = None;
1854                        } else {
1855                            self.strong_alias_id = Some(id); // alias instead of value
1856                        }
1857                    }
1858                    1 => {
1859                        if let Some(id) = self.strong_alias_id.take() {
1860                            // Already defined earlier -> emit alias
1861                            self.ser.write_alias_id(id)?;
1862                        } else {
1863                            // First sight -> serialize value; pending_anchor_id (if any) will be emitted
1864                            value.serialize(&mut *self.ser)?;
1865                        }
1866                    }
1867                    _ => return Err(Error::unexpected("unexpected field in __yaml_anchor")),
1868                }
1869            }
1870            TupleKind::AnchorWeak => {
1871                match self.idx {
1872                    0 => {
1873                        let mut cap = UsizeCapture::default();
1874                        value.serialize(&mut cap)?;
1875                        let ptr = cap.finish()?;
1876                        self.depth_for_normal = ptr; // store ptr for fields #2/#3
1877                    }
1878                    1 => {
1879                        let mut bc = BoolCapture::default();
1880                        value.serialize(&mut bc)?;
1881                        self.weak_present = bc.finish()?;
1882                        if !self.weak_present {
1883                            // present == false: emit null and skip field #3
1884                            if self.ser.at_line_start {
1885                                self.ser.write_indent(self.ser.depth)?;
1886                            }
1887                            self.ser.out.write_str("null")?;
1888                            // Use shared end-of-scalar so pending inline comments (if any) are appended
1889                            self.ser.write_end_of_scalar()?;
1890                            self.skip_third = true;
1891                        } else {
1892                            let ptr = self.depth_for_normal;
1893                            let (id, fresh) = self.ser.alloc_anchor_for(ptr);
1894                            if fresh {
1895                                self.ser.pending_anchor_id = Some(id); // define before value
1896                                self.weak_alias_id = None;
1897                            } else {
1898                                self.weak_alias_id = Some(id); // alias in field #3
1899                            }
1900                        }
1901                    }
1902                    2 => {
1903                        if self.skip_third {
1904                            // nothing to do
1905                        } else if let Some(id) = self.weak_alias_id.take() {
1906                            self.ser.write_alias_id(id)?;
1907                        } else {
1908                            // definition path: pending_anchor_id (if any) will be placed automatically
1909                            value.serialize(&mut *self.ser)?;
1910                        }
1911                    }
1912                    _ => return Err(Error::unexpected("unexpected field in __yaml_weak_anchor")),
1913                }
1914            }
1915            TupleKind::Commented => {
1916                match self.idx {
1917                    0 => {
1918                        // Capture comment string
1919                        let mut sc = StrCapture::default();
1920                        value.serialize(&mut sc)?;
1921                        self.comment_text = Some(sc.finish()?);
1922                    }
1923                    1 => {
1924                        let comment = self.comment_text.take().unwrap_or_default();
1925                        let sanitized = YamlSerializer::<W>::sanitize_comment_text(&comment);
1926                        match self.ser.comment_position {
1927                            CommentPosition::Inline => {
1928                                if self.ser.in_flow == 0 && !sanitized.is_empty() {
1929                                    // Stage the comment so scalar/alias serializers append it inline via write_end_of_scalar.
1930                                    self.ser.pending_inline_comment = Some(sanitized);
1931                                }
1932                                // Serialize the inner value as-is. Complex values will ignore the comment (it will be cleared).
1933                                value.serialize(&mut *self.ser)?;
1934                                // Ensure no leftover staged comment leaks to subsequent tokens.
1935                                self.ser.pending_inline_comment = None;
1936                            }
1937                            CommentPosition::Above => {
1938                                let saved_depth = self.ser.depth;
1939                                let target_depth = self.ser.write_above_comment(&sanitized)?;
1940                                if let Some(depth) = target_depth {
1941                                    self.ser.depth = depth;
1942                                }
1943                                let result = value.serialize(&mut *self.ser);
1944                                self.ser.depth = saved_depth;
1945                                result?;
1946                                self.ser.pending_inline_comment = None;
1947                            }
1948                        }
1949                    }
1950                    _ => return Err(Error::unexpected("unexpected field in __yaml_commented")),
1951                }
1952            }
1953        }
1954        self.idx += 1;
1955        Ok(())
1956    }
1957
1958    fn end(self) -> Result<()> {
1959        Ok(())
1960    }
1961}
1962
1963// Tuple variant (enum Variant: ( ... ))
1964/// Serializer for tuple variants (enum Variant: ( ... )).
1965///
1966/// Created by `YamlSerializer::serialize_tuple_variant` to emit the variant name
1967/// followed by a block sequence of fields.
1968pub struct TupleVariantSer<'a, 'b, W: Write> {
1969    /// Parent YAML serializer.
1970    ser: &'a mut YamlSerializer<'b, W>,
1971    /// Target indentation depth for the fields.
1972    depth: usize,
1973}
1974impl<'a, 'b, W: Write> SerializeTupleVariant for TupleVariantSer<'a, 'b, W> {
1975    type Ok = ();
1976    type Error = Error;
1977
1978    fn serialize_field<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<()> {
1979        self.ser.write_indent(self.depth)?;
1980        self.ser.out.write_str("- ")?;
1981        self.ser.at_line_start = false;
1982        value.serialize(&mut *self.ser)
1983    }
1984    fn end(self) -> Result<()> {
1985        Ok(())
1986    }
1987}
1988
1989// ------------------------------------------------------------
1990// Map / Struct serializers
1991// ------------------------------------------------------------
1992
1993/// Serializer for maps and structs.
1994///
1995/// Created by `YamlSerializer::serialize_map`/`serialize_struct`. Manages indentation
1996/// and flow/block style for key-value pairs.
1997pub struct MapSer<'a, 'b, W: Write> {
1998    /// Parent YAML serializer.
1999    ser: &'a mut YamlSerializer<'b, W>,
2000    /// Target indentation depth for block-style entries.
2001    depth: usize,
2002    /// Whether the mapping is in flow style (`{k: v}`).
2003    flow: bool,
2004    /// Whether the next entry is the first (comma handling in flow style).
2005    first: bool,
2006    /// Whether the most recently serialized key was a complex (non-scalar) node.
2007    last_key_complex: bool,
2008    /// Align continuation lines under an inline-after-dash first key by adding 2 spaces.
2009    align_after_dash: bool,
2010    /// If true, this mapping began in a value position and stayed inline (after `key:`)
2011    /// so that an empty map can be serialized as `{}` right there. When the first key arrives,
2012    /// we must break the line and indent appropriately.
2013    inline_value_start: bool,
2014}
2015
2016impl<'a, 'b, W: Write> SerializeMap for MapSer<'a, 'b, W> {
2017    type Ok = ();
2018    type Error = Error;
2019
2020    fn serialize_key<T: ?Sized + Serialize>(&mut self, key: &T) -> Result<()> {
2021        if self.flow {
2022            if !self.first {
2023                self.ser.out.write_str(", ")?;
2024            }
2025            let text = scalar_key_to_string(key, self.ser.yaml_12)?;
2026            self.ser.out.write_str(&text)?;
2027            self.ser.out.write_str(": ")?;
2028            self.ser.at_line_start = false;
2029            self.last_key_complex = false;
2030        } else {
2031            // If this mapping started inline as a value (after "key:"), but now we
2032            // are about to emit the first entry, move to the next line before the key.
2033            if self.inline_value_start {
2034                // Cancel a pending space after ':' and break the line.
2035                if self.ser.pending_space_after_colon {
2036                    self.ser.pending_space_after_colon = false;
2037                }
2038                if !self.ser.at_line_start {
2039                    self.ser.newline()?;
2040                }
2041                self.inline_value_start = false;
2042            } else if !self.ser.at_line_start {
2043                self.ser.write_space_if_pending()?;
2044            }
2045
2046            // A new key in a block map should clear any pending inline hints from previous siblings.
2047            self.ser.after_dash_depth = None;
2048            self.ser.pending_inline_map = false;
2049            self.ser.last_value_was_block = false;
2050
2051            match scalar_key_to_string(key, self.ser.yaml_12) {
2052                Ok(text) => {
2053                    // Indent continuation lines. If this map started inline after a dash,
2054                    // align under the first key by adding two spaces instead of a full indent step.
2055                    if self.align_after_dash && self.ser.at_line_start {
2056                        let base = self.depth.saturating_sub(1);
2057                        for _ in 0..self.ser.indent_step * base {
2058                            self.ser.out.write_char(' ')?;
2059                        }
2060                        self.ser.out.write_str("  ")?; // width of "- "
2061                        self.ser.at_line_start = false;
2062                    } else {
2063                        self.ser.write_indent(self.depth)?;
2064                    }
2065                    self.ser.out.write_str(&text)?;
2066                    // Defer the decision to put a space vs. newline until we see the value type.
2067                    self.ser.out.write_str(":")?;
2068                    self.ser.pending_space_after_colon = true;
2069                    self.ser.at_line_start = false;
2070                    self.last_key_complex = false;
2071                }
2072                Err(Error::Unexpected { msg }) if msg == "non-scalar key" => {
2073                    self.ser.write_anchor_for_complex_node()?;
2074                    self.ser.write_indent(self.depth)?;
2075                    self.ser.out.write_str("? ")?;
2076                    self.ser.at_line_start = false;
2077
2078                    let saved_depth = self.ser.depth;
2079                    let saved_current_map_depth = self.ser.current_map_depth;
2080                    let saved_pending_inline_map = self.ser.pending_inline_map;
2081                    let saved_inline_map_after_dash = self.ser.inline_map_after_dash;
2082                    let saved_after_dash_depth = self.ser.after_dash_depth;
2083
2084                    self.ser.pending_inline_map = true;
2085                    self.ser.depth = self.depth;
2086                    // Provide a base depth for nested maps within this complex key so that
2087                    // continuation lines indent one level deeper than the parent mapping.
2088                    self.ser.current_map_depth = Some(self.depth);
2089                    self.ser.after_dash_depth = None;
2090                    key.serialize(&mut *self.ser)?;
2091
2092                    self.ser.depth = saved_depth;
2093                    self.ser.current_map_depth = saved_current_map_depth;
2094                    self.ser.pending_inline_map = saved_pending_inline_map;
2095                    self.ser.inline_map_after_dash = saved_inline_map_after_dash;
2096                    self.ser.after_dash_depth = saved_after_dash_depth;
2097                    // A complex key may have been serialized as a block collection, which sets
2098                    // `last_value_was_block`. That state must NOT affect the *value* of this same
2099                    // map entry (e.g. we still want `: x: 3` inline for composite-key maps).
2100                    self.ser.last_value_was_block = false;
2101                    self.last_key_complex = true;
2102                }
2103                Err(e) => return Err(e),
2104            }
2105        }
2106        Ok(())
2107    }
2108
2109    fn serialize_value<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<()> {
2110        if self.flow {
2111            self.ser.with_in_flow(|s| value.serialize(s))?;
2112        } else {
2113            let saved_pending_inline_map = self.ser.pending_inline_map;
2114            let saved_depth = self.ser.depth;
2115            if self.last_key_complex {
2116                if self.align_after_dash && self.ser.at_line_start {
2117                    let base = self.depth.saturating_sub(1);
2118                    for _ in 0..self.ser.indent_step * base {
2119                        self.ser.out.write_char(' ')?;
2120                    }
2121                    self.ser.out.write_str("  ")?;
2122                    self.ser.at_line_start = false;
2123                } else {
2124                    self.ser.write_indent(self.depth)?;
2125                }
2126                self.ser.out.write_str(":")?;
2127                self.ser.pending_space_after_colon = true;
2128                self.ser.pending_inline_map = true;
2129                self.ser.at_line_start = false;
2130                self.ser.depth = self.depth;
2131            }
2132            let prev_map_depth = self.ser.current_map_depth.replace(self.depth);
2133            let result = value.serialize(&mut *self.ser);
2134            self.ser.current_map_depth = prev_map_depth;
2135            // Always restore the parent's pending_inline_map to avoid leaking inline hints
2136            // across sibling values (e.g., after finishing a sequence value like `groups`).
2137            self.ser.pending_inline_map = saved_pending_inline_map;
2138            if self.last_key_complex {
2139                self.ser.depth = saved_depth;
2140                self.last_key_complex = false;
2141            }
2142            result?;
2143        }
2144        self.first = false;
2145        Ok(())
2146    }
2147
2148    fn end(self) -> Result<()> {
2149        if self.flow {
2150            self.ser.out.write_str("}")?;
2151            if self.ser.in_flow == 0 {
2152                self.ser.newline()?;
2153            }
2154        } else if self.first {
2155            // Empty block-style map.
2156            if self.ser.empty_as_braces {
2157                // If we were pending a space after a colon (map value position), write it now.
2158                if self.ser.pending_space_after_colon {
2159                    self.ser.out.write_str(" ")?;
2160                    self.ser.pending_space_after_colon = false;
2161                }
2162                // If at line start, indent appropriately.
2163                if self.ser.at_line_start {
2164                    // If we are aligning after a dash, mimic the indentation logic used for keys.
2165                    if self.align_after_dash {
2166                        let base = self.depth.saturating_sub(1);
2167                        for _ in 0..self.ser.indent_step * base {
2168                            self.ser.out.write_char(' ')?;
2169                        }
2170                        self.ser.out.write_str("  ")?; // width of "- "
2171                        self.ser.at_line_start = false;
2172                    } else {
2173                        self.ser.write_indent(self.depth)?;
2174                    }
2175                }
2176                self.ser.out.write_str("{}")?;
2177                self.ser.newline()?;
2178            } else {
2179                // Preserve legacy behavior: just emit a newline (empty body).
2180                self.ser.newline()?;
2181            }
2182        } else {
2183            // Block collection finished and it was not empty.
2184            self.ser.last_value_was_block = true;
2185        }
2186        Ok(())
2187    }
2188}
2189impl<'a, 'b, W: Write> SerializeStruct for MapSer<'a, 'b, W> {
2190    type Ok = ();
2191    type Error = Error;
2192
2193    fn serialize_field<T: ?Sized + Serialize>(
2194        &mut self,
2195        key: &'static str,
2196        value: &T,
2197    ) -> Result<()> {
2198        SerializeMap::serialize_key(self, &key)?;
2199        SerializeMap::serialize_value(self, value)
2200    }
2201    fn end(self) -> Result<()> {
2202        SerializeMap::end(self)
2203    }
2204}
2205
2206/// Serializer for struct variants (enum Variant: { ... }).
2207///
2208/// Created by `YamlSerializer::serialize_struct_variant` to emit the variant name
2209/// followed by a block mapping of fields.
2210pub struct StructVariantSer<'a, 'b, W: Write> {
2211    /// Parent YAML serializer.
2212    ser: &'a mut YamlSerializer<'b, W>,
2213    /// Target indentation depth for the fields.
2214    depth: usize,
2215}
2216impl<'a, 'b, W: Write> SerializeStructVariant for StructVariantSer<'a, 'b, W> {
2217    type Ok = ();
2218    type Error = Error;
2219
2220    fn serialize_field<T: ?Sized + Serialize>(
2221        &mut self,
2222        key: &'static str,
2223        value: &T,
2224    ) -> Result<()> {
2225        let text = scalar_key_to_string(&key, self.ser.yaml_12)?;
2226        self.ser.write_indent(self.depth)?;
2227        self.ser.out.write_str(&text)?;
2228        // Defer spacing/newline decision to the value serializer similarly to map entries.
2229        self.ser.out.write_str(":")?;
2230        self.ser.pending_space_after_colon = true;
2231        self.ser.at_line_start = false;
2232        // Ensure nested mappings/collections used as this field's value indent relative to this struct variant.
2233        let prev_map_depth = self.ser.current_map_depth.replace(self.depth);
2234        let result = value.serialize(&mut *self.ser);
2235        self.ser.current_map_depth = prev_map_depth;
2236        result
2237    }
2238    fn end(self) -> Result<()> {
2239        Ok(())
2240    }
2241}
2242
2243// ------------------------------------------------------------
2244// Helpers used for extracting ptr/bool inside tuple payloads
2245// ------------------------------------------------------------
2246
2247/// Minimal serializer that captures a numeric `usize` from a serialized field.
2248///
2249/// Used internally to read the raw pointer value encoded as the first field
2250/// of our internal anchor tuple payloads.
2251#[derive(Default)]
2252struct UsizeCapture {
2253    v: Option<usize>,
2254}
2255impl Serializer for &mut UsizeCapture {
2256    type Ok = ();
2257    type Error = Error;
2258
2259    type SerializeSeq = ser::Impossible<(), Error>;
2260    type SerializeTuple = ser::Impossible<(), Error>;
2261    type SerializeTupleStruct = ser::Impossible<(), Error>;
2262    type SerializeTupleVariant = ser::Impossible<(), Error>;
2263    type SerializeMap = ser::Impossible<(), Error>;
2264    type SerializeStruct = ser::Impossible<(), Error>;
2265    type SerializeStructVariant = ser::Impossible<(), Error>;
2266
2267    fn serialize_i8(self, _v: i8) -> Result<()> {
2268        ptr_unsigned_err()
2269    }
2270    fn serialize_i16(self, _v: i16) -> Result<()> {
2271        ptr_unsigned_err()
2272    }
2273    fn serialize_i32(self, _v: i32) -> Result<()> {
2274        ptr_unsigned_err()
2275    }
2276    fn serialize_i64(self, _v: i64) -> Result<()> {
2277        ptr_unsigned_err()
2278    }
2279    fn serialize_u8(self, v: u8) -> Result<()> {
2280        self.v = Some(v as usize);
2281        Ok(())
2282    }
2283    fn serialize_u16(self, v: u16) -> Result<()> {
2284        self.v = Some(v as usize);
2285        Ok(())
2286    }
2287    fn serialize_u32(self, v: u32) -> Result<()> {
2288        self.v = Some(v as usize);
2289        Ok(())
2290    }
2291    fn serialize_u64(self, v: u64) -> Result<()> {
2292        self.v = Some(v as usize);
2293        Ok(())
2294    }
2295    fn serialize_u128(self, v: u128) -> Result<()> {
2296        self.v = Some(v as usize);
2297        Ok(())
2298    }
2299    fn serialize_f32(self, _v: f32) -> Result<()> {
2300        ptr_unsigned_err()
2301    }
2302    fn serialize_f64(self, _v: f64) -> Result<()> {
2303        ptr_unsigned_err()
2304    }
2305    fn serialize_bool(self, _v: bool) -> Result<()> {
2306        ptr_unsigned_err()
2307    }
2308    fn serialize_char(self, _v: char) -> Result<()> {
2309        Err(Error::unexpected("ptr expects number"))
2310    }
2311    fn serialize_str(self, _v: &str) -> Result<()> {
2312        Err(Error::unexpected("ptr expects number"))
2313    }
2314    fn serialize_bytes(self, _v: &[u8]) -> Result<()> {
2315        Err(Error::unexpected("ptr expects number"))
2316    }
2317    fn serialize_none(self) -> Result<()> {
2318        Err(Error::unexpected("ptr cannot be none"))
2319    }
2320    fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<()> {
2321        Err(Error::unexpected("ptr not option"))
2322    }
2323    fn serialize_unit(self) -> Result<()> {
2324        Err(Error::unexpected("ptr cannot be unit"))
2325    }
2326    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
2327        unexpected_e()
2328    }
2329    fn serialize_unit_variant(self, _name: &'static str, _i: u32, _v: &'static str) -> Result<()> {
2330        unexpected_e()
2331    }
2332    fn serialize_newtype_struct<T: ?Sized + Serialize>(
2333        self,
2334        _name: &'static str,
2335        _value: &T,
2336    ) -> Result<()> {
2337        unexpected_e()
2338    }
2339    fn serialize_newtype_variant<T: ?Sized + Serialize>(
2340        self,
2341        _name: &'static str,
2342        _i: u32,
2343        _v: &'static str,
2344        _value: &T,
2345    ) -> Result<()> {
2346        unexpected_e()
2347    }
2348    fn serialize_seq(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2349        unexpected()
2350    }
2351    fn serialize_tuple(self, _len: usize) -> Result<ser::Impossible<(), Error>> {
2352        unexpected()
2353    }
2354    fn serialize_tuple_struct(
2355        self,
2356        _name: &'static str,
2357        _len: usize,
2358    ) -> Result<ser::Impossible<(), Error>> {
2359        unexpected()
2360    }
2361    fn serialize_tuple_variant(
2362        self,
2363        _name: &'static str,
2364        _i: u32,
2365        _v: &'static str,
2366        _len: usize,
2367    ) -> Result<ser::Impossible<(), Error>> {
2368        unexpected()
2369    }
2370    fn serialize_map(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2371        unexpected()
2372    }
2373    fn serialize_struct(
2374        self,
2375        _name: &'static str,
2376        _len: usize,
2377    ) -> Result<ser::Impossible<(), Error>> {
2378        unexpected()
2379    }
2380    fn serialize_struct_variant(
2381        self,
2382        _name: &'static str,
2383        _i: u32,
2384        _v: &'static str,
2385        _len: usize,
2386    ) -> Result<ser::Impossible<(), Error>> {
2387        unexpected()
2388    }
2389    fn collect_str<T: ?Sized + fmt::Display>(self, _value: &T) -> Result<()> {
2390        unexpected_e()
2391    }
2392    fn is_human_readable(&self) -> bool {
2393        true
2394    }
2395}
2396impl UsizeCapture {
2397    fn finish(self) -> Result<usize> {
2398        self.v
2399            .ok_or_else(|| Error::unexpected("missing numeric ptr"))
2400    }
2401}
2402
2403/// Minimal serializer that captures a boolean from a serialized field.
2404///
2405/// Used internally to read the `present` flag from weak-anchor payloads.
2406#[derive(Default)]
2407struct BoolCapture {
2408    v: Option<bool>,
2409}
2410impl Serializer for &mut BoolCapture {
2411    type Ok = ();
2412    type Error = Error;
2413
2414    type SerializeSeq = ser::Impossible<(), Error>;
2415    type SerializeTuple = ser::Impossible<(), Error>;
2416    type SerializeTupleStruct = ser::Impossible<(), Error>;
2417    type SerializeTupleVariant = ser::Impossible<(), Error>;
2418    type SerializeMap = ser::Impossible<(), Error>;
2419    type SerializeStruct = ser::Impossible<(), Error>;
2420    type SerializeStructVariant = ser::Impossible<(), Error>;
2421
2422    fn serialize_bool(self, v: bool) -> Result<()> {
2423        self.v = Some(v);
2424        Ok(())
2425    }
2426    fn serialize_i8(self, _v: i8) -> Result<()> {
2427        bool_expected_err()
2428    }
2429    fn serialize_i16(self, _v: i16) -> Result<()> {
2430        bool_expected_err()
2431    }
2432    fn serialize_i32(self, _v: i32) -> Result<()> {
2433        bool_expected_err()
2434    }
2435    fn serialize_i64(self, _v: i64) -> Result<()> {
2436        bool_expected_err()
2437    }
2438    fn serialize_u8(self, _v: u8) -> Result<()> {
2439        bool_expected_err()
2440    }
2441    fn serialize_u16(self, _v: u16) -> Result<()> {
2442        bool_expected_err()
2443    }
2444    fn serialize_u32(self, _v: u32) -> Result<()> {
2445        bool_expected_err()
2446    }
2447    fn serialize_u64(self, _v: u64) -> Result<()> {
2448        bool_expected_err()
2449    }
2450    fn serialize_f32(self, _v: f32) -> Result<()> {
2451        bool_expected_err()
2452    }
2453    fn serialize_f64(self, _v: f64) -> Result<()> {
2454        bool_expected_err()
2455    }
2456    fn serialize_char(self, _c: char) -> Result<()> {
2457        bool_expected_err()
2458    }
2459    fn serialize_str(self, _v: &str) -> Result<()> {
2460        bool_expected_err()
2461    }
2462    fn serialize_bytes(self, _v: &[u8]) -> Result<()> {
2463        bool_expected_err()
2464    }
2465    fn serialize_none(self) -> Result<()> {
2466        bool_expected_err()
2467    }
2468    fn serialize_some<T: ?Sized + Serialize>(self, _v: &T) -> Result<()> {
2469        bool_expected_err()
2470    }
2471    fn serialize_unit(self) -> Result<()> {
2472        bool_expected_err()
2473    }
2474    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
2475        unexpected_e()
2476    }
2477    fn serialize_unit_variant(self, _name: &'static str, _i: u32, _v: &'static str) -> Result<()> {
2478        unexpected_e()
2479    }
2480    fn serialize_newtype_struct<T: ?Sized + Serialize>(
2481        self,
2482        _name: &'static str,
2483        _value: &T,
2484    ) -> Result<()> {
2485        unexpected_e()
2486    }
2487    fn serialize_newtype_variant<T: ?Sized + Serialize>(
2488        self,
2489        _name: &'static str,
2490        _i: u32,
2491        _v: &'static str,
2492        _value: &T,
2493    ) -> Result<()> {
2494        unexpected_e()
2495    }
2496    fn serialize_seq(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2497        unexpected()
2498    }
2499    fn serialize_tuple(self, _len: usize) -> Result<ser::Impossible<(), Error>> {
2500        unexpected()
2501    }
2502    fn serialize_tuple_struct(
2503        self,
2504        _name: &'static str,
2505        _len: usize,
2506    ) -> Result<ser::Impossible<(), Error>> {
2507        unexpected()
2508    }
2509    fn serialize_tuple_variant(
2510        self,
2511        _name: &'static str,
2512        _i: u32,
2513        _v: &'static str,
2514        _len: usize,
2515    ) -> Result<ser::Impossible<(), Error>> {
2516        unexpected()
2517    }
2518    fn serialize_map(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2519        unexpected()
2520    }
2521    fn serialize_struct(
2522        self,
2523        _name: &'static str,
2524        _len: usize,
2525    ) -> Result<ser::Impossible<(), Error>> {
2526        unexpected()
2527    }
2528    fn serialize_struct_variant(
2529        self,
2530        _name: &'static str,
2531        _i: u32,
2532        _v: &'static str,
2533        _len: usize,
2534    ) -> Result<ser::Impossible<(), Error>> {
2535        unexpected()
2536    }
2537    fn collect_str<T: ?Sized + fmt::Display>(self, _value: &T) -> Result<()> {
2538        unexpected_e()
2539    }
2540    fn is_human_readable(&self) -> bool {
2541        true
2542    }
2543}
2544impl BoolCapture {
2545    fn finish(self) -> Result<bool> {
2546        self.v.ok_or_else(|| Error::unexpected("missing bool"))
2547    }
2548}
2549
2550/// Minimal serializer that captures a string from a serialized field.
2551///
2552/// Used internally to read the comment text for the Commented wrapper.
2553#[derive(Default)]
2554struct StrCapture {
2555    s: Option<String>,
2556}
2557impl Serializer for &mut StrCapture {
2558    type Ok = ();
2559    type Error = Error;
2560
2561    type SerializeSeq = ser::Impossible<(), Error>;
2562    type SerializeTuple = ser::Impossible<(), Error>;
2563    type SerializeTupleStruct = ser::Impossible<(), Error>;
2564    type SerializeTupleVariant = ser::Impossible<(), Error>;
2565    type SerializeMap = ser::Impossible<(), Error>;
2566    type SerializeStruct = ser::Impossible<(), Error>;
2567    type SerializeStructVariant = ser::Impossible<(), Error>;
2568
2569    fn serialize_str(self, v: &str) -> Result<()> {
2570        self.s = Some(v.to_string());
2571        Ok(())
2572    }
2573
2574    fn serialize_bool(self, _v: bool) -> Result<()> {
2575        str_expected_err()
2576    }
2577    fn serialize_i8(self, _v: i8) -> Result<()> {
2578        str_expected_err()
2579    }
2580    fn serialize_i16(self, _v: i16) -> Result<()> {
2581        str_expected_err()
2582    }
2583    fn serialize_i32(self, _v: i32) -> Result<()> {
2584        str_expected_err()
2585    }
2586    fn serialize_i64(self, _v: i64) -> Result<()> {
2587        str_expected_err()
2588    }
2589    fn serialize_i128(self, _v: i128) -> Result<()> {
2590        str_expected_err()
2591    }
2592    fn serialize_u8(self, _v: u8) -> Result<()> {
2593        str_expected_err()
2594    }
2595    fn serialize_u16(self, _v: u16) -> Result<()> {
2596        str_expected_err()
2597    }
2598    fn serialize_u32(self, _v: u32) -> Result<()> {
2599        str_expected_err()
2600    }
2601    fn serialize_u64(self, _v: u64) -> Result<()> {
2602        str_expected_err()
2603    }
2604    fn serialize_u128(self, _v: u128) -> Result<()> {
2605        str_expected_err()
2606    }
2607    fn serialize_f32(self, _v: f32) -> Result<()> {
2608        str_expected_err()
2609    }
2610    fn serialize_f64(self, _v: f64) -> Result<()> {
2611        str_expected_err()
2612    }
2613    fn serialize_char(self, _c: char) -> Result<()> {
2614        str_expected_err()
2615    }
2616    fn serialize_bytes(self, _v: &[u8]) -> Result<()> {
2617        str_expected_err()
2618    }
2619    fn serialize_none(self) -> Result<()> {
2620        str_expected_err()
2621    }
2622    fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<()> {
2623        str_expected_err()
2624    }
2625    fn serialize_unit(self) -> Result<()> {
2626        str_expected_err()
2627    }
2628    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
2629        str_expected_err()
2630    }
2631    fn serialize_unit_variant(self, _name: &'static str, _i: u32, _v: &'static str) -> Result<()> {
2632        str_expected_err()
2633    }
2634    fn serialize_newtype_struct<T: ?Sized + Serialize>(
2635        self,
2636        _name: &'static str,
2637        _value: &T,
2638    ) -> Result<()> {
2639        str_expected_err()
2640    }
2641    fn serialize_newtype_variant<T: ?Sized + Serialize>(
2642        self,
2643        _name: &'static str,
2644        _i: u32,
2645        _v: &'static str,
2646        _value: &T,
2647    ) -> Result<()> {
2648        str_expected_err()
2649    }
2650    fn serialize_seq(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2651        str_expected_err_impossible()
2652    }
2653    fn serialize_tuple(self, _len: usize) -> Result<ser::Impossible<(), Error>> {
2654        str_expected_err_impossible()
2655    }
2656    fn serialize_tuple_struct(
2657        self,
2658        _name: &'static str,
2659        _len: usize,
2660    ) -> Result<ser::Impossible<(), Error>> {
2661        str_expected_err_impossible()
2662    }
2663    fn serialize_tuple_variant(
2664        self,
2665        _name: &'static str,
2666        _i: u32,
2667        _v: &'static str,
2668        _len: usize,
2669    ) -> Result<ser::Impossible<(), Error>> {
2670        str_expected_err_impossible()
2671    }
2672    fn serialize_map(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2673        str_expected_err_impossible()
2674    }
2675    fn serialize_struct(
2676        self,
2677        _name: &'static str,
2678        _len: usize,
2679    ) -> Result<ser::Impossible<(), Error>> {
2680        str_expected_err_impossible()
2681    }
2682    fn serialize_struct_variant(
2683        self,
2684        _name: &'static str,
2685        _i: u32,
2686        _v: &'static str,
2687        _len: usize,
2688    ) -> Result<ser::Impossible<(), Error>> {
2689        str_expected_err_impossible()
2690    }
2691    fn collect_str<T: ?Sized + fmt::Display>(self, _value: &T) -> Result<()> {
2692        str_expected_err()
2693    }
2694    fn is_human_readable(&self) -> bool {
2695        true
2696    }
2697}
2698impl StrCapture {
2699    fn finish(self) -> Result<String> {
2700        self.s.ok_or_else(|| Error::unexpected("missing string"))
2701    }
2702}
2703
2704// ------------------------------------------------------------
2705// Key scalar helper
2706// ------------------------------------------------------------
2707
2708/// Serialize a key using a restricted scalar-only serializer into a `String`.
2709///
2710/// Called by map/struct serializers to ensure YAML keys are scalars.
2711fn scalar_key_to_string<K: Serialize + ?Sized>(key: &K, yaml_12: bool) -> Result<String> {
2712    let mut s = String::new();
2713    {
2714        let mut ks = KeyScalarSink { s: &mut s, yaml_12 };
2715        key.serialize(&mut ks)?;
2716    }
2717    Ok(s)
2718}
2719
2720struct KeyScalarSink<'a> {
2721    s: &'a mut String,
2722    yaml_12: bool,
2723}
2724
2725impl<'a> Serializer for &'a mut KeyScalarSink<'a> {
2726    type Ok = ();
2727    type Error = Error;
2728
2729    type SerializeSeq = ser::Impossible<(), Error>;
2730    type SerializeTuple = ser::Impossible<(), Error>;
2731    type SerializeTupleStruct = ser::Impossible<(), Error>;
2732    type SerializeTupleVariant = ser::Impossible<(), Error>;
2733    type SerializeMap = ser::Impossible<(), Error>;
2734    type SerializeStruct = ser::Impossible<(), Error>;
2735    type SerializeStructVariant = ser::Impossible<(), Error>;
2736
2737    fn serialize_bool(self, v: bool) -> Result<()> {
2738        self.s.push_str(if v { "true" } else { "false" });
2739        Ok(())
2740    }
2741    fn serialize_i64(self, v: i64) -> Result<()> {
2742        let _ = write!(self.s, "{}", v);
2743        Ok(())
2744    }
2745    fn serialize_i32(self, v: i32) -> Result<()> {
2746        self.serialize_i64(v as i64)
2747    }
2748    fn serialize_i16(self, v: i16) -> Result<()> {
2749        self.serialize_i64(v as i64)
2750    }
2751    fn serialize_i8(self, v: i8) -> Result<()> {
2752        self.serialize_i64(v as i64)
2753    }
2754    fn serialize_i128(self, v: i128) -> Result<()> {
2755        let _ = write!(self.s, "{}", v);
2756        Ok(())
2757    }
2758    fn serialize_u64(self, v: u64) -> Result<()> {
2759        let _ = write!(self.s, "{}", v);
2760        Ok(())
2761    }
2762    fn serialize_u32(self, v: u32) -> Result<()> {
2763        self.serialize_u64(v as u64)
2764    }
2765    fn serialize_u16(self, v: u16) -> Result<()> {
2766        self.serialize_u64(v as u64)
2767    }
2768    fn serialize_u8(self, v: u8) -> Result<()> {
2769        self.serialize_u64(v as u64)
2770    }
2771    fn serialize_u128(self, v: u128) -> Result<()> {
2772        let _ = write!(self.s, "{}", v);
2773        Ok(())
2774    }
2775    fn serialize_f32(self, v: f32) -> Result<()> {
2776        zmij_format::push_float_string(self.s, v)
2777    }
2778    fn serialize_f64(self, v: f64) -> Result<()> {
2779        zmij_format::push_float_string(self.s, v)
2780    }
2781
2782    fn serialize_char(self, v: char) -> Result<()> {
2783        let mut buf = [0u8; 4];
2784        self.serialize_str(v.encode_utf8(&mut buf))
2785    }
2786    fn serialize_str(self, v: &str) -> Result<()> {
2787        // Keys are in a more restrictive position than values (':' is structural),
2788        // but they also must avoid ambiguous plain scalars (e.g. YAML 1.1 bool spellings
2789        // like y/n/yes/no) to preserve intended string keys.
2790        // Be conservative here: keys may be emitted in both block and flow mappings,
2791        // and flow mappings treat characters like ','/[]/{} as structural.
2792        if is_plain_safe(v) && is_plain_value_safe(v, self.yaml_12, true) {
2793            self.s.push_str(v);
2794        } else {
2795            self.s.push('"');
2796            for ch in v.chars() {
2797                match ch {
2798                    '\\' => self.s.push_str("\\\\"),
2799                    '"' => self.s.push_str("\\\""),
2800                    '\n' => self.s.push_str("\\n"),
2801                    '\r' => self.s.push_str("\\r"),
2802                    '\t' => self.s.push_str("\\t"),
2803                    c if c.is_control() => {
2804                        use std::fmt::Write as _;
2805                        // Writing into a String cannot fail; ignore the Result to avoid unwrap.
2806                        let _ = write!(self.s, "\\u{:04X}", c as u32);
2807                    }
2808                    c => self.s.push(c),
2809                }
2810            }
2811            self.s.push('"');
2812        }
2813        Ok(())
2814    }
2815    fn serialize_bytes(self, _v: &[u8]) -> Result<()> {
2816        non_scalar_key_e()
2817    }
2818    fn serialize_none(self) -> Result<()> {
2819        self.s.push_str("null");
2820        Ok(())
2821    }
2822    fn serialize_some<T: ?Sized + Serialize>(self, v: &T) -> Result<()> {
2823        v.serialize(self)
2824    }
2825    fn serialize_unit(self) -> Result<()> {
2826        self.s.push_str("null");
2827        Ok(())
2828    }
2829    fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
2830        self.serialize_unit()
2831    }
2832    fn serialize_unit_variant(
2833        self,
2834        _name: &'static str,
2835        _idx: u32,
2836        variant: &'static str,
2837    ) -> Result<()> {
2838        self.serialize_str(variant)
2839    }
2840    fn serialize_newtype_struct<T: ?Sized + Serialize>(
2841        self,
2842        _name: &'static str,
2843        value: &T,
2844    ) -> Result<()> {
2845        // Treat newtype structs transparently. This allows common key wrappers like
2846        // `struct Key(String);` / `struct Id(u64);` to be emitted as scalar keys.
2847        value.serialize(self)
2848    }
2849    fn serialize_newtype_variant<T: ?Sized + Serialize>(
2850        self,
2851        _: &'static str,
2852        _: u32,
2853        _: &'static str,
2854        _: &T,
2855    ) -> Result<()> {
2856        non_scalar_key_e()
2857    }
2858    fn serialize_seq(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2859        non_scalar_key()
2860    }
2861    fn serialize_tuple(self, _len: usize) -> Result<ser::Impossible<(), Error>> {
2862        non_scalar_key()
2863    }
2864    fn serialize_tuple_struct(
2865        self,
2866        _: &'static str,
2867        _: usize,
2868    ) -> Result<ser::Impossible<(), Error>> {
2869        non_scalar_key()
2870    }
2871    fn serialize_tuple_variant(
2872        self,
2873        _: &'static str,
2874        _: u32,
2875        _: &'static str,
2876        _: usize,
2877    ) -> Result<ser::Impossible<(), Error>> {
2878        non_scalar_key()
2879    }
2880    fn serialize_map(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>> {
2881        non_scalar_key()
2882    }
2883    fn serialize_struct(
2884        self,
2885        _name: &'static str,
2886        _len: usize,
2887    ) -> Result<ser::Impossible<(), Error>> {
2888        non_scalar_key()
2889    }
2890    fn serialize_struct_variant(
2891        self,
2892        _: &'static str,
2893        _: u32,
2894        _: &'static str,
2895        _: usize,
2896    ) -> Result<ser::Impossible<(), Error>> {
2897        non_scalar_key()
2898    }
2899    fn collect_str<T: ?Sized + fmt::Display>(self, v: &T) -> Result<()> {
2900        self.serialize_str(&v.to_string())
2901    }
2902    fn is_human_readable(&self) -> bool {
2903        true
2904    }
2905}
2906
2907#[cold]
2908fn ptr_unsigned_err() -> Result<()> {
2909    Err(Error::unexpected("ptr expects unsigned integer"))
2910}
2911
2912#[cold]
2913fn bool_expected_err() -> Result<()> {
2914    Err(Error::unexpected("bool expected"))
2915}
2916
2917#[cold]
2918fn str_expected_err() -> Result<()> {
2919    Err(Error::unexpected("str expected"))
2920}
2921
2922#[cold]
2923fn str_expected_err_impossible() -> Result<ser::Impossible<(), Error>> {
2924    Err(Error::unexpected("str expected"))
2925}
2926
2927#[cold]
2928fn unexpected() -> Result<ser::Impossible<(), Error>> {
2929    Err(Error::unexpected("unexpected"))
2930}
2931
2932#[cold]
2933fn unexpected_e() -> Result<()> {
2934    Err(Error::unexpected("unexpected"))
2935}
2936
2937#[cold]
2938fn non_scalar_key() -> Result<ser::Impossible<(), Error>> {
2939    Err(Error::unexpected("non-scalar key"))
2940}
2941
2942#[cold]
2943fn non_scalar_key_e() -> Result<()> {
2944    Err(Error::unexpected("non-scalar key"))
2945}
2946
2947#[cfg(test)]
2948mod tests_internal {
2949    use super::*;
2950
2951    #[test]
2952    fn test_push_float_string_coverage() {
2953        let mut s = String::new();
2954
2955        // 1.0 -> no decimal, no exp -> line 48-50
2956        zmij_format::push_float_string(&mut s, 1.0f64).unwrap();
2957        assert!(s.contains(".0"));
2958
2959        // 1e-10 -> exponent without plus sign -> line 35-36? wait, no, "1e-10" has minus sign.
2960        // We need exponent missing decimal, and exponent missing sign (+).
2961        s.clear();
2962        zmij_format::push_float_string(&mut s, 1e20f64).unwrap();
2963        // and f32 variations
2964        s.clear();
2965        zmij_format::push_float_string(&mut s, 1e30f32).unwrap();
2966
2967        s.clear();
2968        zmij_format::push_float_string(&mut s, f32::NAN).unwrap();
2969        s.clear();
2970        zmij_format::push_float_string(&mut s, f32::INFINITY).unwrap();
2971        s.clear();
2972        zmij_format::push_float_string(&mut s, f32::NEG_INFINITY).unwrap();
2973    }
2974
2975    #[test]
2976    fn test_write_float_string_coverage() {
2977        let mut s = String::new();
2978        zmij_format::write_float_string(&mut s, 1e20f64).unwrap();
2979        zmij_format::write_float_string(&mut s, 1e30f32).unwrap();
2980    }
2981}
2982
2983#[test]
2984fn test_captures_coverage() {
2985    use serde::Serializer;
2986    let mut u = UsizeCapture::default();
2987    let _ = (&mut u).serialize_unit_struct("A");
2988    let _ = (&mut u).serialize_unit_variant("A", 0, "V");
2989    let _ = (&mut u).serialize_newtype_struct("A", &1);
2990    let _ = (&mut u).serialize_newtype_variant("A", 0, "V", &1);
2991    let _ = (&mut u).collect_str("A");
2992    let serializer = &mut u;
2993    assert!(Serializer::is_human_readable(&serializer));
2994
2995    let mut b = BoolCapture::default();
2996    let _ = (&mut b).serialize_unit_struct("A");
2997    let _ = (&mut b).serialize_unit_variant("A", 0, "V");
2998    let _ = (&mut b).serialize_newtype_struct("A", &1);
2999    let _ = (&mut b).serialize_newtype_variant("A", 0, "V", &1);
3000    let _ = (&mut b).collect_str("A");
3001    let serializer = &mut b;
3002    assert!(Serializer::is_human_readable(&serializer));
3003
3004    let mut st = StrCapture::default();
3005    let _ = (&mut st).serialize_unit_struct("A");
3006    let _ = (&mut st).serialize_unit_variant("A", 0, "V");
3007    let _ = (&mut st).serialize_newtype_struct("A", &1);
3008    let _ = (&mut st).serialize_newtype_variant("A", 0, "V", &1);
3009    let _ = (&mut st).collect_str("A");
3010    let serializer = &mut st;
3011    assert!(Serializer::is_human_readable(&serializer));
3012}