Skip to main content

serde_saphyr/ser/
options.rs

1//! Serializer options for YAML emission.
2//!
3//! Controls indentation and optional anchor name generation for the serializer.
4//!
5//! Example: use 4-space indentation and a custom anchor naming scheme.
6//!
7//! ```rust
8//! use serde::Serialize;
9//!
10//! #[derive(Serialize)]
11//! struct Item { a: i32, b: bool }
12//!
13//! let mut buf = String::new();
14//! let opts = serde_saphyr::ser_options! {
15//!     indent_step: 4,
16//!     anchor_generator: Some(|id| format!("id{}/", id)),
17//! };
18//! serde_saphyr::to_fmt_writer_with_options(&mut buf, &Item { a: 1, b: true }, opts).unwrap();
19//! assert!(buf.contains("a: 1"));
20//! ```
21
22use crate::ser_error::Error;
23
24/// Placement style for comments emitted by [`crate::Commented`].
25///
26/// Requires the `comments` feature, which is enabled by default.
27#[non_exhaustive]
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29#[cfg(feature = "comments")]
30pub enum CommentPosition {
31    /// Emit the comment inline on the right side from the item it describes (default)
32    Inline,
33    /// Emit the comment above the item it describes
34    Above,
35}
36
37/// Serializer options for YAML emission.
38///
39/// This struct controls various aspects of YAML serialization, such as indentation,
40/// anchor generation, and scalar styles.
41///
42/// Construct `SerializerOptions` using the [`ser_options!`](crate::ser_options!)
43/// macro to ensure compatibility with future updates.
44///
45/// ```rust
46/// use serde::Serialize;
47/// use serde_saphyr::{ser_options, to_string_with_options};
48///
49/// #[derive(Serialize)]
50/// struct Config {
51///     name: String,
52///     values: Vec<i32>,
53/// }
54///
55/// let config = Config {
56///     name: "test".to_string(),
57///     values: vec![1, 2, 3],
58/// };
59///
60/// // Use 4-space indentation and quote all strings
61/// let options = ser_options! {
62///     indent_step: 4,
63///     quote_all: true,
64/// };
65///
66/// let yaml = to_string_with_options(&config, options).unwrap();
67/// ```
68#[non_exhaustive]
69#[derive(Clone, Debug)]
70pub struct SerializerOptions {
71    /// If true (the default), empty maps are emitted as `{}` and empty lists as `[]`.
72    ///
73    /// Deprecated: omit this setting to retain the default. Setting it to `false` emits
74    /// blank values for empty block collections without tags or anchors. These are YAML
75    /// null values, so deserializing them back into collection types returns an error.
76    /// The setting remains supported for compatibility.
77    #[deprecated(
78        since = "2.1.0",
79        note = "leave this at its default true; false emits null for empty block collections without tags or anchors, preventing round-trips"
80    )]
81    pub empty_as_braces: bool,
82    /// Number of spaces to indent per nesting level when emitting block-style collections (2 by default).
83    /// Values must be in `1..=64`.
84    pub indent_step: usize,
85    /// When enabled, emit list items with a more compact indentation style. On by default.
86    /// ```yaml
87    ///       containers:
88    ///       - env:
89    ///         - name: METHOD
90    ///           value: WATCH
91    /// ```
92    ///
93    /// Compared to a more expanded indentation style:
94    ///
95    /// ```yaml
96    ///       containers:
97    ///         - env:
98    ///             - name: METHOD
99    ///               value: WATCH
100    /// ```
101    pub compact_list_indent: bool,
102    /// Optional custom anchor-name generator.
103    ///
104    /// Receives a monotonically increasing `usize` id (starting at 1) and returns the
105    /// anchor name to emit. If `None`, the built-in generator yields names like `a1`, `a2`, ...
106    /// Generated names must be 1 to 256 bytes and cannot contain whitespace, control characters,
107    /// or the flow punctuation `[`, `]`, `{`, `}`, or `,`; unsupported names produce a
108    /// serialization error. The generator remains responsible for returning unique names.
109    pub anchor_generator: Option<fn(usize) -> String>,
110    /// Threshold for block-string wrappers ([`crate::LitStr`]/[`crate::FoldStr`] and owned variants
111    /// [`crate::LitString`]/[`crate::FoldString`]).
112    ///
113    /// If the string contains a newline, block style is always used. Otherwise, when the
114    /// string is single-line and its length is strictly less than this threshold, the
115    /// serializer emits a normal YAML scalar (no block style). Longer strings use block
116    /// styles `|` or `>` depending on the wrapper. See the type docs for
117    /// [`crate::LitStr`], [`crate::FoldStr`], [`crate::LitString`] and [`crate::FoldString`] for
118    /// examples.
119    pub min_fold_chars: usize,
120    /// Maximum width (in characters) for lines in folded block scalars (`>`).
121    ///
122    /// Lines are wrapped **only** at whitespace so that each emitted line is at most
123    /// this many characters long (excluding indentation). If no whitespace is present
124    /// within the limit (e.g., a single long token), the line is emitted unwrapped
125    /// to preserve round-trip correctness: YAML folded scalars typically fold inserted
126    /// newlines back as spaces when parsing. Default: 80.
127    pub folded_wrap_chars: usize,
128    /// When enabled, serialize simple enums that become a single scalar (unit variants)
129    /// using YAML tags, e.g. `!!Enum Variant` instead of a plain scalar `Variant`.
130    /// Deserializer does not need this setting as both cases will be understood. Off by default.
131    pub tagged_enums: bool,
132
133    /// When enabled, strings containing more than `folded_wrap_chars` (80 by default) are written
134    /// in folded block scalar style (`>`), and strings containing newlines are written in
135    /// literal block scalar style (`|`), selecting chomping based on the number of trailing empty lines.
136    /// On by default.
137    pub prefer_block_scalars: bool,
138
139    /// When enabled, quote all string scalars. Uses single quotes by default,
140    /// but switches to double quotes when the string contains escape sequences
141    /// (control characters like `\n`, `\t`, `\r`, backslash) or single quotes.
142    /// Disables block scalar styles (`|` and `>`) for quoted strings when active.
143    /// Off by default.
144    pub quote_all: bool,
145
146    /// Controls where [`crate::Commented`] comments are emitted in block style.
147    ///
148    /// [`CommentPosition::Inline`] preserves the existing `value # comment` behavior for
149    /// scalars and aliases. [`CommentPosition::Above`] emits the comment on its own line
150    /// immediately before the wrapped value. Comments remain suppressed in flow-style
151    /// collections in both modes.
152    /// Available with the `comments` feature, which is enabled by default.
153    #[cfg(feature = "comments")]
154    pub comment_position: CommentPosition,
155
156    /// When enabled, emit `%YAML 1.2` and the required document start marker
157    /// at the beginning of the document, and use YAML 1.2 rules for certain
158    /// compatibility heuristics. Set [`no_lang_directive`](Self::no_lang_directive)
159    /// to suppress the directive and its document start marker while keeping
160    /// these heuristics.
161    ///
162    /// With the default `false`, strings matching YAML 1.1 numeric, boolean,
163    /// null, timestamp, and reserved-token spellings are quoted to preserve their
164    /// type across readers. With `true`, YAML 1.1-specific quoting checks are
165    /// disabled, including boolean spellings like `yes`/`no`/`on`/`off`/`y`/`n`,
166    /// sexagesimal numbers, and timestamps. YAML syntax safety checks still apply.
167    /// Default: false.
168    pub yaml_12: bool,
169
170    /// Suppress the `%YAML 1.2` directive and its leading `---` document start
171    /// marker when [`yaml_12`](Self::yaml_12) is enabled, without changing string
172    /// quoting heuristics. Document separators between multiple documents are
173    /// still emitted. Has no effect when `yaml_12` is false. Default: false.
174    pub no_lang_directive: bool,
175}
176
177// Below this length, block-string wrappers serialize as regular scalars
178// instead of YAML block styles. This keeps short values compact.
179pub(crate) const MIN_FOLD_CHARS: usize = 32;
180/// Maximum width (in characters) for lines inside folded block scalars.
181/// Lines will be wrapped at whitespace so that each emitted line is at most
182/// this many characters long (excluding indentation). If no whitespace is
183/// available within the limit, the line is not wrapped.
184pub(crate) const FOLDED_WRAP_CHARS: usize = 80;
185
186impl SerializerOptions {
187    // Keep this value in sync with the literal compile-time check in `ser_options!`.
188    pub(crate) const MAX_INDENT_STEP: usize = 64;
189
190    pub(crate) fn consistent(&self) -> Result<(), Error> {
191        if self.indent_step == 0 || self.indent_step > Self::MAX_INDENT_STEP {
192            return Err(Error::InvalidOptions(format!(
193                "indent_step must be in 1..={}",
194                Self::MAX_INDENT_STEP
195            )));
196        }
197        Ok(())
198    }
199}
200
201impl Default for SerializerOptions {
202    #[allow(deprecated)] // Retain the default that emits explicit empty collections.
203    fn default() -> Self {
204        // Defaults mirror internal constants used by the serializer.
205        Self {
206            indent_step: 2,
207            compact_list_indent: true,
208            anchor_generator: None,
209            min_fold_chars: MIN_FOLD_CHARS,
210            folded_wrap_chars: FOLDED_WRAP_CHARS,
211            tagged_enums: false,
212            empty_as_braces: true,
213            prefer_block_scalars: true,
214            quote_all: false,
215            #[cfg(feature = "comments")]
216            comment_position: CommentPosition::Inline,
217            yaml_12: false,
218            no_lang_directive: false,
219        }
220    }
221}