Skip to main content

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