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 #[allow(deprecated)] fn from(options: &SerializerOptions) -> Self {
460 Self {
461 indent_step: options.indent_step,
462 min_fold_chars: options.min_fold_chars,
463 folded_wrap_col: options.folded_wrap_chars,
464 #[cfg(feature = "comments")]
465 comment_position: options.comment_position,
466 tagged_enums: options.tagged_enums,
467 empty_as_braces: options.empty_as_braces,
468 compact_list_indent: options.compact_list_indent,
469 prefer_block_scalars: options.prefer_block_scalars,
470 quote_all: options.quote_all,
471 yaml_12: options.yaml_12,
472 no_lang_directive: options.no_lang_directive,
473 }
474 }
475}
476
477#[derive(Default)]
484struct PendingLayout {
485 pending_inline_map: bool,
487 pending_space_after_colon: bool,
489 inline_map_after_dash: bool,
491}
492
493struct SerializerState {
500 depth: usize,
502 at_line_start: bool,
504 pending_flow: Option<PendingFlow>,
506 in_flow: usize,
508 pending_str_style: Option<PendingStrStyle>,
510 pending_tag: Option<PendingTag>,
512 #[cfg(feature = "comments")]
514 pending_inline_comment: Option<String>,
515 pending_layout: PendingLayout,
517 last_value_was_block: bool,
519 after_dash_depth: Option<usize>,
521 current_map_depth: Option<usize>,
523 current_map_key_column: Option<usize>,
527 doc_started: bool,
529}
530
531impl Default for SerializerState {
532 fn default() -> Self {
533 Self {
534 depth: 0,
535 at_line_start: true,
536 pending_flow: None,
537 in_flow: 0,
538 pending_str_style: None,
539 pending_tag: None,
540 #[cfg(feature = "comments")]
541 pending_inline_comment: None,
542 pending_layout: PendingLayout::default(),
543 last_value_was_block: false,
544 after_dash_depth: None,
545 current_map_depth: None,
546 current_map_key_column: None,
547 doc_started: false,
548 }
549 }
550}
551
552struct AnchorState {
559 by_ptr: HashMap<usize, AnchorId, BuildNoHashHasher<usize>>,
561 next_id: AnchorId,
563 pending_id: Option<AnchorId>,
565 generator: Option<fn(usize) -> String>,
567 custom_names: Option<Vec<String>>,
569 resolved_tags: Vec<Option<String>>,
571}
572
573impl AnchorState {
574 fn new(generator: Option<fn(usize) -> String>) -> Self {
575 Self {
576 by_ptr: HashMap::with_hasher(BuildNoHashHasher::default()),
577 next_id: 1,
578 pending_id: None,
579 generator,
580 custom_names: None,
581 resolved_tags: Vec::new(),
582 }
583 }
584
585 fn record_resolved_tag(&mut self, id: AnchorId, tag: Option<&str>) {
586 let idx = id as usize - 1;
587 if self.resolved_tags.len() <= idx {
588 self.resolved_tags.resize_with(idx + 1, || None);
589 }
590 self.resolved_tags[idx] = tag.map(ToOwned::to_owned);
591 }
592
593 fn resolved_tag(&self, id: AnchorId) -> Option<&str> {
594 self.resolved_tags
595 .get(id as usize - 1)
596 .and_then(Option::as_deref)
597 }
598}
599
600pub struct YamlSerializer<'a, W: Write> {
626 out: &'a mut W,
628 settings: SerializerSettings,
630 state: SerializerState,
632 anchors: AnchorState,
634}
635
636impl<'a, W: Write> YamlSerializer<'a, W> {
637 #[must_use]
640 pub fn new(out: &'a mut W) -> Self {
641 Self::from_options_unchecked(out, &SerializerOptions::default())
642 }
643
644 fn from_options_unchecked(out: &'a mut W, options: &SerializerOptions) -> Self {
645 Self {
646 out,
647 settings: SerializerSettings::from(options),
648 state: SerializerState::default(),
649 anchors: AnchorState::new(options.anchor_generator),
650 }
651 }
652 pub fn with_indent(out: &'a mut W, indent_step: usize) -> Result<Self> {
656 let options = SerializerOptions {
657 indent_step,
658 ..SerializerOptions::default()
659 };
660 Self::with_options(out, options)
661 }
662 pub fn with_options(out: &'a mut W, options: SerializerOptions) -> Result<Self> {
665 options.consistent()?;
666 Ok(Self::from_options_unchecked(out, &options))
667 }
668
669 #[inline]
675 fn needs_double_quotes(s: &str) -> bool {
676 s.chars().any(|c| {
677 c == '\'' || c == '\\' || is_controll_which_needs_escaping(c) })
681 }
682
683 fn write_single_quoted(&mut self, s: &str) -> Result<()> {
685 self.out.write_char('\'')?;
686 let mut start = 0;
687 for (index, _) in s.match_indices('\'') {
688 if start < index {
689 self.out.write_str(&s[start..index])?;
690 }
691 self.out.write_str("''")?;
692 start = index + 1;
693 }
694 if start < s.len() {
695 self.out.write_str(&s[start..])?;
696 }
697 self.out.write_char('\'')?;
698 Ok(())
699 }
700
701 #[cfg(feature = "comments")]
707 #[inline]
708 fn write_pending_inline_comment(&mut self) -> Result<()> {
709 if self.state.in_flow == 0
710 && let Some(c) = self.state.pending_inline_comment.take()
711 {
712 self.out.write_str(" # ")?;
713 self.out.write_str(&c)?;
714 }
715 Ok(())
716 }
717
718 #[inline]
721 fn write_end_of_scalar(&mut self) -> Result<()> {
722 if self.state.in_flow == 0 {
723 #[cfg(feature = "comments")]
724 self.write_pending_inline_comment()?;
725 self.newline()?;
726 }
727 Ok(())
728 }
729
730 #[cfg(feature = "comments")]
734 #[inline]
735 fn sanitize_comment_text(comment: &str) -> String {
736 comment
737 .chars()
738 .map(|ch| match ch {
739 '\t' => ch,
740 '\n' | '\r' | '\u{85}' | '\u{2028}' | '\u{2029}' => ' ',
741 c if is_controll_which_needs_escaping(c) => ' ',
742 _ => ch,
745 })
746 .collect()
747 }
748
749 #[cfg(feature = "comments")]
750 fn write_above_comment(&mut self, comment: &str) -> Result<Option<usize>> {
751 if self.state.in_flow > 0 || comment.is_empty() {
752 return Ok(None);
753 }
754
755 let target_depth = if self.state.pending_layout.pending_space_after_colon {
756 let base = self.state.current_map_depth.unwrap_or(self.state.depth);
757 self.state.pending_layout.pending_space_after_colon = false;
758 if !self.state.at_line_start {
759 self.newline()?;
760 }
761 checked_depth_add(base, 1)?
762 } else if !self.state.at_line_start {
763 let base = self.state.after_dash_depth.unwrap_or(self.state.depth);
764 self.state.pending_layout.pending_inline_map = false;
765 self.newline()?;
766 checked_depth_add(base, 1)?
767 } else {
768 self.state.depth
769 };
770
771 self.write_indent(target_depth)?;
772 self.out.write_str("# ")?;
773 self.out.write_str(comment)?;
774 self.newline()?;
775 Ok(Some(target_depth))
776 }
777
778 #[inline]
781 fn alloc_anchor_for(&mut self, ptr: usize) -> Result<(AnchorId, bool)> {
782 match self.anchors.by_ptr.entry(ptr) {
783 std::collections::hash_map::Entry::Occupied(e) => Ok((*e.get(), false)),
784 std::collections::hash_map::Entry::Vacant(v) => {
785 let id = self.anchors.next_id;
786 let custom_name = if let Some(generator) = self.anchors.generator {
787 let name = generator(id as usize);
788 if !is_supported_anchor_name(&name) {
789 return Err(Error::InvalidOptions(format!(
790 "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 '[', ']', '{{', '}}', ','"
791 )));
792 }
793 Some(name)
794 } else {
795 None
796 };
797
798 self.anchors.next_id = self.anchors.next_id.saturating_add(1);
799 if let Some(name) = custom_name {
800 self.anchors
801 .custom_names
802 .get_or_insert_with(Vec::new)
803 .push(name);
804 }
805 v.insert(id);
806 Ok((id, true))
807 }
808 }
809 }
810
811 #[inline]
813 fn write_anchor_name(&mut self, id: AnchorId) -> Result<()> {
814 if let Some(names) = &self.anchors.custom_names {
815 let idx = id as usize - 1;
817 if let Some(name) = names.get(idx) {
818 self.out.write_str(name)?;
819 } else {
820 write!(self.out, "a{id}")?;
822 }
823 } else {
824 write!(self.out, "a{id}")?;
825 }
826 Ok(())
827 }
828
829 #[inline]
832 fn write_space_if_pending(&mut self) -> Result<()> {
833 if self.state.pending_layout.pending_space_after_colon {
834 self.out.write_char(' ')?;
835 self.state.pending_layout.pending_space_after_colon = false;
836 }
837 self.state.last_value_was_block = false;
840 Ok(())
841 }
842
843 #[inline]
846 fn write_indent(&mut self, depth: usize) -> Result<()> {
847 if self.state.at_line_start {
848 if !self.state.doc_started {
849 self.state.doc_started = true;
850 if self.settings.yaml_12 && !self.settings.no_lang_directive {
851 self.out.write_str("%YAML 1.2\n---\n")?;
852 self.state.at_line_start = true;
854 }
855 }
856 self.write_indent_spaces(depth)?;
857 self.state.at_line_start = false;
858 }
859 Ok(())
860 }
861
862 #[inline]
863 fn write_indent_spaces(&mut self, depth: usize) -> Result<()> {
864 let spaces = checked_indentation(self.settings.indent_step, depth)?;
865 for _ in 0..spaces {
866 self.out.write_char(' ')?;
867 }
868 Ok(())
869 }
870
871 fn variant_depth_after_dash(
873 &self,
874 dash_depth: usize,
875 anchor_broke_line: bool,
876 ) -> Result<usize> {
877 let levels = if anchor_broke_line {
881 1
882 } else {
883 2usize.div_ceil(self.settings.indent_step)
884 };
885 checked_depth_add(dash_depth, levels)
886 }
887
888 #[inline]
891 fn newline(&mut self) -> Result<()> {
892 self.out.write_char('\n')?;
893 self.state.at_line_start = true;
894 Ok(())
895 }
896
897 fn write_folded_block(&mut self, s: &str, column: usize) -> Result<()> {
900 self::wrapping::write_folded_block(self.out, s, column, 1, self.settings.folded_wrap_col)?;
901 self.state.at_line_start = true;
902 Ok(())
903 }
904
905 fn write_key_scalar(&mut self, s: &str) -> Result<()> {
911 let text = scalar_key_to_string(&s, self.settings.yaml_12)?;
912 self.out.write_str(&text)?;
913 Ok(())
914 }
915
916 fn write_quoted(&mut self, s: &str) -> Result<()> {
918 self.out.write_char('"')?;
919 escape_double_quoted(s, self.out)?;
920 self.out.write_char('"')?;
921 Ok(())
922 }
923
924 #[inline]
926 fn write_plain_or_quoted_value(&mut self, s: &str) -> Result<()> {
927 if self.settings.quote_all {
928 if Self::needs_double_quotes(s) {
930 self.write_quoted(s)
931 } else {
932 self.write_single_quoted(s)
933 }
934 } else if is_plain_value_safe(s, self.settings.yaml_12, self.state.in_flow > 0) {
935 self.out.write_str(s)?;
936 Ok(())
937 } else {
938 self.write_quoted(s)
940 }
941 }
942
943 fn stage_resolved_tag(&mut self, resolved: &str) -> Result<()> {
945 if resolved.is_empty() {
946 return Err(Error::EmptyResolvedTag);
947 }
948 let emitted = resolved_tag_token(resolved);
949 if !resolved.starts_with('!') && !absolute_uri_is_valid(&emitted[2..emitted.len() - 1]) {
950 return Err(Error::InvalidGlobalTagUri {
951 tag: resolved.to_owned(),
952 });
953 }
954 self.stage_tag_with_token(resolved, emitted)
955 }
956
957 fn serialize_tagged_value<T: ?Sized + Serialize>(
960 &mut self,
961 resolved: &str,
962 value: &T,
963 ) -> Result<()> {
964 let previous = self.state.pending_tag.clone();
965 let result = self
966 .stage_resolved_tag(resolved)
967 .and_then(|()| value.serialize(&mut *self));
968 if result.is_err() {
969 self.state.pending_tag = previous;
970 }
971 result
972 }
973
974 fn stage_tag_with_token(&mut self, resolved: &str, emitted: String) -> Result<()> {
979 match self.state.pending_tag.as_ref() {
980 Some(pending) if pending.resolved == resolved => Ok(()),
981 Some(pending) => Err(Error::custom(format_args!(
982 "cannot serialize one YAML node with both tag {:?} and tag {:?}",
983 pending.resolved, resolved
984 ))),
985 None => {
986 self.state.pending_tag = Some(PendingTag {
987 resolved: resolved.to_owned(),
988 emitted,
989 variant_selector_claimed: false,
990 });
991 Ok(())
992 }
993 }
994 }
995
996 fn stage_core_tag(&mut self, suffix: &str) -> Result<()> {
997 let mut resolved = String::with_capacity(YAML_TAG_NAMESPACE.len() + suffix.len());
998 resolved.push_str(YAML_TAG_NAMESPACE);
999 resolved.push_str(suffix);
1000 self.stage_tag_with_token(&resolved, core_tag_token(suffix))
1001 }
1002
1003 #[inline]
1009 fn pending_tag_selects_variant(&mut self, variant: &str) -> Result<bool> {
1010 let Some(tag) = self.state.pending_tag.as_mut() else {
1011 return Ok(false);
1012 };
1013 let resolved = tag.resolved.as_str();
1014
1015 if yaml_core_type_tag_name(resolved) == Some(variant) {
1016 return Err(Error::CoreTypeTagAsEnumVariant {
1017 tag: resolved.to_owned(),
1018 variant: variant.to_owned(),
1019 });
1020 }
1021
1022 if simple_enum_variant_name(resolved) != Some(variant) || tag.variant_selector_claimed {
1023 return Ok(false);
1024 }
1025
1026 tag.variant_selector_claimed = true;
1027 Ok(true)
1028 }
1029
1030 fn serialize_tagged_scalar(&mut self, enum_name: &str, variant: &str) -> Result<()> {
1033 self.stage_core_tag(enum_name)?;
1034 self.write_scalar_prefix_if_anchor()?;
1035 if self.state.at_line_start {
1036 self.write_indent(self.state.depth)?;
1037 }
1038 self.write_plain_or_quoted_value(variant)?;
1039 self.write_end_of_scalar()
1040 }
1041
1042 fn serialize_double_quoted_scalar(&mut self, value: &str) -> Result<()> {
1043 self.write_space_if_pending()?;
1044 self.write_scalar_prefix_if_anchor()?;
1045 if self.state.at_line_start {
1046 self.write_indent(self.state.depth)?;
1047 }
1048 self.write_quoted(value)?;
1049 self.write_end_of_scalar()
1050 }
1051
1052 fn serialize_single_quoted_scalar(&mut self, value: &str) -> Result<()> {
1053 if let Some(ch) = value
1054 .chars()
1055 .find(|ch| is_controll_which_needs_escaping(*ch))
1056 {
1057 return Err(Error::SingleQuotedRequiresEscaping { ch });
1058 }
1059 self.write_space_if_pending()?;
1060 self.write_scalar_prefix_if_anchor()?;
1061 if self.state.at_line_start {
1062 self.write_indent(self.state.depth)?;
1063 }
1064 self.write_single_quoted(value)?;
1065 self.write_end_of_scalar()
1066 }
1067
1068 fn serialize_tilde_null(&mut self) -> Result<()> {
1069 self.state.pending_flow = None;
1070 self.write_space_if_pending()?;
1071 self.write_scalar_prefix_if_anchor()?;
1072 if self.state.at_line_start {
1073 self.write_indent(self.state.depth)?;
1074 }
1075 self.out.write_char('~')?;
1076 self.write_end_of_scalar()
1077 }
1078
1079 #[inline]
1081 fn write_scalar_prefix_if_anchor(&mut self) -> Result<()> {
1082 let tag = self.state.pending_tag.take();
1083 let anchor_id = self.anchors.pending_id.take();
1084 if tag.is_some() || anchor_id.is_some() {
1085 if self.state.at_line_start {
1086 self.write_indent(self.state.depth)?;
1087 }
1088 if let Some(tag) = tag.as_ref() {
1089 self.out.write_str(&tag.emitted)?;
1090 self.out.write_char(' ')?;
1091 }
1092 if let Some(id) = anchor_id {
1093 self.anchors
1094 .record_resolved_tag(id, tag.as_ref().map(|tag| tag.resolved.as_str()));
1095 self.out.write_char('&')?;
1096 self.write_anchor_name(id)?;
1097 self.out.write_char(' ')?;
1098 }
1099 }
1100 Ok(())
1101 }
1102
1103 #[inline]
1105 fn write_anchor_for_complex_node(&mut self) -> Result<()> {
1106 let tag = self.state.pending_tag.take();
1107 let anchor_id = self.anchors.pending_id.take();
1108 if tag.is_some() || anchor_id.is_some() {
1109 self.write_space_if_pending()?;
1110 if self.state.at_line_start {
1111 self.write_indent(self.state.depth)?;
1112 }
1113 if let Some(tag) = tag.as_ref() {
1114 self.out.write_str(&tag.emitted)?;
1115 }
1116 if let Some(id) = anchor_id {
1117 self.anchors
1118 .record_resolved_tag(id, tag.as_ref().map(|tag| tag.resolved.as_str()));
1119 if tag.is_some() {
1120 self.out.write_char(' ')?;
1121 }
1122 self.out.write_char('&')?;
1123 self.write_anchor_name(id)?;
1124 }
1125 self.newline()?;
1126 }
1127 Ok(())
1128 }
1129
1130 #[inline]
1133 fn write_alias_id(&mut self, id: AnchorId) -> Result<()> {
1134 let anchor_tag = self.anchors.resolved_tag(id);
1135 if let Some(requested) = self.state.pending_tag.take()
1136 && anchor_tag != Some(requested.resolved.as_str())
1137 {
1138 return Err(Error::custom(format_args!(
1139 "cannot apply tag {:?} to alias whose anchor was defined with tag {:?}",
1140 requested.resolved,
1141 anchor_tag.unwrap_or("")
1142 )));
1143 }
1144 if self.state.at_line_start {
1145 self.write_indent(self.state.depth)?;
1146 }
1147 self.write_space_if_pending()?;
1148 self.out.write_char('*')?;
1149 self.write_anchor_name(id)?;
1150 self.write_end_of_scalar()?;
1152 Ok(())
1153 }
1154
1155 #[inline]
1156 fn has_pending_node_properties(&self) -> bool {
1157 self.state.pending_tag.is_some() || self.anchors.pending_id.is_some()
1158 }
1159
1160 #[inline]
1163 fn take_flow_for_seq(&mut self) -> bool {
1164 if self.state.in_flow > 0 {
1165 true
1166 } else {
1167 matches!(self.state.pending_flow.take(), Some(PendingFlow::AnySeq))
1168 }
1169 }
1170 #[inline]
1173 fn take_flow_for_map(&mut self) -> bool {
1174 if self.state.in_flow > 0 {
1175 true
1176 } else {
1177 matches!(self.state.pending_flow.take(), Some(PendingFlow::AnyMap))
1178 }
1179 }
1180
1181 #[inline]
1184 fn with_in_flow<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
1185 self.state.in_flow += 1;
1186 let r = f(self);
1187 self.state.in_flow -= 1;
1188 r
1189 }
1190}
1191
1192impl<'a, 'b, W: Write> Serializer for &'a mut YamlSerializer<'b, W> {
1197 type Ok = ();
1198 type Error = Error;
1199
1200 type SerializeSeq = SeqSer<'a, 'b, W>;
1201 type SerializeTuple = SeqSer<'a, 'b, W>;
1202 type SerializeTupleStruct = TupleSer<'a, 'b, W>;
1203 type SerializeTupleVariant = SeqSer<'a, 'b, W>;
1204 type SerializeMap = MapSer<'a, 'b, W>;
1205 type SerializeStruct = MapSer<'a, 'b, W>;
1206 type SerializeStructVariant = StructVariantSer<'a, 'b, W>;
1207
1208 fn serialize_bool(self, v: bool) -> Result<()> {
1211 self.write_space_if_pending()?;
1212 self.write_scalar_prefix_if_anchor()?;
1213 if self.state.at_line_start {
1214 self.write_indent(self.state.depth)?;
1215 }
1216 self.out.write_str(if v { "true" } else { "false" })?;
1217 self.write_end_of_scalar()?;
1218 Ok(())
1219 }
1220
1221 fn serialize_i8(self, v: i8) -> Result<()> {
1222 self.serialize_i64(i64::from(v))
1223 }
1224 fn serialize_i16(self, v: i16) -> Result<()> {
1225 self.serialize_i64(i64::from(v))
1226 }
1227 fn serialize_i32(self, v: i32) -> Result<()> {
1228 self.serialize_i64(i64::from(v))
1229 }
1230 fn serialize_i64(self, v: i64) -> Result<()> {
1231 self.write_space_if_pending()?;
1232 self.write_scalar_prefix_if_anchor()?;
1233 if self.state.at_line_start {
1234 self.write_indent(self.state.depth)?;
1235 }
1236 write!(self.out, "{v}")?;
1237 self.write_end_of_scalar()?;
1238 Ok(())
1239 }
1240
1241 fn serialize_i128(self, v: i128) -> Result<()> {
1242 self.write_space_if_pending()?;
1243 self.write_scalar_prefix_if_anchor()?;
1244 if self.state.at_line_start {
1245 self.write_indent(self.state.depth)?;
1246 }
1247 write!(self.out, "{v}")?;
1248 self.write_end_of_scalar()?;
1249 Ok(())
1250 }
1251
1252 fn serialize_u8(self, v: u8) -> Result<()> {
1253 self.serialize_u64(u64::from(v))
1254 }
1255 fn serialize_u16(self, v: u16) -> Result<()> {
1256 self.serialize_u64(u64::from(v))
1257 }
1258 fn serialize_u32(self, v: u32) -> Result<()> {
1259 self.serialize_u64(u64::from(v))
1260 }
1261 fn serialize_u64(self, v: u64) -> Result<()> {
1262 self.write_space_if_pending()?;
1263 self.write_scalar_prefix_if_anchor()?;
1264 if self.state.at_line_start {
1265 self.write_indent(self.state.depth)?;
1266 }
1267 write!(self.out, "{v}")?;
1268 self.write_end_of_scalar()?;
1269 Ok(())
1270 }
1271
1272 fn serialize_u128(self, v: u128) -> Result<()> {
1273 self.write_space_if_pending()?;
1274 self.write_scalar_prefix_if_anchor()?;
1275 if self.state.at_line_start {
1276 self.write_indent(self.state.depth)?;
1277 }
1278 write!(self.out, "{v}")?;
1279 self.write_end_of_scalar()?;
1280 Ok(())
1281 }
1282
1283 fn serialize_f32(self, v: f32) -> Result<()> {
1284 self.write_space_if_pending()?;
1285 self.write_scalar_prefix_if_anchor()?;
1286 if self.state.at_line_start {
1287 self.write_indent(self.state.depth)?;
1288 }
1289 zmij_format::write_float_string(self.out, v)?;
1290 self.write_end_of_scalar()
1291 }
1292
1293 fn serialize_f64(self, v: f64) -> Result<()> {
1294 self.write_space_if_pending()?;
1295 self.write_scalar_prefix_if_anchor()?;
1296 if self.state.at_line_start {
1297 self.write_indent(self.state.depth)?;
1298 }
1299 zmij_format::write_float_string(self.out, v)?;
1300 self.write_end_of_scalar()
1301 }
1302
1303 fn serialize_char(self, v: char) -> Result<()> {
1304 self.write_space_if_pending()?;
1305 let mut buf = [0u8; 4];
1306 self.serialize_str(v.encode_utf8(&mut buf))
1307 }
1308
1309 fn serialize_str(self, v: &str) -> Result<()> {
1310 #[inline]
1311 fn block_indent_indicator_digit(indent_n: usize) -> Result<char> {
1312 match indent_n {
1315 1..=9 => {
1316 let digit = u32::try_from(indent_n).expect("checked 1..=9");
1317 Ok(char::from_digit(digit, 10).expect("checked 1..=9"))
1318 }
1319 _ => Err(Error::custom(
1320 "indentation indicator must be a single digit (1..=9)",
1321 )),
1322 }
1323 }
1324
1325 if self.state.pending_str_style.is_none()
1336 && self.state.in_flow == 0
1337 && !self.settings.quote_all
1338 {
1339 if v.contains('\n') {
1340 if self.settings.prefer_block_scalars
1341 && is_block_scalar_content_safe(v)
1342 && is_auto_block_scalar_readable(v)
1343 {
1344 self.state.pending_str_style =
1345 Some(PendingStrStyle::Automatic(StrStyle::Literal));
1346 }
1347 } else if self.settings.prefer_block_scalars
1348 && v.len() > self.settings.folded_wrap_col
1350 {
1351 let auto_fold_probe = v.trim_end_matches(' ');
1355 let can_auto_fold = !auto_fold_probe.is_empty()
1356 && is_plain_value_safe(auto_fold_probe, self.settings.yaml_12, false);
1357 if can_auto_fold {
1358 if v.chars().count() > self.settings.folded_wrap_col {
1360 self.state.pending_str_style =
1361 Some(PendingStrStyle::Automatic(StrStyle::Folded));
1362 }
1363 }
1364 }
1365 }
1366 if let Some(pending_style) = self.state.pending_str_style.take() {
1367 let (style, from_auto) = pending_style.into_parts();
1368 if !is_block_scalar_content_safe(v) {
1369 self.write_space_if_pending()?;
1370 self.write_scalar_prefix_if_anchor()?;
1371 if self.state.at_line_start {
1372 self.write_indent(self.state.depth)?;
1373 }
1374 self.write_quoted(v)?;
1375 self.write_end_of_scalar()?;
1376 return Ok(());
1377 }
1378
1379 let was_map_value = self.state.pending_layout.pending_space_after_colon;
1385 self.write_space_if_pending()?;
1386 let base = if was_map_value {
1396 self.state.current_map_depth.unwrap_or(self.state.depth)
1397 } else {
1398 self.state.after_dash_depth.unwrap_or(self.state.depth)
1401 };
1402 if self.state.at_line_start {
1403 self.write_indent(base)?;
1404 }
1405
1406 self.write_scalar_prefix_if_anchor()?;
1410
1411 let parent_column = match (was_map_value, self.state.current_map_key_column) {
1425 (true, Some(col)) => col,
1426 _ => checked_indentation(self.settings.indent_step, base)?,
1427 };
1428 let body_column = parent_column
1429 .checked_add(self.settings.indent_step)
1430 .ok_or_else(|| Error::custom("serializer indentation exceeds usize"))?;
1431 let indent_n = self.settings.indent_step;
1432
1433 let content_trimmed = v.trim_end_matches('\n');
1436 let first_line_spaces = self::wrapping::first_line_leading_spaces(content_trimmed);
1437 let needs_indicator = first_line_spaces > 0;
1438
1439 let indicator_digit = if needs_indicator {
1443 let Ok(digit) = block_indent_indicator_digit(indent_n) else {
1444 self.write_plain_or_quoted_value(v)?;
1445 self.write_end_of_scalar()?;
1446 return Ok(());
1447 };
1448 Some(digit)
1449 } else {
1450 None
1451 };
1452
1453 match style {
1454 StrStyle::Literal => {
1455 let content = v.trim_end_matches('\n');
1461 let trailing_nl = v.len() - content.len();
1462
1463 self.out.write_char('|')?;
1465 if let Some(digit) = indicator_digit {
1466 self.out.write_char(digit)?;
1467 }
1468 match trailing_nl {
1469 0 => self.out.write_char('-')?,
1470 1 if !content.is_empty() => {}
1472 _ => self.out.write_char('+')?,
1473 }
1474 #[cfg(feature = "comments")]
1475 self.write_pending_inline_comment()?;
1476 self.newline()?;
1477
1478 let mut indent_buf: String = String::new();
1483 let spaces = body_column;
1484 if spaces > 0 {
1485 indent_buf.reserve(spaces);
1486 for _ in 0..spaces {
1487 indent_buf.push(' ');
1488 }
1489 }
1490 let indent_str = indent_buf.as_str();
1491
1492 if content.is_empty() {
1493 for _ in 0..trailing_nl {
1494 self.newline()?;
1495 }
1496 } else {
1497 for line in content.split('\n') {
1498 if !line.is_empty() {
1501 self.out.write_str(indent_str)?;
1502 self.state.at_line_start = false;
1503 self.out.write_str(line)?;
1504 }
1505 self.newline()?;
1506 }
1507 if trailing_nl >= 2 {
1508 for _ in 0..(trailing_nl - 1) {
1509 self.newline()?;
1510 }
1511 }
1512 }
1513 }
1514 StrStyle::Folded => {
1515 self.out.write_char('>')?;
1517 if let Some(digit) = indicator_digit {
1518 self.out.write_char(digit)?;
1519 }
1520 if from_auto {
1521 let content = v.trim_end_matches('\n');
1524 let trailing_nl = v.len() - content.len();
1525 match trailing_nl {
1526 0 => self.out.write_char('-')?,
1527 1 => {} _ => self.out.write_char('+')?,
1529 }
1530 }
1531 #[cfg(feature = "comments")]
1534 self.write_pending_inline_comment()?;
1535 self.newline()?;
1536 self.write_folded_block(v, body_column)?;
1537 }
1538 }
1539 return Ok(());
1540 }
1541 self.write_space_if_pending()?;
1542 self.write_scalar_prefix_if_anchor()?;
1543 if self.state.at_line_start {
1544 self.write_indent(self.state.depth)?;
1545 }
1546 if v.len() == 1
1549 && let Some(ch) = v.chars().next()
1550 && (ch == '.' || ch == '#' || ch == '-')
1551 {
1552 self.out.write_char('\'')?;
1553 self.out.write_char(ch)?;
1554 self.out.write_char('\'')?;
1555 self.write_end_of_scalar()?;
1556 return Ok(());
1557 }
1558 self.write_plain_or_quoted_value(v)?;
1559 self.write_end_of_scalar()?;
1560 Ok(())
1561 }
1562
1563 fn serialize_bytes(self, v: &[u8]) -> Result<()> {
1564 if self.state.at_line_start && self.state.pending_tag.is_none() {
1578 let mut seq = self.serialize_seq(Some(v.len()))?;
1580 for b in v {
1581 serde_core::ser::SerializeSeq::serialize_element(&mut seq, b)?;
1582 }
1583 return serde_core::ser::SerializeSeq::end(seq);
1584 }
1585
1586 self.write_space_if_pending()?;
1589 self.stage_core_tag("binary")?;
1590 self.write_scalar_prefix_if_anchor()?;
1591 let mut s = String::new();
1593 B64.encode_string(v, &mut s);
1594 self.out.write_str(&s)?;
1595 self.write_end_of_scalar()?;
1596 Ok(())
1597 }
1598
1599 fn serialize_none(self) -> Result<()> {
1600 self.write_space_if_pending()?;
1601 self.write_scalar_prefix_if_anchor()?;
1602 self.state.last_value_was_block = false;
1603 if self.state.at_line_start {
1604 self.write_indent(self.state.depth)?;
1605 }
1606 self.out.write_str("null")?;
1607 self.write_end_of_scalar()?;
1608 Ok(())
1609 }
1610
1611 fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
1612 value.serialize(self)
1613 }
1614
1615 fn serialize_unit(self) -> Result<()> {
1616 self.write_space_if_pending()?;
1617 self.write_scalar_prefix_if_anchor()?;
1618 self.state.last_value_was_block = false;
1619 if self.state.at_line_start {
1620 self.write_indent(self.state.depth)?;
1621 }
1622 self.out.write_str("null")?;
1623 self.write_end_of_scalar()?;
1624 Ok(())
1625 }
1626
1627 fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
1628 self.serialize_unit()
1629 }
1630
1631 fn serialize_unit_variant(
1632 self,
1633 name: &'static str,
1634 _variant_index: u32,
1635 variant: &'static str,
1636 ) -> Result<()> {
1637 self.write_space_if_pending()?;
1639 if self.settings.tagged_enums {
1640 self.serialize_tagged_scalar(name, variant)
1641 } else {
1642 self.serialize_str(variant)
1643 }
1644 }
1645
1646 fn serialize_newtype_struct<T: ?Sized + Serialize>(
1647 self,
1648 name: &'static str,
1649 value: &T,
1650 ) -> Result<()> {
1651 match name {
1653 NAME_FLOW_SEQ => {
1654 self.state.pending_flow = Some(PendingFlow::AnySeq);
1655 return value.serialize(self);
1656 }
1657 NAME_FLOW_MAP => {
1658 self.state.pending_flow = Some(PendingFlow::AnyMap);
1659 return value.serialize(self);
1660 }
1661 NAME_LIT_STR => {
1662 let mut cap = StrCapture::default();
1666 value.serialize(&mut cap)?;
1667 let s = cap.finish()?;
1668 self.state.pending_str_style = Some(PendingStrStyle::Explicit(StrStyle::Literal));
1669 return self.serialize_str(&s);
1670 }
1671 NAME_FOLD_STR => {
1672 let mut cap = StrCapture::default();
1673 value.serialize(&mut cap)?;
1674 let s = cap.finish()?;
1675 let is_multiline = s.contains('\n');
1676 if !is_multiline && s.len() < self.settings.min_fold_chars {
1677 return self.serialize_str(&s);
1678 }
1679 self.state.pending_str_style = Some(PendingStrStyle::Explicit(StrStyle::Folded));
1680 return self.serialize_str(&s);
1681 }
1682 NAME_SPACE_AFTER => {
1683 let result = value.serialize(&mut *self);
1685 if self.state.in_flow == 0 {
1686 self.newline()?;
1688 }
1689 return result;
1690 }
1691 NAME_DOUBLE_QUOTED => {
1692 let mut cap = StrCapture::default();
1693 value.serialize(&mut cap)?;
1694 let text = cap.finish()?;
1695 return self.serialize_double_quoted_scalar(&text);
1696 }
1697 NAME_SINGLE_QUOTED => {
1698 let mut cap = StrCapture::default();
1699 value.serialize(&mut cap)?;
1700 let text = cap.finish()?;
1701 return self.serialize_single_quoted_scalar(&text);
1702 }
1703 NAME_NULLABLE_TILDE => {
1704 return self.serialize_tilde_null();
1705 }
1706 _ => {}
1707 }
1708 value.serialize(self)
1710 }
1711
1712 fn serialize_newtype_variant<T: ?Sized + Serialize>(
1713 self,
1714 _name: &'static str,
1715 _variant_index: u32,
1716 variant: &'static str,
1717 value: &T,
1718 ) -> Result<()> {
1719 if self.pending_tag_selects_variant(variant)? {
1723 return value.serialize(self);
1724 }
1725
1726 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
1727 let anchor_broke_line = self.has_pending_node_properties();
1728 let after_dash_depth = self.state.after_dash_depth;
1729 self.write_anchor_for_complex_node()?;
1730
1731 if was_inline_value {
1736 self.state.pending_layout.pending_space_after_colon = false;
1738 if !self.state.at_line_start {
1739 self.newline()?;
1740 }
1741 let base = self.state.current_map_depth.unwrap_or(self.state.depth);
1744 let variant_depth = checked_depth_add(base, 1)?;
1745 self.write_indent(variant_depth)?;
1746 self.write_key_scalar(variant)?;
1747 self.out.write_str(":")?;
1750 self.state.pending_layout.pending_space_after_colon = true;
1751 self.state.at_line_start = false;
1752 self.state.pending_layout.pending_inline_map = false;
1755 let prev_map_depth = self.state.current_map_depth.replace(variant_depth);
1758 let prev_key_column = self.state.current_map_key_column.take();
1761 let res = value.serialize(&mut *self);
1762 self.state.current_map_depth = prev_map_depth;
1763 self.state.current_map_key_column = prev_key_column;
1764 return res;
1765 }
1766 if self.state.at_line_start {
1768 let depth = if anchor_broke_line {
1769 match after_dash_depth {
1770 Some(depth) => checked_depth_add(depth, 1)?,
1771 None => self.state.depth,
1772 }
1773 } else {
1774 self.state.depth
1775 };
1776 self.write_indent(depth)?;
1777 }
1778 self.write_key_scalar(variant)?;
1779 self.out.write_str(":")?;
1781 self.state.pending_layout.pending_space_after_colon = true;
1782 self.state.at_line_start = false;
1783 self.state.pending_layout.pending_inline_map = false;
1786 if let Some(d) = self.state.after_dash_depth.take() {
1790 let nested_depth = self.variant_depth_after_dash(d, anchor_broke_line)?;
1791 let prev_map_depth = self.state.current_map_depth.replace(nested_depth);
1792 let key_column = if anchor_broke_line {
1795 None
1796 } else {
1797 Some(checked_indentation(self.settings.indent_step, d)? + 2)
1798 };
1799 let prev_key_column =
1800 std::mem::replace(&mut self.state.current_map_key_column, key_column);
1801 let res = value.serialize(&mut *self);
1802 self.state.current_map_depth = prev_map_depth;
1803 self.state.current_map_key_column = prev_key_column;
1804 res
1805 } else {
1806 value.serialize(&mut *self)
1807 }
1808 }
1809
1810 fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
1813 let flow = self.take_flow_for_seq();
1814 if flow {
1815 self.write_space_if_pending()?;
1817 self.write_scalar_prefix_if_anchor()?;
1818 if self.state.at_line_start {
1819 self.write_indent(self.state.depth)?;
1820 }
1821 self.out.write_str("[")?;
1822 self.state.at_line_start = false;
1823 let depth_next = self.state.depth; Ok(SeqSer {
1825 ser: self,
1826 depth: depth_next,
1827 flow: true,
1828 first: true,
1829 force_empty_marker: false,
1830 })
1831 } else {
1832 let was_inline_value = !self.state.at_line_start;
1834
1835 if self.state.pending_layout.pending_space_after_colon
1839 && self.state.last_value_was_block
1840 && !self.has_pending_node_properties()
1841 {
1842 self.state.pending_layout.pending_space_after_colon = false;
1843 if !self.state.at_line_start {
1844 self.newline()?;
1845 }
1846 self.state.last_value_was_block = false;
1848 }
1849
1850 let after_dash = (!self.state.at_line_start)
1861 && self.state.after_dash_depth.is_some()
1862 && !self.state.pending_layout.pending_space_after_colon;
1863 let inline_first = after_dash && self.settings.indent_step == 2;
1864 let anchor_broke_line = self.has_pending_node_properties();
1866 if after_dash && !inline_first {
1868 self.state.pending_layout.pending_inline_map = false;
1869 if !anchor_broke_line {
1870 self.newline()?;
1871 }
1872 }
1873 self.write_anchor_for_complex_node()?;
1874 if inline_first {
1875 if anchor_broke_line {
1876 self.state.pending_layout.pending_inline_map = false;
1878 } else {
1879 self.state.at_line_start = false;
1881 }
1882 } else if was_inline_value {
1883 }
1888 let base = if after_dash {
1893 self.state.after_dash_depth.unwrap_or(self.state.depth)
1894 } else if was_inline_value && self.state.current_map_depth.is_some() {
1895 self.state.current_map_depth.unwrap_or(self.state.depth)
1896 } else {
1897 self.state.depth
1898 };
1899 let depth_next = if after_dash {
1903 checked_depth_add(base, 1)?
1904 } else if was_inline_value {
1905 if self.settings.compact_list_indent
1910 && self.state.current_map_depth.is_some()
1911 && !anchor_broke_line
1912 && self.settings.indent_step >= 2
1913 {
1914 base
1915 } else {
1916 checked_depth_add(base, 1)?
1917 }
1918 } else {
1919 base
1920 };
1921 #[cfg(feature = "comments")]
1923 {
1924 self.state.pending_inline_comment = None;
1925 }
1926 Ok(SeqSer {
1927 ser: self,
1928 depth: depth_next,
1929 flow: false,
1930 first: true,
1931 force_empty_marker: anchor_broke_line,
1932 })
1933 }
1934 }
1935
1936 fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
1937 self.serialize_seq(Some(len))
1938 }
1939
1940 fn serialize_tuple_struct(
1941 self,
1942 name: &'static str,
1943 len: usize,
1944 ) -> Result<Self::SerializeTupleStruct> {
1945 match name {
1946 NAME_TUPLE_ANCHOR => Ok(TupleSer::anchor_strong(self)),
1947 NAME_TUPLE_WEAK => Ok(TupleSer::anchor_weak(self)),
1948 #[cfg(feature = "comments")]
1949 NAME_TUPLE_COMMENTED => Ok(TupleSer::commented(self)),
1950 NAME_TUPLE_TAGGED => Ok(TupleSer::tagged(self)),
1951 _ => Ok(TupleSer::sequence(self.serialize_seq(Some(len))?)),
1953 }
1954 }
1955
1956 fn serialize_tuple_variant(
1957 self,
1958 _name: &'static str,
1959 _variant_index: u32,
1960 variant: &'static str,
1961 len: usize,
1962 ) -> Result<Self::SerializeTupleVariant> {
1963 if self.pending_tag_selects_variant(variant)? {
1964 return self.serialize_seq(Some(len));
1965 }
1966
1967 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
1968 let anchor_broke_line = self.has_pending_node_properties();
1969 let after_dash_depth = self.state.after_dash_depth;
1970 self.write_anchor_for_complex_node()?;
1971
1972 if was_inline_value {
1976 self.state.pending_layout.pending_space_after_colon = false;
1977 if !self.state.at_line_start {
1978 self.newline()?;
1979 }
1980 let base =
1981 checked_depth_add(self.state.current_map_depth.unwrap_or(self.state.depth), 1)?;
1982 self.write_indent(base)?;
1983 self.write_key_scalar(variant)?;
1984 self.out.write_str(":\n")?;
1985 self.state.at_line_start = true;
1986 self.state.pending_layout.pending_inline_map = false;
1987 let depth_next = checked_depth_add(base, 1)?;
1988 return Ok(SeqSer {
1989 ser: self,
1990 depth: depth_next,
1991 flow: false,
1992 first: true,
1993 force_empty_marker: false,
1994 });
1995 }
1996 if self.state.at_line_start {
1998 let depth = if anchor_broke_line {
1999 match after_dash_depth {
2000 Some(depth) => checked_depth_add(depth, 1)?,
2001 None => self.state.depth,
2002 }
2003 } else {
2004 self.state.depth
2005 };
2006 self.write_indent(depth)?;
2007 }
2008 self.write_key_scalar(variant)?;
2009 self.out.write_str(":\n")?;
2010 self.state.at_line_start = true;
2011 let mut depth_next = checked_depth_add(self.state.depth, 1)?;
2012 if let Some(d) = self.state.after_dash_depth.take() {
2013 depth_next =
2014 checked_depth_add(self.variant_depth_after_dash(d, anchor_broke_line)?, 1)?;
2015 self.state.pending_layout.pending_inline_map = false;
2016 }
2017 Ok(SeqSer {
2018 ser: self,
2019 depth: depth_next,
2020 flow: false,
2021 first: true,
2022 force_empty_marker: false,
2023 })
2024 }
2025
2026 fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
2027 let flow = self.take_flow_for_map();
2028 if flow {
2029 self.write_space_if_pending()?;
2031 self.write_scalar_prefix_if_anchor()?;
2032 if self.state.at_line_start {
2033 self.write_indent(self.state.depth)?;
2034 }
2035 self.out.write_str("{")?;
2036 self.state.at_line_start = false;
2037 let depth_next = self.state.depth;
2038 Ok(MapSer::flow(self, depth_next))
2039 } else {
2040 let node_properties_broke_line = self.has_pending_node_properties();
2041 if self.state.pending_layout.pending_inline_map
2046 && self.settings.indent_step < 2
2047 && !self.state.at_line_start
2048 && self.state.after_dash_depth.is_some()
2049 && !self.state.pending_layout.pending_space_after_colon
2050 {
2051 self.state.pending_layout.pending_inline_map = false;
2052 self.newline()?;
2053 if let Some(d) = self.state.after_dash_depth {
2054 self.state.depth = checked_depth_add(d, 1)?;
2055 }
2056 }
2057 let inline_first = self.state.pending_layout.pending_inline_map;
2058 #[cfg(feature = "comments")]
2060 {
2061 self.state.pending_inline_comment = None;
2062 }
2063 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
2065 let mut forced_newline = false;
2066
2067 if was_inline_value
2071 && self.state.last_value_was_block
2072 && !self.has_pending_node_properties()
2073 {
2074 self.state.pending_layout.pending_space_after_colon = false;
2075 if !self.state.at_line_start {
2076 self.newline()?;
2077 }
2078 forced_newline = true;
2079 self.state.last_value_was_block = false;
2081 }
2082
2083 self.write_anchor_for_complex_node()?;
2084 if inline_first {
2085 self.state.pending_layout.pending_inline_map = false;
2087 self.state.pending_layout.inline_map_after_dash = true;
2089 } else if was_inline_value {
2090 let known_empty = matches!(len, Some(0));
2098 let known_non_empty = matches!(len, Some(n) if n > 0);
2099
2100 if !self.settings.empty_as_braces || known_non_empty {
2101 self.state.pending_layout.pending_space_after_colon = false;
2104 if !self.state.at_line_start {
2105 self.newline()?;
2106 }
2107 } else if !known_empty {
2108 }
2111 }
2112 let base = if inline_first {
2119 self.state.after_dash_depth.unwrap_or(self.state.depth)
2120 } else if was_inline_value && self.state.current_map_depth.is_some() {
2121 self.state.current_map_depth.unwrap_or(self.state.depth)
2122 } else {
2123 self.state.depth
2124 };
2125 let depth_next = if inline_first || was_inline_value {
2126 checked_depth_add(base, 1)?
2127 } else {
2128 base
2129 };
2130 let inline_value_start_flag = was_inline_value
2131 && self.settings.empty_as_braces
2132 && len.is_none()
2133 && !inline_first
2134 && !forced_newline;
2135 Ok(MapSer::block(
2136 self,
2137 depth_next,
2138 inline_first,
2139 inline_value_start_flag,
2140 node_properties_broke_line,
2141 ))
2142 }
2143 }
2144
2145 fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
2146 self.serialize_map(Some(len))
2147 }
2148
2149 fn serialize_struct_variant(
2150 self,
2151 _name: &'static str,
2152 _variant_index: u32,
2153 variant: &'static str,
2154 len: usize,
2155 ) -> Result<Self::SerializeStructVariant> {
2156 if self.pending_tag_selects_variant(variant)? {
2157 return Ok(StructVariantSer::payload(self.serialize_map(Some(len))?));
2158 }
2159
2160 let was_inline_value = self.state.pending_layout.pending_space_after_colon;
2161 let anchor_broke_line = self.has_pending_node_properties();
2162 let after_dash_depth = self.state.after_dash_depth;
2163 self.write_anchor_for_complex_node()?;
2164
2165 if was_inline_value {
2169 self.state.pending_layout.pending_space_after_colon = false;
2171 if !self.state.at_line_start {
2172 self.newline()?;
2173 }
2174 let base =
2176 checked_depth_add(self.state.current_map_depth.unwrap_or(self.state.depth), 1)?;
2177 self.write_indent(base)?;
2178 self.write_key_scalar(variant)?;
2179 self.out.write_str(":\n")?;
2180 self.state.at_line_start = true;
2181 self.state.pending_layout.pending_inline_map = false;
2183 let depth_next = checked_depth_add(base, 1)?;
2185 return Ok(StructVariantSer::external(self, depth_next));
2186 }
2187 if self.state.at_line_start {
2189 let depth = if anchor_broke_line {
2190 match after_dash_depth {
2191 Some(depth) => checked_depth_add(depth, 1)?,
2192 None => self.state.depth,
2193 }
2194 } else {
2195 self.state.depth
2196 };
2197 self.write_indent(depth)?;
2198 }
2199 self.write_key_scalar(variant)?;
2200 self.out.write_str(":\n")?;
2201 self.state.at_line_start = true;
2202 let mut depth_next = checked_depth_add(self.state.depth, 1)?;
2204 if let Some(d) = self.state.after_dash_depth.take() {
2206 depth_next =
2207 checked_depth_add(self.variant_depth_after_dash(d, anchor_broke_line)?, 1)?;
2208 self.state.pending_layout.pending_inline_map = false;
2209 }
2210 Ok(StructVariantSer::external(self, depth_next))
2211 }
2212}
2213
2214#[cfg(test)]
2215mod tests {
2216 use super::{YamlSerializer, absolute_uri_is_valid};
2217
2218 #[test]
2219 fn absolute_uri_validation_covers_global_tag_syntax() {
2220 for valid in [
2221 "tag:yaml.org,2002:str",
2222 "urn:isbn:9780141036144",
2223 "https://example.com/path?query=yes#section",
2224 "https://example.com",
2225 "git+ssh://user@example.com:22/repository",
2226 "custom:",
2227 "http://[2001:db8::1]:8080/path",
2228 "http://[v1.alpha:beta]/path",
2229 "tag:escaped%20text",
2230 ] {
2231 assert!(absolute_uri_is_valid(valid), "expected valid URI: {valid}");
2232 }
2233
2234 for invalid in [
2235 "",
2236 "$:?",
2237 "relative/path",
2238 "1scheme:value",
2239 "bad scheme:value",
2240 "http://[not-ip]/",
2241 "http://[v1]/",
2242 "http://[2001:db8::1/",
2243 "http://example.com:port/",
2244 "http://host:name:80/",
2245 "http://bad[info@example.com/",
2246 "http://first@second@third/",
2247 "http://example.com/path[0]",
2248 "tag:value?bad[query]",
2249 "tag:value#bad[fragment]",
2250 "tag:bad%escape",
2251 ] {
2252 assert!(
2253 !absolute_uri_is_valid(invalid),
2254 "expected invalid URI: {invalid}"
2255 );
2256 }
2257 }
2258
2259 #[test]
2260 fn write_indent_rejects_indentation_overflow() {
2261 let mut out = String::new();
2262 let mut serializer = YamlSerializer::new(&mut out);
2263 let err = serializer.write_indent(usize::MAX).unwrap_err();
2264 assert_eq!(err.to_string(), "serializer indentation exceeds usize");
2265 assert!(out.is_empty());
2266 }
2267}