1mod compound;
2mod helpers;
3
4#[doc(hidden)]
5pub use self::compound::{MapSer, SeqSer, StructVariantSer, TupleSer};
6
7use self::helpers::{StrCapture, scalar_key_to_string};
8use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
9use nohash_hasher::BuildNoHashHasher;
10use serde_core::ser::Error as _;
11use serde_core::ser::{Serialize, Serializer};
12use std::collections::HashMap;
13use std::fmt::Write;
14
15use crate::long_strings::{NAME_FOLD_STR, NAME_LIT_STR};
16use crate::tag::{YAML_TAG_NAMESPACE, simple_enum_variant_name, yaml_core_type_tag_name};
17
18#[cfg(feature = "comments")]
19use super::NAME_TUPLE_COMMENTED;
20#[cfg(feature = "comments")]
21use super::options::CommentPosition;
22use super::options::SerializerOptions;
23use super::quoting::{
24 escape_double_quoted, is_auto_block_scalar_readable, is_block_scalar_content_safe,
25 is_controll_which_needs_escaping, is_plain_value_safe,
26};
27use super::{
28 Error, NAME_DOUBLE_QUOTED, NAME_FLOW_MAP, NAME_FLOW_SEQ, NAME_NULLABLE_TILDE,
29 NAME_SINGLE_QUOTED, NAME_SPACE_AFTER, NAME_TUPLE_ANCHOR, NAME_TUPLE_TAGGED, NAME_TUPLE_WEAK,
30 Result, checked_depth_add, checked_indentation, wrapping, zmij_format,
31};
32
33#[derive(Clone, Copy, PartialEq, Eq)]
38enum PendingFlow {
39 AnySeq,
40 AnyMap,
41}
42#[derive(Clone, Copy, PartialEq, Eq)]
43enum StrStyle {
44 Literal, Folded, }
47
48#[derive(Clone, Copy, PartialEq, Eq)]
54enum PendingStrStyle {
55 Explicit(StrStyle),
56 Automatic(StrStyle),
57}
58
59impl PendingStrStyle {
60 fn into_parts(self) -> (StrStyle, bool) {
62 match self {
63 Self::Explicit(style) => (style, false),
64 Self::Automatic(style) => (style, true),
65 }
66 }
67}
68
69type AnchorId = u32;
71
72const MAX_ANCHOR_NAME_BYTES: usize = 256;
73
74#[derive(Clone)]
80struct PendingTag {
81 resolved: String,
82 emitted: String,
83 variant_selector_claimed: bool,
85}
86
87#[inline]
88const fn is_global_tag_uri_byte(byte: u8) -> bool {
89 byte.is_ascii_alphanumeric()
90 || matches!(
91 byte,
92 b'-' | b'#'
93 | b';'
94 | b'/'
95 | b'?'
96 | b':'
97 | b'@'
98 | b'&'
99 | b'='
100 | b'+'
101 | b'$'
102 | b','
103 | b'_'
104 | b'.'
105 | b'!'
106 | b'~'
107 | b'*'
108 | b'\''
109 | b'('
110 | b')'
111 )
112}
113
114#[inline]
115const fn is_local_tag_suffix_byte(byte: u8) -> bool {
116 is_global_tag_uri_byte(byte) && !matches!(byte, b'!' | b',')
117}
118
119fn push_percent_encoded_byte(out: &mut String, byte: u8, safe: bool) {
120 const HEX: &[u8; 16] = b"0123456789ABCDEF";
121 if byte != b'%' && safe {
124 out.push(char::from(byte));
125 } else {
126 out.push('%');
127 out.push(char::from(HEX[usize::from(byte >> 4)]));
128 out.push(char::from(HEX[usize::from(byte & 0x0f)]));
129 }
130}
131
132fn push_percent_encoded(out: &mut String, value: &str, safe: fn(u8) -> bool) {
133 for byte in value.bytes() {
134 push_percent_encoded_byte(out, byte, safe(byte));
135 }
136}
137
138fn uri_ip_literal_host_is_valid(host_and_port: &str) -> bool {
140 let Some(literal_and_port) = host_and_port.strip_prefix('[') else {
141 return false;
142 };
143 let Some(close) = literal_and_port.find(']') else {
144 return false;
145 };
146 if !uri_ip_literal_is_valid(&literal_and_port[..close]) {
147 return false;
148 }
149 let port = &literal_and_port[close + 1..];
150 port.is_empty()
151 || port
152 .strip_prefix(':')
153 .is_some_and(|value| value.bytes().all(|byte| byte.is_ascii_digit()))
154}
155
156fn push_uri_authority_percent_encoded(out: &mut String, authority: &str) {
157 let host_and_port = if let Some((userinfo, host_and_port)) = authority.rsplit_once('@') {
158 push_percent_encoded(out, userinfo, is_uri_userinfo_char);
159 out.push('@');
160 host_and_port
161 } else {
162 authority
163 };
164
165 if uri_ip_literal_host_is_valid(host_and_port) {
166 out.push_str(host_and_port);
167 return;
168 }
169
170 let (host, port) = match host_and_port.rsplit_once(':') {
171 Some((host, port)) if port.bytes().all(|byte| byte.is_ascii_digit()) => (host, Some(port)),
172 _ => (host_and_port, None),
173 };
174 push_percent_encoded(out, host, is_uri_reg_name_char);
175 if let Some(port) = port {
176 out.push(':');
177 out.push_str(port);
178 }
179}
180
181fn push_uri_hier_part_percent_encoded(out: &mut String, hier_part: &str) {
182 if let Some(authority_and_path) = hier_part.strip_prefix("//") {
183 out.push_str("//");
184 let path_start = authority_and_path.find('/');
185 let (authority, path) = match path_start {
186 Some(idx) => authority_and_path.split_at(idx),
187 None => (authority_and_path, ""),
188 };
189 push_uri_authority_percent_encoded(out, authority);
190 push_percent_encoded(out, path, is_uri_path_char);
191 } else {
192 push_percent_encoded(out, hier_part, is_uri_path_char);
193 }
194}
195
196fn push_global_tag_uri_percent_encoded(out: &mut String, uri: &str) {
197 let Some((scheme, remainder)) = uri.split_once(':') else {
198 push_percent_encoded(out, uri, is_global_tag_uri_byte);
199 return;
200 };
201 push_percent_encoded(out, scheme, is_uri_scheme_char);
202 out.push(':');
203
204 let (without_fragment, fragment) = match remainder.split_once('#') {
205 Some((head, tail)) => (head, Some(tail)),
206 None => (remainder, None),
207 };
208 let (hier_part, query) = match without_fragment.split_once('?') {
209 Some((head, tail)) => (head, Some(tail)),
210 None => (without_fragment, None),
211 };
212
213 push_uri_hier_part_percent_encoded(out, hier_part);
214 if let Some(query) = query {
215 out.push('?');
216 push_percent_encoded(out, query, is_uri_query_or_fragment_char);
217 }
218 if let Some(fragment) = fragment {
219 out.push('#');
220 push_percent_encoded(out, fragment, is_uri_query_or_fragment_char);
221 }
222}
223
224#[inline]
225const fn is_uri_scheme_char(byte: u8) -> bool {
226 byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')
227}
228
229fn resolved_tag_token(resolved: &str) -> String {
231 if let Some(suffix) = resolved.strip_prefix('!') {
232 let mut token = String::with_capacity(resolved.len());
233 token.push('!');
234 push_percent_encoded(&mut token, suffix, is_local_tag_suffix_byte);
235 token
236 } else {
237 let mut token = String::with_capacity(resolved.len() + 3);
238 token.push_str("!<");
239 push_global_tag_uri_percent_encoded(&mut token, resolved);
240 token.push('>');
241 token
242 }
243}
244
245fn core_tag_token(suffix: &str) -> String {
246 let mut token = String::with_capacity(suffix.len() + 2);
247 token.push_str("!!");
248 push_percent_encoded(&mut token, suffix, is_local_tag_suffix_byte);
249 token
250}
251
252#[inline]
253const fn is_uri_unreserved(byte: u8) -> bool {
254 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~')
255}
256
257#[inline]
258const fn is_uri_sub_delim(byte: u8) -> bool {
259 matches!(
260 byte,
261 b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
262 )
263}
264
265#[inline]
266const fn is_uri_pchar(byte: u8) -> bool {
267 is_uri_unreserved(byte) || is_uri_sub_delim(byte) || matches!(byte, b':' | b'@')
268}
269
270fn uri_component_is_valid(value: &str, allowed: fn(u8) -> bool) -> bool {
271 let bytes = value.as_bytes();
272 let mut idx = 0;
273 while idx < bytes.len() {
274 if bytes[idx] == b'%' {
275 if idx + 2 >= bytes.len()
276 || !bytes[idx + 1].is_ascii_hexdigit()
277 || !bytes[idx + 2].is_ascii_hexdigit()
278 {
279 return false;
280 }
281 idx += 3;
282 } else if allowed(bytes[idx]) {
283 idx += 1;
284 } else {
285 return false;
286 }
287 }
288 true
289}
290
291#[inline]
292const fn is_uri_userinfo_char(byte: u8) -> bool {
293 is_uri_unreserved(byte) || is_uri_sub_delim(byte) || byte == b':'
294}
295
296#[inline]
297const fn is_uri_reg_name_char(byte: u8) -> bool {
298 is_uri_unreserved(byte) || is_uri_sub_delim(byte)
299}
300
301#[inline]
302const fn is_uri_path_char(byte: u8) -> bool {
303 is_uri_pchar(byte) || byte == b'/'
304}
305
306#[inline]
307const fn is_uri_query_or_fragment_char(byte: u8) -> bool {
308 is_uri_pchar(byte) || matches!(byte, b'/' | b'?')
309}
310
311#[inline]
312const fn is_ipv_future_char(byte: u8) -> bool {
313 is_uri_unreserved(byte) || is_uri_sub_delim(byte) || byte == b':'
314}
315
316fn uri_ip_literal_is_valid(value: &str) -> bool {
317 if value.parse::<std::net::Ipv6Addr>().is_ok() {
318 return true;
319 }
320
321 let Some(version_and_address) = value.strip_prefix('v').or_else(|| value.strip_prefix('V'))
322 else {
323 return false;
324 };
325 let Some((version, address)) = version_and_address.split_once('.') else {
326 return false;
327 };
328 !version.is_empty()
329 && version.bytes().all(|byte| byte.is_ascii_hexdigit())
330 && !address.is_empty()
331 && address.bytes().all(is_ipv_future_char)
332}
333
334fn uri_authority_is_valid(authority: &str) -> bool {
335 let mut userinfo_and_host = authority.split('@');
336 let first = userinfo_and_host.next().unwrap_or_default();
337 let (userinfo, host_and_port) = match userinfo_and_host.next() {
338 Some(host) if userinfo_and_host.next().is_none() => (Some(first), host),
339 Some(_) => return false,
340 None => (None, first),
341 };
342 if userinfo.is_some_and(|value| !uri_component_is_valid(value, is_uri_userinfo_char)) {
343 return false;
344 }
345
346 if let Some(literal_and_port) = host_and_port.strip_prefix('[') {
347 let Some((literal, port)) = literal_and_port.split_once(']') else {
348 return false;
349 };
350 if !uri_ip_literal_is_valid(literal) {
351 return false;
352 }
353 return port.is_empty()
354 || port
355 .strip_prefix(':')
356 .is_some_and(|value| value.bytes().all(|byte| byte.is_ascii_digit()));
357 }
358
359 let (host, port) = match host_and_port.rsplit_once(':') {
360 Some((host, port)) if !host.contains(':') => (host, Some(port)),
361 Some(_) => return false,
362 None => (host_and_port, None),
363 };
364 uri_component_is_valid(host, is_uri_reg_name_char)
365 && port.is_none_or(|value| value.bytes().all(|byte| byte.is_ascii_digit()))
366}
367
368fn absolute_uri_is_valid(uri: &str) -> bool {
373 let Some((scheme, remainder)) = uri.split_once(':') else {
374 return false;
375 };
376 if !scheme
377 .as_bytes()
378 .first()
379 .is_some_and(u8::is_ascii_alphabetic)
380 || !scheme
381 .bytes()
382 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.'))
383 {
384 return false;
385 }
386
387 let (without_fragment, fragment) = match remainder.split_once('#') {
388 Some((head, tail)) => (head, Some(tail)),
389 None => (remainder, None),
390 };
391 if fragment.is_some_and(|value| !uri_component_is_valid(value, is_uri_query_or_fragment_char)) {
392 return false;
393 }
394
395 let (hier_part, query) = match without_fragment.split_once('?') {
396 Some((head, tail)) => (head, Some(tail)),
397 None => (without_fragment, None),
398 };
399 if query.is_some_and(|value| !uri_component_is_valid(value, is_uri_query_or_fragment_char)) {
400 return false;
401 }
402
403 if let Some(authority_and_path) = hier_part.strip_prefix("//") {
404 let path_start = authority_and_path.find('/');
405 let (authority, path) = match path_start {
406 Some(idx) => authority_and_path.split_at(idx),
407 None => (authority_and_path, ""),
408 };
409 uri_authority_is_valid(authority) && uri_component_is_valid(path, is_uri_path_char)
410 } else {
411 uri_component_is_valid(hier_part, is_uri_path_char)
412 }
413}
414
415fn is_supported_anchor_name(name: &str) -> bool {
416 !name.is_empty()
417 && name.len() <= MAX_ANCHOR_NAME_BYTES
418 && name.chars().all(|ch| {
419 !ch.is_whitespace() && !ch.is_control() && !matches!(ch, '[' | ']' | '{' | '}' | ',')
420 })
421}
422
423#[allow(clippy::struct_excessive_bools)]
431struct SerializerSettings {
432 indent_step: usize,
434 min_fold_chars: usize,
436 folded_wrap_col: usize,
438 #[cfg(feature = "comments")]
440 comment_position: CommentPosition,
441 tagged_enums: bool,
443 empty_as_braces: bool,
445 compact_list_indent: bool,
447 prefer_block_scalars: bool,
449 quote_all: bool,
451 yaml_12: bool,
453 no_lang_directive: bool,
455}
456
457impl From<&SerializerOptions> for SerializerSettings {
458 fn from(options: &SerializerOptions) -> Self {
459 Self {
460 indent_step: options.indent_step,
461 min_fold_chars: options.min_fold_chars,
462 folded_wrap_col: options.folded_wrap_chars,
463 #[cfg(feature = "comments")]
464 comment_position: options.comment_position,
465 tagged_enums: options.tagged_enums,
466 empty_as_braces: options.empty_as_braces,
467 compact_list_indent: options.compact_list_indent,
468 prefer_block_scalars: options.prefer_block_scalars,
469 quote_all: options.quote_all,
470 yaml_12: options.yaml_12,
471 no_lang_directive: options.no_lang_directive,
472 }
473 }
474}
475
476#[derive(Default)]
483struct PendingLayout {
484 pending_inline_map: bool,
486 pending_space_after_colon: bool,
488 inline_map_after_dash: bool,
490}
491
492struct SerializerState {
499 depth: usize,
501 at_line_start: bool,
503 pending_flow: Option<PendingFlow>,
505 in_flow: usize,
507 pending_str_style: Option<PendingStrStyle>,
509 pending_tag: Option<PendingTag>,
511 #[cfg(feature = "comments")]
513 pending_inline_comment: Option<String>,
514 pending_layout: PendingLayout,
516 last_value_was_block: bool,
518 after_dash_depth: Option<usize>,
520 current_map_depth: Option<usize>,
522 current_map_key_column: Option<usize>,
526 doc_started: bool,
528}
529
530impl Default for SerializerState {
531 fn default() -> Self {
532 Self {
533 depth: 0,
534 at_line_start: true,
535 pending_flow: None,
536 in_flow: 0,
537 pending_str_style: None,
538 pending_tag: None,
539 #[cfg(feature = "comments")]
540 pending_inline_comment: None,
541 pending_layout: PendingLayout::default(),
542 last_value_was_block: false,
543 after_dash_depth: None,
544 current_map_depth: None,
545 current_map_key_column: None,
546 doc_started: false,
547 }
548 }
549}
550
551struct AnchorState {
558 by_ptr: HashMap<usize, AnchorId, BuildNoHashHasher<usize>>,
560 next_id: AnchorId,
562 pending_id: Option<AnchorId>,
564 generator: Option<fn(usize) -> String>,
566 custom_names: Option<Vec<String>>,
568 resolved_tags: Vec<Option<String>>,
570}
571
572impl AnchorState {
573 fn new(generator: Option<fn(usize) -> String>) -> Self {
574 Self {
575 by_ptr: HashMap::with_hasher(BuildNoHashHasher::default()),
576 next_id: 1,
577 pending_id: None,
578 generator,
579 custom_names: None,
580 resolved_tags: Vec::new(),
581 }
582 }
583
584 fn record_resolved_tag(&mut self, id: AnchorId, tag: Option<&str>) {
585 let idx = id as usize - 1;
586 if self.resolved_tags.len() <= idx {
587 self.resolved_tags.resize_with(idx + 1, || None);
588 }
589 self.resolved_tags[idx] = tag.map(ToOwned::to_owned);
590 }
591
592 fn resolved_tag(&self, id: AnchorId) -> Option<&str> {
593 self.resolved_tags
594 .get(id as usize - 1)
595 .and_then(Option::as_deref)
596 }
597}
598
599pub struct YamlSerializer<'a, W: Write> {
625 out: &'a mut W,
627 settings: SerializerSettings,
629 state: SerializerState,
631 anchors: AnchorState,
633}
634
635impl<'a, W: Write> YamlSerializer<'a, W> {
636 #[must_use]
639 pub fn new(out: &'a mut W) -> Self {
640 Self::from_options_unchecked(out, &SerializerOptions::default())
641 }
642
643 fn from_options_unchecked(out: &'a mut W, options: &SerializerOptions) -> Self {
644 Self {
645 out,
646 settings: SerializerSettings::from(options),
647 state: SerializerState::default(),
648 anchors: AnchorState::new(options.anchor_generator),
649 }
650 }
651 pub fn with_indent(out: &'a mut W, indent_step: usize) -> Result<Self> {
655 let options = SerializerOptions {
656 indent_step,
657 ..SerializerOptions::default()
658 };
659 Self::with_options(out, options)
660 }
661 pub fn with_options(out: &'a mut W, options: SerializerOptions) -> Result<Self> {
664 options.consistent()?;
665 Ok(Self::from_options_unchecked(out, &options))
666 }
667
668 #[inline]
674 fn needs_double_quotes(s: &str) -> bool {
675 s.chars().any(|c| {
676 c == '\'' || c == '\\' || is_controll_which_needs_escaping(c) })
680 }
681
682 fn write_single_quoted(&mut self, s: &str) -> Result<()> {
684 self.out.write_char('\'')?;
685 for ch in s.chars() {
686 if ch == '\'' {
687 self.out.write_str("''")?; } else {
689 self.out.write_char(ch)?;
690 }
691 }
692 self.out.write_char('\'')?;
693 Ok(())
694 }
695
696 #[cfg(feature = "comments")]
702 #[inline]
703 fn write_pending_inline_comment(&mut self) -> Result<()> {
704 if self.state.in_flow == 0
705 && let Some(c) = self.state.pending_inline_comment.take()
706 {
707 self.out.write_str(" # ")?;
708 self.out.write_str(&c)?;
709 }
710 Ok(())
711 }
712
713 #[inline]
716 fn write_end_of_scalar(&mut self) -> Result<()> {
717 if self.state.in_flow == 0 {
718 #[cfg(feature = "comments")]
719 self.write_pending_inline_comment()?;
720 self.newline()?;
721 }
722 Ok(())
723 }
724
725 #[cfg(feature = "comments")]
729 #[inline]
730 fn sanitize_comment_text(comment: &str) -> String {
731 comment
732 .chars()
733 .map(|ch| match ch {
734 '\t' => ch,
735 '\n' | '\r' | '\u{85}' | '\u{2028}' | '\u{2029}' => ' ',
736 c if is_controll_which_needs_escaping(c) => ' ',
737 _ => ch,
740 })
741 .collect()
742 }
743
744 #[cfg(feature = "comments")]
745 fn write_above_comment(&mut self, comment: &str) -> Result<Option<usize>> {
746 if self.state.in_flow > 0 || comment.is_empty() {
747 return Ok(None);
748 }
749
750 let target_depth = if self.state.pending_layout.pending_space_after_colon {
751 let base = self.state.current_map_depth.unwrap_or(self.state.depth);
752 self.state.pending_layout.pending_space_after_colon = false;
753 if !self.state.at_line_start {
754 self.newline()?;
755 }
756 checked_depth_add(base, 1)?
757 } else if !self.state.at_line_start {
758 let base = self.state.after_dash_depth.unwrap_or(self.state.depth);
759 self.state.pending_layout.pending_inline_map = false;
760 self.newline()?;
761 checked_depth_add(base, 1)?
762 } else {
763 self.state.depth
764 };
765
766 self.write_indent(target_depth)?;
767 self.out.write_str("# ")?;
768 self.out.write_str(comment)?;
769 self.newline()?;
770 Ok(Some(target_depth))
771 }
772
773 #[inline]
776 fn alloc_anchor_for(&mut self, ptr: usize) -> Result<(AnchorId, bool)> {
777 match self.anchors.by_ptr.entry(ptr) {
778 std::collections::hash_map::Entry::Occupied(e) => Ok((*e.get(), false)),
779 std::collections::hash_map::Entry::Vacant(v) => {
780 let id = self.anchors.next_id;
781 let custom_name = if let Some(generator) = self.anchors.generator {
782 let name = generator(id as usize);
783 if !is_supported_anchor_name(&name) {
784 return Err(Error::InvalidOptions(format!(
785 "custom anchor generator returned an invalid name for anchor id {id}; names must be 1..={MAX_ANCHOR_NAME_BYTES} bytes and contain no whitespace, control characters, or '[', ']', '{{', '}}', ','"
786 )));
787 }
788 Some(name)
789 } else {
790 None
791 };
792
793 self.anchors.next_id = self.anchors.next_id.saturating_add(1);
794 if let Some(name) = custom_name {
795 self.anchors
796 .custom_names
797 .get_or_insert_with(Vec::new)
798 .push(name);
799 }
800 v.insert(id);
801 Ok((id, true))
802 }
803 }
804 }
805
806 #[inline]
808 fn write_anchor_name(&mut self, id: AnchorId) -> Result<()> {
809 if let Some(names) = &self.anchors.custom_names {
810 let idx = id as usize - 1;
812 if let Some(name) = names.get(idx) {
813 self.out.write_str(name)?;
814 } else {
815 write!(self.out, "a{id}")?;
817 }
818 } else {
819 write!(self.out, "a{id}")?;
820 }
821 Ok(())
822 }
823
824 #[inline]
827 fn write_space_if_pending(&mut self) -> Result<()> {
828 if self.state.pending_layout.pending_space_after_colon {
829 self.out.write_char(' ')?;
830 self.state.pending_layout.pending_space_after_colon = false;
831 }
832 self.state.last_value_was_block = false;
835 Ok(())
836 }
837
838 #[inline]
841 fn write_indent(&mut self, depth: usize) -> Result<()> {
842 if self.state.at_line_start {
843 if !self.state.doc_started {
844 self.state.doc_started = true;
845 if self.settings.yaml_12 && !self.settings.no_lang_directive {
846 self.out.write_str("%YAML 1.2\n---\n")?;
847 self.state.at_line_start = true;
849 }
850 }
851 self.write_indent_spaces(depth)?;
852 self.state.at_line_start = false;
853 }
854 Ok(())
855 }
856
857 #[inline]
858 fn write_indent_spaces(&mut self, depth: usize) -> Result<()> {
859 let spaces = checked_indentation(self.settings.indent_step, depth)?;
860 for _ in 0..spaces {
861 self.out.write_char(' ')?;
862 }
863 Ok(())
864 }
865
866 fn variant_depth_after_dash(
868 &self,
869 dash_depth: usize,
870 anchor_broke_line: bool,
871 ) -> Result<usize> {
872 let levels = if anchor_broke_line {
876 1
877 } else {
878 2usize.div_ceil(self.settings.indent_step)
879 };
880 checked_depth_add(dash_depth, levels)
881 }
882
883 #[inline]
886 fn newline(&mut self) -> Result<()> {
887 self.out.write_char('\n')?;
888 self.state.at_line_start = true;
889 Ok(())
890 }
891
892 fn write_folded_block(&mut self, s: &str, column: usize) -> Result<()> {
895 self::wrapping::write_folded_block(self.out, s, column, 1, self.settings.folded_wrap_col)?;
896 self.state.at_line_start = true;
897 Ok(())
898 }
899
900 fn write_key_scalar(&mut self, s: &str) -> Result<()> {
906 let text = scalar_key_to_string(&s, self.settings.yaml_12)?;
907 self.out.write_str(&text)?;
908 Ok(())
909 }
910
911 fn write_quoted(&mut self, s: &str) -> Result<()> {
913 self.out.write_char('"')?;
914 escape_double_quoted(s, self.out)?;
915 self.out.write_char('"')?;
916 Ok(())
917 }
918
919 #[inline]
921 fn write_plain_or_quoted_value(&mut self, s: &str) -> Result<()> {
922 if self.settings.quote_all {
923 if Self::needs_double_quotes(s) {
925 self.write_quoted(s)
926 } else {
927 self.write_single_quoted(s)
928 }
929 } else if is_plain_value_safe(s, self.settings.yaml_12, self.state.in_flow > 0) {
930 self.out.write_str(s)?;
931 Ok(())
932 } else {
933 self.write_quoted(s)
935 }
936 }
937
938 fn stage_resolved_tag(&mut self, resolved: &str) -> Result<()> {
940 if resolved.is_empty() {
941 return Err(Error::EmptyResolvedTag);
942 }
943 let emitted = resolved_tag_token(resolved);
944 if !resolved.starts_with('!') && !absolute_uri_is_valid(&emitted[2..emitted.len() - 1]) {
945 return Err(Error::InvalidGlobalTagUri {
946 tag: resolved.to_owned(),
947 });
948 }
949 self.stage_tag_with_token(resolved, emitted)
950 }
951
952 fn serialize_tagged_value<T: ?Sized + Serialize>(
955 &mut self,
956 resolved: &str,
957 value: &T,
958 ) -> Result<()> {
959 let previous = self.state.pending_tag.clone();
960 let result = self
961 .stage_resolved_tag(resolved)
962 .and_then(|()| value.serialize(&mut *self));
963 if result.is_err() {
964 self.state.pending_tag = previous;
965 }
966 result
967 }
968
969 fn stage_tag_with_token(&mut self, resolved: &str, emitted: String) -> Result<()> {
974 match self.state.pending_tag.as_ref() {
975 Some(pending) if pending.resolved == resolved => Ok(()),
976 Some(pending) => Err(Error::custom(format_args!(
977 "cannot serialize one YAML node with both tag {:?} and tag {:?}",
978 pending.resolved, resolved
979 ))),
980 None => {
981 self.state.pending_tag = Some(PendingTag {
982 resolved: resolved.to_owned(),
983 emitted,
984 variant_selector_claimed: false,
985 });
986 Ok(())
987 }
988 }
989 }
990
991 fn stage_core_tag(&mut self, suffix: &str) -> Result<()> {
992 let mut resolved = String::with_capacity(YAML_TAG_NAMESPACE.len() + suffix.len());
993 resolved.push_str(YAML_TAG_NAMESPACE);
994 resolved.push_str(suffix);
995 self.stage_tag_with_token(&resolved, core_tag_token(suffix))
996 }
997
998 #[inline]
1004 fn pending_tag_selects_variant(&mut self, variant: &str) -> Result<bool> {
1005 let Some(tag) = self.state.pending_tag.as_mut() else {
1006 return Ok(false);
1007 };
1008 let resolved = tag.resolved.as_str();
1009
1010 if yaml_core_type_tag_name(resolved) == Some(variant) {
1011 return Err(Error::CoreTypeTagAsEnumVariant {
1012 tag: resolved.to_owned(),
1013 variant: variant.to_owned(),
1014 });
1015 }
1016
1017 if simple_enum_variant_name(resolved) != Some(variant) || tag.variant_selector_claimed {
1018 return Ok(false);
1019 }
1020
1021 tag.variant_selector_claimed = true;
1022 Ok(true)
1023 }
1024
1025 fn serialize_tagged_scalar(&mut self, enum_name: &str, variant: &str) -> Result<()> {
1028 self.stage_core_tag(enum_name)?;
1029 self.write_scalar_prefix_if_anchor()?;
1030 if self.state.at_line_start {
1031 self.write_indent(self.state.depth)?;
1032 }
1033 self.write_plain_or_quoted_value(variant)?;
1034 self.write_end_of_scalar()
1035 }
1036
1037 fn serialize_double_quoted_scalar(&mut self, value: &str) -> Result<()> {
1038 self.write_space_if_pending()?;
1039 self.write_scalar_prefix_if_anchor()?;
1040 if self.state.at_line_start {
1041 self.write_indent(self.state.depth)?;
1042 }
1043 self.write_quoted(value)?;
1044 self.write_end_of_scalar()
1045 }
1046
1047 fn serialize_single_quoted_scalar(&mut self, value: &str) -> Result<()> {
1048 if let Some(ch) = value
1049 .chars()
1050 .find(|ch| is_controll_which_needs_escaping(*ch))
1051 {
1052 return Err(Error::SingleQuotedRequiresEscaping { ch });
1053 }
1054 self.write_space_if_pending()?;
1055 self.write_scalar_prefix_if_anchor()?;
1056 if self.state.at_line_start {
1057 self.write_indent(self.state.depth)?;
1058 }
1059 self.write_single_quoted(value)?;
1060 self.write_end_of_scalar()
1061 }
1062
1063 fn serialize_tilde_null(&mut self) -> Result<()> {
1064 self.state.pending_flow = None;
1065 self.write_space_if_pending()?;
1066 self.write_scalar_prefix_if_anchor()?;
1067 if self.state.at_line_start {
1068 self.write_indent(self.state.depth)?;
1069 }
1070 self.out.write_char('~')?;
1071 self.write_end_of_scalar()
1072 }
1073
1074 #[inline]
1076 fn write_scalar_prefix_if_anchor(&mut self) -> Result<()> {
1077 let tag = self.state.pending_tag.take();
1078 let anchor_id = self.anchors.pending_id.take();
1079 if tag.is_some() || anchor_id.is_some() {
1080 if self.state.at_line_start {
1081 self.write_indent(self.state.depth)?;
1082 }
1083 if let Some(tag) = tag.as_ref() {
1084 self.out.write_str(&tag.emitted)?;
1085 self.out.write_char(' ')?;
1086 }
1087 if let Some(id) = anchor_id {
1088 self.anchors
1089 .record_resolved_tag(id, tag.as_ref().map(|tag| tag.resolved.as_str()));
1090 self.out.write_char('&')?;
1091 self.write_anchor_name(id)?;
1092 self.out.write_char(' ')?;
1093 }
1094 }
1095 Ok(())
1096 }
1097
1098 #[inline]
1100 fn write_anchor_for_complex_node(&mut self) -> Result<()> {
1101 let tag = self.state.pending_tag.take();
1102 let anchor_id = self.anchors.pending_id.take();
1103 if tag.is_some() || anchor_id.is_some() {
1104 self.write_space_if_pending()?;
1105 if self.state.at_line_start {
1106 self.write_indent(self.state.depth)?;
1107 }
1108 if let Some(tag) = tag.as_ref() {
1109 self.out.write_str(&tag.emitted)?;
1110 }
1111 if let Some(id) = anchor_id {
1112 self.anchors
1113 .record_resolved_tag(id, tag.as_ref().map(|tag| tag.resolved.as_str()));
1114 if tag.is_some() {
1115 self.out.write_char(' ')?;
1116 }
1117 self.out.write_char('&')?;
1118 self.write_anchor_name(id)?;
1119 }
1120 self.newline()?;
1121 }
1122 Ok(())
1123 }
1124
1125 #[inline]
1128 fn write_alias_id(&mut self, id: AnchorId) -> Result<()> {
1129 let anchor_tag = self.anchors.resolved_tag(id);
1130 if let Some(requested) = self.state.pending_tag.take()
1131 && anchor_tag != Some(requested.resolved.as_str())
1132 {
1133 return Err(Error::custom(format_args!(
1134 "cannot apply tag {:?} to alias whose anchor was defined with tag {:?}",
1135 requested.resolved,
1136 anchor_tag.unwrap_or("")
1137 )));
1138 }
1139 if self.state.at_line_start {
1140 self.write_indent(self.state.depth)?;
1141 }
1142 self.write_space_if_pending()?;
1143 self.out.write_char('*')?;
1144 self.write_anchor_name(id)?;
1145 self.write_end_of_scalar()?;
1147 Ok(())
1148 }
1149
1150 #[inline]
1151 fn has_pending_node_properties(&self) -> bool {
1152 self.state.pending_tag.is_some() || self.anchors.pending_id.is_some()
1153 }
1154
1155 #[inline]
1158 fn take_flow_for_seq(&mut self) -> bool {
1159 if self.state.in_flow > 0 {
1160 true
1161 } else {
1162 matches!(self.state.pending_flow.take(), Some(PendingFlow::AnySeq))
1163 }
1164 }
1165 #[inline]
1168 fn take_flow_for_map(&mut self) -> bool {
1169 if self.state.in_flow > 0 {
1170 true
1171 } else {
1172 matches!(self.state.pending_flow.take(), Some(PendingFlow::AnyMap))
1173 }
1174 }
1175
1176 #[inline]
1179 fn with_in_flow<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
1180 self.state.in_flow += 1;
1181 let r = f(self);
1182 self.state.in_flow -= 1;
1183 r
1184 }
1185}
1186
1187impl<'a, 'b, W: Write> Serializer for &'a mut YamlSerializer<'b, W> {
1192 type Ok = ();
1193 type Error = Error;
1194
1195 type SerializeSeq = SeqSer<'a, 'b, W>;
1196 type SerializeTuple = SeqSer<'a, 'b, W>;
1197 type SerializeTupleStruct = TupleSer<'a, 'b, W>;
1198 type SerializeTupleVariant = SeqSer<'a, 'b, W>;
1199 type SerializeMap = MapSer<'a, 'b, W>;
1200 type SerializeStruct = MapSer<'a, 'b, W>;
1201 type SerializeStructVariant = StructVariantSer<'a, 'b, W>;
1202
1203 fn serialize_bool(self, v: bool) -> Result<()> {
1206 self.write_space_if_pending()?;
1207 self.write_scalar_prefix_if_anchor()?;
1208 if self.state.at_line_start {
1209 self.write_indent(self.state.depth)?;
1210 }
1211 self.out.write_str(if v { "true" } else { "false" })?;
1212 self.write_end_of_scalar()?;
1213 Ok(())
1214 }
1215
1216 fn serialize_i8(self, v: i8) -> Result<()> {
1217 self.serialize_i64(i64::from(v))
1218 }
1219 fn serialize_i16(self, v: i16) -> Result<()> {
1220 self.serialize_i64(i64::from(v))
1221 }
1222 fn serialize_i32(self, v: i32) -> Result<()> {
1223 self.serialize_i64(i64::from(v))
1224 }
1225 fn serialize_i64(self, v: i64) -> Result<()> {
1226 self.write_space_if_pending()?;
1227 self.write_scalar_prefix_if_anchor()?;
1228 if self.state.at_line_start {
1229 self.write_indent(self.state.depth)?;
1230 }
1231 write!(self.out, "{v}")?;
1232 self.write_end_of_scalar()?;
1233 Ok(())
1234 }
1235
1236 fn serialize_i128(self, v: i128) -> Result<()> {
1237 self.write_space_if_pending()?;
1238 self.write_scalar_prefix_if_anchor()?;
1239 if self.state.at_line_start {
1240 self.write_indent(self.state.depth)?;
1241 }
1242 write!(self.out, "{v}")?;
1243 self.write_end_of_scalar()?;
1244 Ok(())
1245 }
1246
1247 fn serialize_u8(self, v: u8) -> Result<()> {
1248 self.serialize_u64(u64::from(v))
1249 }
1250 fn serialize_u16(self, v: u16) -> Result<()> {
1251 self.serialize_u64(u64::from(v))
1252 }
1253 fn serialize_u32(self, v: u32) -> Result<()> {
1254 self.serialize_u64(u64::from(v))
1255 }
1256 fn serialize_u64(self, v: u64) -> Result<()> {
1257 self.write_space_if_pending()?;
1258 self.write_scalar_prefix_if_anchor()?;
1259 if self.state.at_line_start {
1260 self.write_indent(self.state.depth)?;
1261 }
1262 write!(self.out, "{v}")?;
1263 self.write_end_of_scalar()?;
1264 Ok(())
1265 }
1266
1267 fn serialize_u128(self, v: u128) -> Result<()> {
1268 self.write_space_if_pending()?;
1269 self.write_scalar_prefix_if_anchor()?;
1270 if self.state.at_line_start {
1271 self.write_indent(self.state.depth)?;
1272 }
1273 write!(self.out, "{v}")?;
1274 self.write_end_of_scalar()?;
1275 Ok(())
1276 }
1277
1278 fn serialize_f32(self, v: f32) -> Result<()> {
1279 self.write_space_if_pending()?;
1280 self.write_scalar_prefix_if_anchor()?;
1281 if self.state.at_line_start {
1282 self.write_indent(self.state.depth)?;
1283 }
1284 zmij_format::write_float_string(self.out, v)?;
1285 self.write_end_of_scalar()
1286 }
1287
1288 fn serialize_f64(self, v: f64) -> Result<()> {
1289 self.write_space_if_pending()?;
1290 self.write_scalar_prefix_if_anchor()?;
1291 if self.state.at_line_start {
1292 self.write_indent(self.state.depth)?;
1293 }
1294 zmij_format::write_float_string(self.out, v)?;
1295 self.write_end_of_scalar()
1296 }
1297
1298 fn serialize_char(self, v: char) -> Result<()> {
1299 self.write_space_if_pending()?;
1300 let mut buf = [0u8; 4];
1301 self.serialize_str(v.encode_utf8(&mut buf))
1302 }
1303
1304 fn serialize_str(self, v: &str) -> Result<()> {
1305 #[inline]
1306 fn block_indent_indicator_digit(indent_n: usize) -> Result<char> {
1307 match indent_n {
1310 1..=9 => {
1311 let digit = u32::try_from(indent_n).expect("checked 1..=9");
1312 Ok(char::from_digit(digit, 10).expect("checked 1..=9"))
1313 }
1314 _ => Err(Error::custom(
1315 "indentation indicator must be a single digit (1..=9)",
1316 )),
1317 }
1318 }
1319
1320 if self.state.pending_str_style.is_none()
1331 && self.state.in_flow == 0
1332 && !self.settings.quote_all
1333 {
1334 if v.contains('\n') {
1335 if self.settings.prefer_block_scalars
1336 && is_block_scalar_content_safe(v)
1337 && is_auto_block_scalar_readable(v)
1338 {
1339 self.state.pending_str_style =
1340 Some(PendingStrStyle::Automatic(StrStyle::Literal));
1341 }
1342 } else if self.settings.prefer_block_scalars {
1343 let auto_fold_probe = v.trim_end_matches(' ');
1347 let can_auto_fold = !auto_fold_probe.is_empty()
1348 && is_plain_value_safe(auto_fold_probe, self.settings.yaml_12, false);
1349 if can_auto_fold {
1350 if v.chars().count() > self.settings.folded_wrap_col {
1352 self.state.pending_str_style =
1353 Some(PendingStrStyle::Automatic(StrStyle::Folded));
1354 }
1355 }
1356 }
1357 }
1358 if let Some(pending_style) = self.state.pending_str_style.take() {
1359 let (style, from_auto) = pending_style.into_parts();
1360 if !is_block_scalar_content_safe(v) {
1361 self.write_space_if_pending()?;
1362 self.write_scalar_prefix_if_anchor()?;
1363 if self.state.at_line_start {
1364 self.write_indent(self.state.depth)?;
1365 }
1366 self.write_quoted(v)?;
1367 self.write_end_of_scalar()?;
1368 return Ok(());
1369 }
1370
1371 let was_map_value = self.state.pending_layout.pending_space_after_colon;
1377 self.write_space_if_pending()?;
1378 let base = if was_map_value {
1388 self.state.current_map_depth.unwrap_or(self.state.depth)
1389 } else {
1390 self.state.after_dash_depth.unwrap_or(self.state.depth)
1393 };
1394 if self.state.at_line_start {
1395 self.write_indent(base)?;
1396 }
1397
1398 self.write_scalar_prefix_if_anchor()?;
1402
1403 let parent_column = match (was_map_value, self.state.current_map_key_column) {
1417 (true, Some(col)) => col,
1418 _ => checked_indentation(self.settings.indent_step, base)?,
1419 };
1420 let body_column = parent_column
1421 .checked_add(self.settings.indent_step)
1422 .ok_or_else(|| Error::custom("serializer indentation exceeds usize"))?;
1423 let indent_n = self.settings.indent_step;
1424
1425 let content_trimmed = v.trim_end_matches('\n');
1428 let first_line_spaces = self::wrapping::first_line_leading_spaces(content_trimmed);
1429 let needs_indicator = first_line_spaces > 0;
1430
1431 let indicator_digit = if needs_indicator {
1435 let Ok(digit) = block_indent_indicator_digit(indent_n) else {
1436 self.write_plain_or_quoted_value(v)?;
1437 self.write_end_of_scalar()?;
1438 return Ok(());
1439 };
1440 Some(digit)
1441 } else {
1442 None
1443 };
1444
1445 match style {
1446 StrStyle::Literal => {
1447 let content = v.trim_end_matches('\n');
1453 let trailing_nl = v.len() - content.len();
1454
1455 self.out.write_char('|')?;
1457 if let Some(digit) = indicator_digit {
1458 self.out.write_char(digit)?;
1459 }
1460 match trailing_nl {
1461 0 => self.out.write_char('-')?,
1462 1 if !content.is_empty() => {}
1464 _ => self.out.write_char('+')?,
1465 }
1466 #[cfg(feature = "comments")]
1467 self.write_pending_inline_comment()?;
1468 self.newline()?;
1469
1470 let mut indent_buf: String = String::new();
1475 let spaces = body_column;
1476 if spaces > 0 {
1477 indent_buf.reserve(spaces);
1478 for _ in 0..spaces {
1479 indent_buf.push(' ');
1480 }
1481 }
1482 let indent_str = indent_buf.as_str();
1483
1484 if content.is_empty() {
1485 for _ in 0..trailing_nl {
1486 self.out.write_str(indent_str)?;
1487 self.state.at_line_start = false;
1488 self.newline()?;
1489 }
1490 } else {
1491 for line in content.split('\n') {
1492 self.out.write_str(indent_str)?;
1493 self.state.at_line_start = false;
1494 self.out.write_str(line)?;
1495 self.newline()?;
1496 }
1497 if trailing_nl >= 2 {
1498 for _ in 0..(trailing_nl - 1) {
1499 self.out.write_str(indent_str)?;
1500 self.state.at_line_start = false;
1501 self.newline()?;
1502 }
1503 }
1504 }
1505 }
1506 StrStyle::Folded => {
1507 self.out.write_char('>')?;
1509 if let Some(digit) = indicator_digit {
1510 self.out.write_char(digit)?;
1511 }
1512 if from_auto {
1513 let content = v.trim_end_matches('\n');
1516 let trailing_nl = v.len() - content.len();
1517 match trailing_nl {
1518 0 => self.out.write_char('-')?,
1519 1 => {} _ => self.out.write_char('+')?,
1521 }
1522 }
1523 #[cfg(feature = "comments")]
1526 self.write_pending_inline_comment()?;
1527 self.newline()?;
1528 self.write_folded_block(v, body_column)?;
1529 }
1530 }
1531 return Ok(());
1532 }
1533 self.write_space_if_pending()?;
1534 self.write_scalar_prefix_if_anchor()?;
1535 if self.state.at_line_start {
1536 self.write_indent(self.state.depth)?;
1537 }
1538 if v.len() == 1
1541 && let Some(ch) = v.chars().next()
1542 && (ch == '.' || ch == '#' || ch == '-')
1543 {
1544 self.out.write_char('\'')?;
1545 self.out.write_char(ch)?;
1546 self.out.write_char('\'')?;
1547 self.write_end_of_scalar()?;
1548 return Ok(());
1549 }
1550 self.write_plain_or_quoted_value(v)?;
1551 self.write_end_of_scalar()?;
1552 Ok(())
1553 }
1554
1555 fn serialize_bytes(self, v: &[u8]) -> Result<()> {
1556 if self.state.at_line_start && self.state.pending_tag.is_none() {
1570 let mut seq = self.serialize_seq(Some(v.len()))?;
1572 for b in v {
1573 serde_core::ser::SerializeSeq::serialize_element(&mut seq, b)?;
1574 }
1575 return serde_core::ser::SerializeSeq::end(seq);
1576 }
1577
1578 self.write_space_if_pending()?;
1581 self.stage_core_tag("binary")?;
1582 self.write_scalar_prefix_if_anchor()?;
1583 let mut s = String::new();
1585 B64.encode_string(v, &mut s);
1586 self.out.write_str(&s)?;
1587 self.write_end_of_scalar()?;
1588 Ok(())
1589 }
1590
1591 fn serialize_none(self) -> Result<()> {
1592 self.write_space_if_pending()?;
1593 self.write_scalar_prefix_if_anchor()?;
1594 self.state.last_value_was_block = false;
1595 if self.state.at_line_start {
1596 self.write_indent(self.state.depth)?;
1597 }
1598 self.out.write_str("null")?;
1599 self.write_end_of_scalar()?;
1600 Ok(())
1601 }
1602
1603 fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
1604 value.serialize(self)
1605 }
1606
1607 fn serialize_unit(self) -> Result<()> {
1608 self.write_space_if_pending()?;
1609 self.write_scalar_prefix_if_anchor()?;
1610 self.state.last_value_was_block = false;
1611 if self.state.at_line_start {
1612 self.write_indent(self.state.depth)?;
1613 }
1614 self.out.write_str("null")?;
1615 self.write_end_of_scalar()?;
1616 Ok(())
1617 }
1618
1619 fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
1620 self.serialize_unit()
1621 }
1622
1623 fn serialize_unit_variant(
1624 self,
1625 name: &'static str,
1626 _variant_index: u32,
1627 variant: &'static str,
1628 ) -> Result<()> {
1629 self.write_space_if_pending()?;
1631 if self.settings.tagged_enums {
1632 self.serialize_tagged_scalar(name, variant)
1633 } else {
1634 self.serialize_str(variant)
1635 }
1636 }
1637
1638 fn serialize_newtype_struct<T: ?Sized + Serialize>(
1639 self,
1640 name: &'static str,
1641 value: &T,
1642 ) -> Result<()> {
1643 match name {
1645 NAME_FLOW_SEQ => {
1646 self.state.pending_flow = Some(PendingFlow::AnySeq);
1647 return value.serialize(self);
1648 }
1649 NAME_FLOW_MAP => {
1650 self.state.pending_flow = Some(PendingFlow::AnyMap);
1651 return value.serialize(self);
1652 }
1653 NAME_LIT_STR => {
1654 let mut cap = StrCapture::default();
1658 value.serialize(&mut cap)?;
1659 let s = cap.finish()?;
1660 self.state.pending_str_style = Some(PendingStrStyle::Explicit(StrStyle::Literal));
1661 return self.serialize_str(&s);
1662 }
1663 NAME_FOLD_STR => {
1664 let mut cap = StrCapture::default();
1665 value.serialize(&mut cap)?;
1666 let s = cap.finish()?;
1667 let is_multiline = s.contains('\n');
1668 if !is_multiline && s.len() < self.settings.min_fold_chars {
1669 return self.serialize_str(&s);
1670 }
1671 self.state.pending_str_style = Some(PendingStrStyle::Explicit(StrStyle::Folded));
1672 return self.serialize_str(&s);
1673 }
1674 NAME_SPACE_AFTER => {
1675 let result = value.serialize(&mut *self);
1677 if self.state.in_flow == 0 {
1678 self.newline()?;
1680 }
1681 return result;
1682 }
1683 NAME_DOUBLE_QUOTED => {
1684 let mut cap = StrCapture::default();
1685 value.serialize(&mut cap)?;
1686 let text = cap.finish()?;
1687 return self.serialize_double_quoted_scalar(&text);
1688 }
1689 NAME_SINGLE_QUOTED => {
1690 let mut cap = StrCapture::default();
1691 value.serialize(&mut cap)?;
1692 let text = cap.finish()?;
1693 return self.serialize_single_quoted_scalar(&text);
1694 }
1695 NAME_NULLABLE_TILDE => {
1696 return self.serialize_tilde_null();
1697 }
1698 _ => {}
1699 }
1700 value.serialize(self)
1702 }
1703
1704 fn serialize_newtype_variant<T: ?Sized + Serialize>(
1705 self,
1706 _name: &'static str,
1707 _variant_index: u32,
1708 variant: &'static str,
1709 value: &T,
1710 ) -> Result<()> {
1711 if self.pending_tag_selects_variant(variant)? {
1715 return value.serialize(self);
1716 }
1717
1718 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
1719 let anchor_broke_line = self.has_pending_node_properties();
1720 let after_dash_depth = self.state.after_dash_depth;
1721 self.write_anchor_for_complex_node()?;
1722
1723 if was_inline_value {
1728 self.state.pending_layout.pending_space_after_colon = false;
1730 if !self.state.at_line_start {
1731 self.newline()?;
1732 }
1733 let base = self.state.current_map_depth.unwrap_or(self.state.depth);
1736 let variant_depth = checked_depth_add(base, 1)?;
1737 self.write_indent(variant_depth)?;
1738 self.write_key_scalar(variant)?;
1739 self.out.write_str(":")?;
1742 self.state.pending_layout.pending_space_after_colon = true;
1743 self.state.at_line_start = false;
1744 self.state.pending_layout.pending_inline_map = false;
1747 let prev_map_depth = self.state.current_map_depth.replace(variant_depth);
1750 let prev_key_column = self.state.current_map_key_column.take();
1753 let res = value.serialize(&mut *self);
1754 self.state.current_map_depth = prev_map_depth;
1755 self.state.current_map_key_column = prev_key_column;
1756 return res;
1757 }
1758 if self.state.at_line_start {
1760 let depth = if anchor_broke_line {
1761 match after_dash_depth {
1762 Some(depth) => checked_depth_add(depth, 1)?,
1763 None => self.state.depth,
1764 }
1765 } else {
1766 self.state.depth
1767 };
1768 self.write_indent(depth)?;
1769 }
1770 self.write_key_scalar(variant)?;
1771 self.out.write_str(":")?;
1773 self.state.pending_layout.pending_space_after_colon = true;
1774 self.state.at_line_start = false;
1775 self.state.pending_layout.pending_inline_map = false;
1778 if let Some(d) = self.state.after_dash_depth.take() {
1782 let nested_depth = self.variant_depth_after_dash(d, anchor_broke_line)?;
1783 let prev_map_depth = self.state.current_map_depth.replace(nested_depth);
1784 let key_column = if anchor_broke_line {
1787 None
1788 } else {
1789 Some(checked_indentation(self.settings.indent_step, d)? + 2)
1790 };
1791 let prev_key_column =
1792 std::mem::replace(&mut self.state.current_map_key_column, key_column);
1793 let res = value.serialize(&mut *self);
1794 self.state.current_map_depth = prev_map_depth;
1795 self.state.current_map_key_column = prev_key_column;
1796 res
1797 } else {
1798 value.serialize(&mut *self)
1799 }
1800 }
1801
1802 fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
1805 let flow = self.take_flow_for_seq();
1806 if flow {
1807 self.write_space_if_pending()?;
1809 self.write_scalar_prefix_if_anchor()?;
1810 if self.state.at_line_start {
1811 self.write_indent(self.state.depth)?;
1812 }
1813 self.out.write_str("[")?;
1814 self.state.at_line_start = false;
1815 let depth_next = self.state.depth; Ok(SeqSer {
1817 ser: self,
1818 depth: depth_next,
1819 flow: true,
1820 first: true,
1821 force_empty_marker: false,
1822 })
1823 } else {
1824 let was_inline_value = !self.state.at_line_start;
1826
1827 if self.state.pending_layout.pending_space_after_colon
1831 && self.state.last_value_was_block
1832 && !self.has_pending_node_properties()
1833 {
1834 self.state.pending_layout.pending_space_after_colon = false;
1835 if !self.state.at_line_start {
1836 self.newline()?;
1837 }
1838 self.state.last_value_was_block = false;
1840 }
1841
1842 let after_dash = (!self.state.at_line_start)
1853 && self.state.after_dash_depth.is_some()
1854 && !self.state.pending_layout.pending_space_after_colon;
1855 let inline_first = after_dash && self.settings.indent_step == 2;
1856 let anchor_broke_line = self.has_pending_node_properties();
1858 if after_dash && !inline_first {
1860 self.state.pending_layout.pending_inline_map = false;
1861 if !anchor_broke_line {
1862 self.newline()?;
1863 }
1864 }
1865 self.write_anchor_for_complex_node()?;
1866 if inline_first {
1867 if anchor_broke_line {
1868 self.state.pending_layout.pending_inline_map = false;
1870 } else {
1871 self.state.at_line_start = false;
1873 }
1874 } else if was_inline_value {
1875 }
1880 let base = if after_dash {
1885 self.state.after_dash_depth.unwrap_or(self.state.depth)
1886 } else if was_inline_value && self.state.current_map_depth.is_some() {
1887 self.state.current_map_depth.unwrap_or(self.state.depth)
1888 } else {
1889 self.state.depth
1890 };
1891 let depth_next = if after_dash {
1895 checked_depth_add(base, 1)?
1896 } else if was_inline_value {
1897 if self.settings.compact_list_indent
1902 && self.state.current_map_depth.is_some()
1903 && !anchor_broke_line
1904 && self.settings.indent_step >= 2
1905 {
1906 base
1907 } else {
1908 checked_depth_add(base, 1)?
1909 }
1910 } else {
1911 base
1912 };
1913 #[cfg(feature = "comments")]
1915 {
1916 self.state.pending_inline_comment = None;
1917 }
1918 Ok(SeqSer {
1919 ser: self,
1920 depth: depth_next,
1921 flow: false,
1922 first: true,
1923 force_empty_marker: anchor_broke_line,
1924 })
1925 }
1926 }
1927
1928 fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
1929 self.serialize_seq(Some(len))
1930 }
1931
1932 fn serialize_tuple_struct(
1933 self,
1934 name: &'static str,
1935 len: usize,
1936 ) -> Result<Self::SerializeTupleStruct> {
1937 match name {
1938 NAME_TUPLE_ANCHOR => Ok(TupleSer::anchor_strong(self)),
1939 NAME_TUPLE_WEAK => Ok(TupleSer::anchor_weak(self)),
1940 #[cfg(feature = "comments")]
1941 NAME_TUPLE_COMMENTED => Ok(TupleSer::commented(self)),
1942 NAME_TUPLE_TAGGED => Ok(TupleSer::tagged(self)),
1943 _ => Ok(TupleSer::sequence(self.serialize_seq(Some(len))?)),
1945 }
1946 }
1947
1948 fn serialize_tuple_variant(
1949 self,
1950 _name: &'static str,
1951 _variant_index: u32,
1952 variant: &'static str,
1953 len: usize,
1954 ) -> Result<Self::SerializeTupleVariant> {
1955 if self.pending_tag_selects_variant(variant)? {
1956 return self.serialize_seq(Some(len));
1957 }
1958
1959 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
1960 let anchor_broke_line = self.has_pending_node_properties();
1961 let after_dash_depth = self.state.after_dash_depth;
1962 self.write_anchor_for_complex_node()?;
1963
1964 if was_inline_value {
1968 self.state.pending_layout.pending_space_after_colon = false;
1969 if !self.state.at_line_start {
1970 self.newline()?;
1971 }
1972 let base =
1973 checked_depth_add(self.state.current_map_depth.unwrap_or(self.state.depth), 1)?;
1974 self.write_indent(base)?;
1975 self.write_key_scalar(variant)?;
1976 self.out.write_str(":\n")?;
1977 self.state.at_line_start = true;
1978 self.state.pending_layout.pending_inline_map = false;
1979 let depth_next = checked_depth_add(base, 1)?;
1980 return Ok(SeqSer {
1981 ser: self,
1982 depth: depth_next,
1983 flow: false,
1984 first: true,
1985 force_empty_marker: false,
1986 });
1987 }
1988 if self.state.at_line_start {
1990 let depth = if anchor_broke_line {
1991 match after_dash_depth {
1992 Some(depth) => checked_depth_add(depth, 1)?,
1993 None => self.state.depth,
1994 }
1995 } else {
1996 self.state.depth
1997 };
1998 self.write_indent(depth)?;
1999 }
2000 self.write_key_scalar(variant)?;
2001 self.out.write_str(":\n")?;
2002 self.state.at_line_start = true;
2003 let mut depth_next = checked_depth_add(self.state.depth, 1)?;
2004 if let Some(d) = self.state.after_dash_depth.take() {
2005 depth_next =
2006 checked_depth_add(self.variant_depth_after_dash(d, anchor_broke_line)?, 1)?;
2007 self.state.pending_layout.pending_inline_map = false;
2008 }
2009 Ok(SeqSer {
2010 ser: self,
2011 depth: depth_next,
2012 flow: false,
2013 first: true,
2014 force_empty_marker: false,
2015 })
2016 }
2017
2018 fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
2019 let flow = self.take_flow_for_map();
2020 if flow {
2021 self.write_space_if_pending()?;
2023 self.write_scalar_prefix_if_anchor()?;
2024 if self.state.at_line_start {
2025 self.write_indent(self.state.depth)?;
2026 }
2027 self.out.write_str("{")?;
2028 self.state.at_line_start = false;
2029 let depth_next = self.state.depth;
2030 Ok(MapSer::flow(self, depth_next))
2031 } else {
2032 let node_properties_broke_line = self.has_pending_node_properties();
2033 if self.state.pending_layout.pending_inline_map
2038 && self.settings.indent_step < 2
2039 && !self.state.at_line_start
2040 && self.state.after_dash_depth.is_some()
2041 && !self.state.pending_layout.pending_space_after_colon
2042 {
2043 self.state.pending_layout.pending_inline_map = false;
2044 self.newline()?;
2045 if let Some(d) = self.state.after_dash_depth {
2046 self.state.depth = checked_depth_add(d, 1)?;
2047 }
2048 }
2049 let inline_first = self.state.pending_layout.pending_inline_map;
2050 #[cfg(feature = "comments")]
2052 {
2053 self.state.pending_inline_comment = None;
2054 }
2055 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
2057 let mut forced_newline = false;
2058
2059 if was_inline_value
2063 && self.state.last_value_was_block
2064 && !self.has_pending_node_properties()
2065 {
2066 self.state.pending_layout.pending_space_after_colon = false;
2067 if !self.state.at_line_start {
2068 self.newline()?;
2069 }
2070 forced_newline = true;
2071 self.state.last_value_was_block = false;
2073 }
2074
2075 self.write_anchor_for_complex_node()?;
2076 if inline_first {
2077 self.state.pending_layout.pending_inline_map = false;
2079 self.state.pending_layout.inline_map_after_dash = true;
2081 } else if was_inline_value {
2082 let known_empty = matches!(len, Some(0));
2090 let known_non_empty = matches!(len, Some(n) if n > 0);
2091
2092 if !self.settings.empty_as_braces || known_non_empty {
2093 self.state.pending_layout.pending_space_after_colon = false;
2096 if !self.state.at_line_start {
2097 self.newline()?;
2098 }
2099 } else if !known_empty {
2100 }
2103 }
2104 let base = if inline_first {
2111 self.state.after_dash_depth.unwrap_or(self.state.depth)
2112 } else if was_inline_value && self.state.current_map_depth.is_some() {
2113 self.state.current_map_depth.unwrap_or(self.state.depth)
2114 } else {
2115 self.state.depth
2116 };
2117 let depth_next = if inline_first || was_inline_value {
2118 checked_depth_add(base, 1)?
2119 } else {
2120 base
2121 };
2122 let inline_value_start_flag = was_inline_value
2123 && self.settings.empty_as_braces
2124 && len.is_none()
2125 && !inline_first
2126 && !forced_newline;
2127 Ok(MapSer::block(
2128 self,
2129 depth_next,
2130 inline_first,
2131 inline_value_start_flag,
2132 node_properties_broke_line,
2133 ))
2134 }
2135 }
2136
2137 fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
2138 self.serialize_map(Some(len))
2139 }
2140
2141 fn serialize_struct_variant(
2142 self,
2143 _name: &'static str,
2144 _variant_index: u32,
2145 variant: &'static str,
2146 len: usize,
2147 ) -> Result<Self::SerializeStructVariant> {
2148 if self.pending_tag_selects_variant(variant)? {
2149 return Ok(StructVariantSer::payload(self.serialize_map(Some(len))?));
2150 }
2151
2152 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
2153 let anchor_broke_line = self.has_pending_node_properties();
2154 let after_dash_depth = self.state.after_dash_depth;
2155 self.write_anchor_for_complex_node()?;
2156
2157 if was_inline_value {
2161 self.state.pending_layout.pending_space_after_colon = false;
2163 if !self.state.at_line_start {
2164 self.newline()?;
2165 }
2166 let base =
2168 checked_depth_add(self.state.current_map_depth.unwrap_or(self.state.depth), 1)?;
2169 self.write_indent(base)?;
2170 self.write_key_scalar(variant)?;
2171 self.out.write_str(":\n")?;
2172 self.state.at_line_start = true;
2173 self.state.pending_layout.pending_inline_map = false;
2175 let depth_next = checked_depth_add(base, 1)?;
2177 return Ok(StructVariantSer::external(self, depth_next));
2178 }
2179 if self.state.at_line_start {
2181 let depth = if anchor_broke_line {
2182 match after_dash_depth {
2183 Some(depth) => checked_depth_add(depth, 1)?,
2184 None => self.state.depth,
2185 }
2186 } else {
2187 self.state.depth
2188 };
2189 self.write_indent(depth)?;
2190 }
2191 self.write_key_scalar(variant)?;
2192 self.out.write_str(":\n")?;
2193 self.state.at_line_start = true;
2194 let mut depth_next = checked_depth_add(self.state.depth, 1)?;
2196 if let Some(d) = self.state.after_dash_depth.take() {
2198 depth_next =
2199 checked_depth_add(self.variant_depth_after_dash(d, anchor_broke_line)?, 1)?;
2200 self.state.pending_layout.pending_inline_map = false;
2201 }
2202 Ok(StructVariantSer::external(self, depth_next))
2203 }
2204}
2205
2206#[cfg(test)]
2207mod tests {
2208 use super::{YamlSerializer, absolute_uri_is_valid};
2209
2210 #[test]
2211 fn absolute_uri_validation_covers_global_tag_syntax() {
2212 for valid in [
2213 "tag:yaml.org,2002:str",
2214 "urn:isbn:9780141036144",
2215 "https://example.com/path?query=yes#section",
2216 "https://example.com",
2217 "git+ssh://user@example.com:22/repository",
2218 "custom:",
2219 "http://[2001:db8::1]:8080/path",
2220 "http://[v1.alpha:beta]/path",
2221 "tag:escaped%20text",
2222 ] {
2223 assert!(absolute_uri_is_valid(valid), "expected valid URI: {valid}");
2224 }
2225
2226 for invalid in [
2227 "",
2228 "$:?",
2229 "relative/path",
2230 "1scheme:value",
2231 "bad scheme:value",
2232 "http://[not-ip]/",
2233 "http://[v1]/",
2234 "http://[2001:db8::1/",
2235 "http://example.com:port/",
2236 "http://host:name:80/",
2237 "http://bad[info@example.com/",
2238 "http://first@second@third/",
2239 "http://example.com/path[0]",
2240 "tag:value?bad[query]",
2241 "tag:value#bad[fragment]",
2242 "tag:bad%escape",
2243 ] {
2244 assert!(
2245 !absolute_uri_is_valid(invalid),
2246 "expected invalid URI: {invalid}"
2247 );
2248 }
2249 }
2250
2251 #[test]
2252 fn write_indent_rejects_indentation_overflow() {
2253 let mut out = String::new();
2254 let mut serializer = YamlSerializer::new(&mut out);
2255 let err = serializer.write_indent(usize::MAX).unwrap_err();
2256 assert_eq!(err.to_string(), "serializer indentation exceeds usize");
2257 assert!(out.is_empty());
2258 }
2259}