use crate::prelude::*;
use core::fmt::Write as _;
use serde::ser::{self, Serialize};
use crate::error::{Error, Result};
use crate::value::{Mapping, Number, Sequence, Tag, TaggedValue, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum FlowStyle {
#[default]
Block,
Flow,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ScalarStyle {
#[default]
Auto,
DoubleQuoted,
SingleQuoted,
Literal,
Folded,
Plain,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct SerializerConfig {
pub indent: usize,
pub document_start: bool,
pub document_end: bool,
pub block_scalars: bool,
pub block_scalar_threshold: usize,
pub flow_style: FlowStyle,
pub scalar_style: ScalarStyle,
pub flow_threshold: usize,
pub quote_all: bool,
pub compact_list_indent: bool,
pub folded_wrap_chars: usize,
pub min_fold_chars: usize,
pub max_depth: usize,
}
impl Default for SerializerConfig {
fn default() -> Self {
Self {
indent: 2,
document_start: false,
document_end: false,
block_scalars: true,
block_scalar_threshold: 1,
flow_style: FlowStyle::Block,
scalar_style: ScalarStyle::Auto,
flow_threshold: 4,
quote_all: false,
compact_list_indent: false,
folded_wrap_chars: 80,
min_fold_chars: 80,
max_depth: 128,
}
}
}
impl SerializerConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn indent(mut self, spaces: usize) -> Self {
self.indent = spaces;
self
}
#[must_use]
pub fn document_start(mut self, enabled: bool) -> Self {
self.document_start = enabled;
self
}
#[must_use]
pub fn document_end(mut self, enabled: bool) -> Self {
self.document_end = enabled;
self
}
#[must_use]
pub fn block_scalars(mut self, enabled: bool) -> Self {
self.block_scalars = enabled;
self
}
#[must_use]
pub fn block_scalar_threshold(mut self, count: usize) -> Self {
self.block_scalar_threshold = count;
self
}
#[must_use]
pub fn flow_style(mut self, style: FlowStyle) -> Self {
self.flow_style = style;
self
}
#[must_use]
pub fn scalar_style(mut self, style: ScalarStyle) -> Self {
self.scalar_style = style;
self
}
#[must_use]
pub fn flow_threshold(mut self, threshold: usize) -> Self {
self.flow_threshold = threshold;
self
}
#[must_use]
pub fn quote_all(mut self, enabled: bool) -> Self {
self.quote_all = enabled;
self
}
#[must_use]
pub fn compact_list_indent(mut self, enabled: bool) -> Self {
self.compact_list_indent = enabled;
self
}
#[must_use]
pub fn folded_wrap_chars(mut self, chars: usize) -> Self {
self.folded_wrap_chars = chars;
self
}
#[must_use]
pub fn min_fold_chars(mut self, chars: usize) -> Self {
self.min_fold_chars = chars;
self
}
#[must_use]
pub fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
}
pub fn to_string<T>(value: &T) -> Result<String>
where
T: ?Sized + Serialize,
{
let v = to_value(value)?;
value_to_string(&v, &SerializerConfig::default())
}
pub fn to_string_with_config<T>(value: &T, config: &SerializerConfig) -> Result<String>
where
T: ?Sized + Serialize,
{
let v = to_value(value)?;
value_to_string(&v, config)
}
#[cfg(feature = "std")]
pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
where
W: std::io::Write,
T: ?Sized + Serialize,
{
to_writer_with_config(writer, value, &SerializerConfig::default())
}
#[cfg(feature = "std")]
pub fn to_writer_with_config<W, T>(writer: W, value: &T, config: &SerializerConfig) -> Result<()>
where
W: std::io::Write,
T: ?Sized + Serialize,
{
let s = to_string_with_config(value, config)?;
let mut writer = writer;
writer.write_all(s.as_bytes())?;
Ok(())
}
#[cfg(feature = "std")]
pub fn to_string_tracking_shared<T>(value: &T) -> Result<String>
where
T: ?Sized + Serialize,
{
to_string_tracking_shared_with_config(value, &SerializerConfig::default())
}
#[cfg(feature = "std")]
pub fn to_string_tracking_shared_with_config<T>(
value: &T,
config: &SerializerConfig,
) -> Result<String>
where
T: ?Sized + Serialize,
{
let _scope = crate::anchors::shared_tracking::AnchorScope::enter();
to_string_with_config(value, config)
}
#[cfg(feature = "std")]
pub fn to_writer_tracking_shared<W, T>(writer: W, value: &T) -> Result<()>
where
W: std::io::Write,
T: ?Sized + Serialize,
{
to_writer_tracking_shared_with_config(writer, value, &SerializerConfig::default())
}
#[cfg(feature = "std")]
pub fn to_writer_tracking_shared_with_config<W, T>(
writer: W,
value: &T,
config: &SerializerConfig,
) -> Result<()>
where
W: std::io::Write,
T: ?Sized + Serialize,
{
let s = to_string_tracking_shared_with_config(value, config)?;
let mut writer = writer;
writer.write_all(s.as_bytes())?;
Ok(())
}
pub fn to_fmt_writer<W, T>(writer: &mut W, value: &T) -> Result<()>
where
W: fmt::Write,
T: ?Sized + Serialize,
{
to_fmt_writer_with_config(writer, value, &SerializerConfig::default())
}
pub fn to_fmt_writer_with_config<W, T>(
writer: &mut W,
value: &T,
config: &SerializerConfig,
) -> Result<()>
where
W: fmt::Write,
T: ?Sized + Serialize,
{
let s = to_string_with_config(value, config)?;
writer
.write_str(&s)
.map_err(|e| Error::Serialize(e.to_string()))
}
pub fn to_value<T>(value: &T) -> Result<Value>
where
T: ?Sized + Serialize,
{
value.serialize(Serializer)
}
pub fn to_string_value(value: &Value) -> Result<String> {
value_to_string(value, &SerializerConfig::default())
}
pub fn to_string_value_with_config(value: &Value, config: &SerializerConfig) -> Result<String> {
value_to_string(value, config)
}
#[cfg(feature = "std")]
pub fn to_writer_value<W>(writer: W, value: &Value) -> Result<()>
where
W: std::io::Write,
{
to_writer_value_with_config(writer, value, &SerializerConfig::default())
}
#[cfg(feature = "std")]
pub fn to_writer_value_with_config<W>(
writer: W,
value: &Value,
config: &SerializerConfig,
) -> Result<()>
where
W: std::io::Write,
{
let s = to_string_value_with_config(value, config)?;
let mut writer = writer;
writer.write_all(s.as_bytes())?;
Ok(())
}
fn value_to_string(value: &Value, config: &SerializerConfig) -> Result<String> {
let mut output = String::with_capacity(estimate_yaml_size(value));
if config.document_start {
output.push_str("---\n");
}
write_value(&mut output, value, 0, true, config, 0)?;
if config.document_end {
output.push_str("\n...");
}
Ok(output)
}
fn estimate_yaml_size(value: &Value) -> usize {
match value {
Value::Null => 4,
Value::Bool(_) => 5,
Value::Number(_) => 12,
Value::String(s) => s.len() + 4,
Value::Sequence(seq) => 4 + seq.iter().map(|v| estimate_yaml_size(v) + 4).sum::<usize>(),
Value::Mapping(map) => {
4 + map
.iter()
.map(|(k, v)| k.len() + estimate_yaml_size(v) + 6)
.sum::<usize>()
}
Value::Tagged(t) => 20 + estimate_yaml_size(t.value()),
}
}
#[inline]
fn write_indent(output: &mut String, total_spaces: usize) {
const SPACES: &str = " ";
let mut remaining = total_spaces;
while remaining > 0 {
let n = remaining.min(SPACES.len());
output.push_str(&SPACES[..n]);
remaining -= n;
}
}
fn write_value(
output: &mut String,
value: &Value,
indent: usize,
is_root: bool,
config: &SerializerConfig,
depth: usize,
) -> Result<()> {
if depth > config.max_depth {
return Err(Error::RecursionLimitExceeded { depth });
}
match value {
Value::Null => output.push_str("null"),
Value::Bool(b) => output.push_str(if *b { "true" } else { "false" }),
Value::Number(Number::Integer(n)) => {
#[cfg(feature = "fast-int")]
{
let mut buf = itoa::Buffer::new();
output.push_str(buf.format(*n));
}
#[cfg(not(feature = "fast-int"))]
{
let _ = write!(output, "{n}");
}
}
Value::Number(Number::Float(n)) => {
if n.is_nan() {
output.push_str(".nan");
} else if n.is_infinite() {
if *n > 0.0 {
output.push_str(".inf");
} else {
output.push_str("-.inf");
}
} else {
#[cfg(feature = "fast-float")]
{
let mut buf = ryu::Buffer::new();
output.push_str(buf.format(*n));
}
#[cfg(not(feature = "fast-float"))]
{
let _ = write!(output, "{n:?}");
}
}
}
Value::String(s) => write_string(output, s, indent, config),
Value::Sequence(seq) => write_sequence(output, seq, indent, is_root, config, depth)?,
Value::Mapping(map) => write_mapping(output, map, indent, is_root, config, depth)?,
Value::Tagged(tagged) => {
let tag_str = tagged.tag().as_str();
if tag_str.starts_with("__noya_") {
write_internal_tag(
output,
tag_str,
tagged.value(),
indent,
is_root,
config,
depth,
)?;
} else {
output.push_str(tag_str);
output.push(' ');
write_value(output, tagged.value(), indent, false, config, depth + 1)?;
}
}
}
Ok(())
}
fn looks_like_number(s: &str) -> bool {
let bytes = s.as_bytes();
if bytes.is_empty() {
return false;
}
if matches!(
s,
".inf"
| ".Inf"
| ".INF"
| "+.inf"
| "+.Inf"
| "+.INF"
| "-.inf"
| "-.Inf"
| "-.INF"
| ".nan"
| ".NaN"
| ".NAN"
) {
return true;
}
let mut i = 0;
while i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
if i >= bytes.len() {
return false;
}
let rest = &bytes[i..];
if rest[0].is_ascii_digit() {
return true;
}
if rest[0] == b'.' && rest.len() > 1 && rest[1].is_ascii_digit() {
return true;
}
false
}
static NEEDS_QUOTE_BYTE: [bool; 128] = {
let mut t = [false; 128];
let mut i = 0u8;
while i < 0x20 {
if i != b'\t' {
t[i as usize] = true;
}
i += 1;
}
t[b':' as usize] = true;
t[b'#' as usize] = true;
t[b'\n' as usize] = true;
t[b'\r' as usize] = true;
t[b'\0' as usize] = true;
t
};
static FIRST_CHAR_QUOTE: [bool; 128] = {
let mut t = [false; 128];
t[b' ' as usize] = true;
t[b'-' as usize] = true;
t[b'&' as usize] = true;
t[b'*' as usize] = true;
t[b'!' as usize] = true;
t[b'|' as usize] = true;
t[b'>' as usize] = true;
t[b'%' as usize] = true;
t[b'@' as usize] = true;
t[b'`' as usize] = true;
t[b'{' as usize] = true;
t[b'}' as usize] = true;
t[b'[' as usize] = true;
t[b']' as usize] = true;
t[b',' as usize] = true;
t[b'?' as usize] = true;
t[b'\'' as usize] = true;
t[b'"' as usize] = true;
t
};
fn write_string(output: &mut String, s: &str, indent: usize, config: &SerializerConfig) {
let bytes = s.as_bytes();
if bytes.is_empty() {
output.push_str("\"\"");
return;
}
if config.quote_all {
write_single_quoted(output, s);
return;
}
if bytes.len() <= 64
&& bytes[0].is_ascii_alphanumeric()
&& bytes[bytes.len() - 1].is_ascii_alphanumeric()
&& !config.block_scalars
|| bytes.iter().all(|&b| b != b'\n')
{
let safe = bytes.iter().all(|&b| {
b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.' || b == b'/'
});
if safe
&& !matches!(
s,
"true"
| "false"
| "null"
| "~"
| "True"
| "False"
| "TRUE"
| "FALSE"
| "Null"
| "NULL"
)
&& !looks_like_number(s)
{
output.push_str(s);
return;
}
}
if config.block_scalars {
let newlines = bytes.iter().filter(|&&b| b == b'\n').count();
if newlines >= config.block_scalar_threshold {
write_block_scalar(output, s, indent, config);
return;
}
}
let mut needs_quotes = false;
let mut has_control = false;
if bytes[0] < 128 && FIRST_CHAR_QUOTE[bytes[0] as usize] {
needs_quotes = true;
}
if bytes[bytes.len() - 1] == b' ' {
needs_quotes = true;
}
if !needs_quotes {
needs_quotes = matches!(
s,
"true" | "false" | "null" | "~" | "True" | "False" | "TRUE" | "FALSE" | "Null" | "NULL"
) || looks_like_number(s);
}
if !needs_quotes {
for &b in bytes {
if b < 128 && NEEDS_QUOTE_BYTE[b as usize] {
if b < 0x20 && b != b'\t' {
has_control = true;
}
needs_quotes = true;
}
}
}
if !needs_quotes {
output.push_str(s);
return;
}
let _ = has_control;
write_double_quoted(output, s);
}
fn write_single_quoted(output: &mut String, s: &str) {
output.push('\'');
for c in s.chars() {
if c == '\'' {
output.push_str("''");
} else {
output.push(c);
}
}
output.push('\'');
}
fn write_double_quoted(output: &mut String, s: &str) {
output.push('"');
let bytes = s.as_bytes();
let mut start = 0;
for (i, &b) in bytes.iter().enumerate() {
let esc = match b {
b'"' => "\\\"",
b'\\' => "\\\\",
b'\n' => "\\n",
b'\r' => "\\r",
b'\t' => "\\t",
b'\0' => "\\0",
c if c < 0x20 && c != b'\t' => {
output.push_str(&s[start..i]);
let _ = write!(output, "\\x{c:02X}");
start = i + 1;
continue;
}
_ => continue,
};
output.push_str(&s[start..i]);
output.push_str(esc);
start = i + 1;
}
output.push_str(&s[start..]);
output.push('"');
}
fn write_block_scalar(output: &mut String, s: &str, indent: usize, config: &SerializerConfig) {
let chomping = if s.ends_with('\n') {
if s.ends_with("\n\n") {
"+" } else {
"" }
} else {
"-" };
output.push('|');
output.push_str(chomping);
for line in s.lines() {
output.push('\n');
write_indent(output, config.indent * (indent + 1));
output.push_str(line);
}
if s.ends_with('\n') {
let trailing = s.len() - s.trim_end_matches('\n').len();
for _ in 0..trailing {
output.push('\n');
}
}
}
fn write_sequence(
output: &mut String,
seq: &Sequence,
indent: usize,
is_root: bool,
config: &SerializerConfig,
depth: usize,
) -> Result<()> {
if seq.is_empty() {
output.push_str("[]");
return Ok(());
}
for (i, value) in seq.iter().enumerate() {
if i > 0 || !is_root {
output.push('\n');
write_indent(output, config.indent * indent);
}
output.push_str("- ");
match value {
Value::Mapping(m) if !m.is_empty() => {
let mut iter = m.iter();
if let Some((k, v)) = iter.next() {
write_string(output, k, indent + 1, config);
output.push_str(": ");
if matches!(v, Value::Mapping(_) | Value::Sequence(_)) {
write_value(output, v, indent + 2, false, config, depth + 1)?;
} else {
write_value(output, v, indent + 1, false, config, depth + 1)?;
}
}
for (k, v) in iter {
output.push('\n');
write_indent(output, config.indent * (indent + 1));
write_string(output, k, indent + 1, config);
output.push_str(": ");
if matches!(v, Value::Mapping(_) | Value::Sequence(_)) {
write_value(output, v, indent + 2, false, config, depth + 1)?;
} else {
write_value(output, v, indent + 1, false, config, depth + 1)?;
}
}
}
Value::Sequence(_) => {
write_value(output, value, indent + 1, false, config, depth + 1)?;
}
_ => {
write_value(output, value, indent + 1, false, config, depth + 1)?;
}
}
}
Ok(())
}
fn needs_block_layout(v: &Value) -> bool {
match v {
Value::Mapping(m) => !m.is_empty(),
Value::Sequence(s) => !s.is_empty(),
Value::Tagged(t) if t.tag().as_str() == crate::fmt::MAGIC_ANCHOR_DEF => {
if let Value::Sequence(seq) = t.value() {
if seq.len() == 2 {
return needs_block_layout(&seq[1]);
}
}
false
}
_ => false,
}
}
fn write_mapping(
output: &mut String,
map: &Mapping,
indent: usize,
is_root: bool,
config: &SerializerConfig,
depth: usize,
) -> Result<()> {
if map.is_empty() {
output.push_str("{}");
return Ok(());
}
for (i, (key, value)) in map.iter().enumerate() {
if i > 0 || !is_root {
output.push('\n');
write_indent(output, config.indent * indent);
}
write_string(output, key, indent, config);
if needs_block_layout(value) {
output.push(':');
if matches!(
value,
Value::Tagged(t) if t.tag().as_str() == crate::fmt::MAGIC_ANCHOR_DEF
) {
output.push(' ');
}
let next_indent = if config.compact_list_indent && matches!(value, Value::Sequence(_)) {
indent
} else {
indent + 1
};
write_value(output, value, next_indent, false, config, depth + 1)?;
} else {
output.push_str(": ");
write_value(output, value, indent, false, config, depth + 1)?;
}
}
Ok(())
}
fn write_internal_tag(
output: &mut String,
tag: &str,
value: &Value,
indent: usize,
is_root: bool,
config: &SerializerConfig,
depth: usize,
) -> Result<()> {
match tag {
crate::fmt::MAGIC_FLOW_SEQ => {
if let Value::Sequence(seq) = value {
write_flow_sequence(output, seq, config, depth)?;
} else {
write_value(output, value, indent, is_root, config, depth)?;
}
}
crate::fmt::MAGIC_FLOW_MAP => {
if let Value::Mapping(map) = value {
write_flow_mapping(output, map, config, depth)?;
} else {
write_value(output, value, indent, is_root, config, depth)?;
}
}
crate::fmt::MAGIC_LIT_STR => {
if let Value::String(s) = value {
write_literal_block(output, s, indent, config);
} else {
write_value(output, value, indent, is_root, config, depth)?;
}
}
crate::fmt::MAGIC_FOLD_STR => {
if let Value::String(s) = value {
write_folded_block(output, s, indent, config);
} else {
write_value(output, value, indent, is_root, config, depth)?;
}
}
crate::fmt::MAGIC_COMMENTED => {
if let Value::Sequence(seq) = value {
if seq.len() == 2 {
write_value(output, &seq[0], indent, is_root, config, depth)?;
if let Value::String(comment) = &seq[1] {
output.push_str(" # ");
output.push_str(comment);
}
} else {
write_value(output, value, indent, is_root, config, depth)?;
}
} else {
write_value(output, value, indent, is_root, config, depth)?;
}
}
crate::fmt::MAGIC_SPACE_AFTER => {
write_value(output, value, indent, is_root, config, depth)?;
output.push('\n');
}
crate::fmt::MAGIC_ANCHOR_DEF => {
if let Value::Sequence(seq) = value {
if seq.len() == 2 {
if let Value::String(id) = &seq[0] {
let inner = &seq[1];
output.push('&');
output.push_str(id);
match inner {
Value::Mapping(m) if !m.is_empty() => {
output.push('\n');
write_indent(output, config.indent * indent);
write_mapping(output, m, indent, true, config, depth + 1)?;
}
Value::Sequence(s) if !s.is_empty() => {
output.push('\n');
write_indent(output, config.indent * indent);
write_sequence(output, s, indent, true, config, depth + 1)?;
}
_ => {
output.push(' ');
write_value(output, inner, indent, false, config, depth + 1)?;
}
}
}
}
}
}
crate::fmt::MAGIC_ANCHOR_REF => {
if let Value::String(id) = value {
output.push('*');
output.push_str(id);
}
}
_ => {
write_value(output, value, indent, is_root, config, depth)?;
}
}
Ok(())
}
fn write_flow_sequence(
output: &mut String,
seq: &Sequence,
config: &SerializerConfig,
depth: usize,
) -> Result<()> {
output.push('[');
for (i, value) in seq.iter().enumerate() {
if i > 0 {
output.push_str(", ");
}
write_value(output, value, 0, false, config, depth + 1)?;
}
output.push(']');
Ok(())
}
fn write_flow_mapping(
output: &mut String,
map: &Mapping,
config: &SerializerConfig,
depth: usize,
) -> Result<()> {
output.push('{');
for (i, (key, value)) in map.iter().enumerate() {
if i > 0 {
output.push_str(", ");
}
write_string(output, key, 0, config);
output.push_str(": ");
write_value(output, value, 0, false, config, depth + 1)?;
}
output.push('}');
Ok(())
}
fn write_literal_block(output: &mut String, s: &str, indent: usize, config: &SerializerConfig) {
let chomping = if s.ends_with('\n') {
if s.ends_with("\n\n") { "+" } else { "" }
} else {
"-"
};
output.push('|');
output.push_str(chomping);
for line in s.lines() {
output.push('\n');
write_indent(output, config.indent * (indent + 1));
output.push_str(line);
}
}
fn write_folded_block(output: &mut String, s: &str, indent: usize, config: &SerializerConfig) {
let chomping = if s.ends_with('\n') {
if s.ends_with("\n\n") { "+" } else { "" }
} else {
"-"
};
output.push('>');
output.push_str(chomping);
for line in s.lines() {
output.push('\n');
write_indent(output, config.indent * (indent + 1));
output.push_str(line);
}
}
pub fn to_string_multi<T: Serialize>(values: &[T]) -> Result<String> {
to_string_multi_with_config(values, &SerializerConfig::default())
}
pub fn to_string_multi_with_config<T: Serialize>(
values: &[T],
config: &SerializerConfig,
) -> Result<String> {
let mut output = String::new();
for (i, value) in values.iter().enumerate() {
if i > 0 {
output.push('\n');
}
output.push_str("---\n");
let v = to_value(value)?;
write_value(&mut output, &v, 0, true, config, 0)?;
output.push('\n');
}
Ok(output)
}
#[cfg(feature = "std")]
pub fn to_writer_multi<W: std::io::Write, T: Serialize>(writer: W, values: &[T]) -> Result<()> {
to_writer_multi_with_config(writer, values, &SerializerConfig::default())
}
#[cfg(feature = "std")]
pub fn to_writer_multi_with_config<W: std::io::Write, T: Serialize>(
writer: W,
values: &[T],
config: &SerializerConfig,
) -> Result<()> {
let s = to_string_multi_with_config(values, config)?;
let mut writer = writer;
writer.write_all(s.as_bytes())?;
Ok(())
}
#[derive(Debug, Copy, Clone)]
pub struct Serializer;
impl ser::Serializer for Serializer {
type Ok = Value;
type Error = Error;
type SerializeSeq = SerializeSeq;
type SerializeTuple = SerializeSeq;
type SerializeTupleStruct = SerializeSeq;
type SerializeTupleVariant = SerializeTupleVariant;
type SerializeMap = SerializeMap;
type SerializeStruct = SerializeMap;
type SerializeStructVariant = SerializeStructVariant;
fn serialize_bool(self, v: bool) -> Result<Value> {
Ok(Value::Bool(v))
}
fn serialize_i8(self, v: i8) -> Result<Value> {
self.serialize_i64(i64::from(v))
}
fn serialize_i16(self, v: i16) -> Result<Value> {
self.serialize_i64(i64::from(v))
}
fn serialize_i32(self, v: i32) -> Result<Value> {
self.serialize_i64(i64::from(v))
}
fn serialize_i64(self, v: i64) -> Result<Value> {
Ok(Value::Number(Number::Integer(v)))
}
fn serialize_u8(self, v: u8) -> Result<Value> {
self.serialize_i64(i64::from(v))
}
fn serialize_u16(self, v: u16) -> Result<Value> {
self.serialize_i64(i64::from(v))
}
fn serialize_u32(self, v: u32) -> Result<Value> {
self.serialize_i64(i64::from(v))
}
fn serialize_u64(self, v: u64) -> Result<Value> {
if v <= i64::MAX as u64 {
Ok(Value::Number(Number::Integer(v as i64)))
} else {
Err(Error::Serialize(format!(
"u64 value {v} exceeds i64::MAX and cannot be represented losslessly"
)))
}
}
fn serialize_f32(self, v: f32) -> Result<Value> {
self.serialize_f64(f64::from(v))
}
fn serialize_f64(self, v: f64) -> Result<Value> {
Ok(Value::Number(Number::Float(v)))
}
fn serialize_char(self, v: char) -> Result<Value> {
self.serialize_str(&v.to_string())
}
fn serialize_str(self, v: &str) -> Result<Value> {
Ok(Value::String(v.to_owned()))
}
fn serialize_bytes(self, v: &[u8]) -> Result<Value> {
let encoded = crate::base64::encode(v);
Ok(Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!!binary"),
Value::String(encoded),
))))
}
fn serialize_none(self) -> Result<Value> {
Ok(Value::Null)
}
fn serialize_some<T>(self, value: &T) -> Result<Value>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_unit(self) -> Result<Value> {
Ok(Value::Null)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value> {
self.serialize_unit()
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Value> {
self.serialize_str(variant)
}
fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<Value>
where
T: ?Sized + Serialize,
{
match name {
crate::fmt::MAGIC_FLOW_SEQ
| crate::fmt::MAGIC_FLOW_MAP
| crate::fmt::MAGIC_LIT_STR
| crate::fmt::MAGIC_FOLD_STR
| crate::fmt::MAGIC_SPACE_AFTER => {
let inner = value.serialize(Serializer)?;
Ok(Value::Tagged(Box::new(TaggedValue::new(
Tag::new(name),
inner,
))))
}
crate::fmt::MAGIC_COMMENTED => {
let inner = value.serialize(Serializer)?;
Ok(Value::Tagged(Box::new(TaggedValue::new(
Tag::new(name),
inner,
))))
}
crate::fmt::MAGIC_ANCHOR_DEF | crate::fmt::MAGIC_ANCHOR_REF => {
let inner = value.serialize(Serializer)?;
Ok(Value::Tagged(Box::new(TaggedValue::new(
Tag::new(name),
inner,
))))
}
_ => value.serialize(self),
}
}
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Value>
where
T: ?Sized + Serialize,
{
let mut map = Mapping::new();
let _ = map.insert(variant.to_owned(), value.serialize(Serializer)?);
Ok(Value::Mapping(map))
}
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
Ok(SerializeSeq {
vec: Vec::with_capacity(len.unwrap_or(0)),
})
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant> {
Ok(SerializeTupleVariant {
name: variant.to_owned(),
vec: Vec::with_capacity(len),
})
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
Ok(SerializeMap {
map: Mapping::new(),
key: None,
})
}
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
self.serialize_map(Some(len))
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
Ok(SerializeStructVariant {
name: variant.to_owned(),
map: Mapping::new(),
})
}
}
#[derive(Debug)]
pub struct SerializeSeq {
vec: Vec<Value>,
}
impl ser::SerializeSeq for SerializeSeq {
type Ok = Value;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
self.vec.push(value.serialize(Serializer)?);
Ok(())
}
fn end(self) -> Result<Value> {
Ok(Value::Sequence(self.vec))
}
}
impl ser::SerializeTuple for SerializeSeq {
type Ok = Value;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value> {
ser::SerializeSeq::end(self)
}
}
impl ser::SerializeTupleStruct for SerializeSeq {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value> {
ser::SerializeSeq::end(self)
}
}
#[derive(Debug)]
pub struct SerializeTupleVariant {
name: String,
vec: Vec<Value>,
}
impl ser::SerializeTupleVariant for SerializeTupleVariant {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
self.vec.push(value.serialize(Serializer)?);
Ok(())
}
fn end(self) -> Result<Value> {
let mut map = Mapping::new();
let _ = map.insert(self.name, Value::Sequence(self.vec));
Ok(Value::Mapping(map))
}
}
#[derive(Debug)]
pub struct SerializeMap {
map: Mapping,
key: Option<String>,
}
impl ser::SerializeMap for SerializeMap {
type Ok = Value;
type Error = Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let key_value = key.serialize(Serializer)?;
let key_str = match key_value {
Value::String(s) => s,
Value::Number(Number::Integer(n)) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => return Err(Error::Serialize("map key must be a string".to_string())),
};
self.key = Some(key_str);
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let key = self
.key
.take()
.ok_or_else(|| Error::Serialize("missing key".to_string()))?;
let _ = self.map.insert(key, value.serialize(Serializer)?);
Ok(())
}
fn end(self) -> Result<Value> {
Ok(Value::Mapping(self.map))
}
}
impl ser::SerializeStruct for SerializeMap {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let _ = self
.map
.insert(key.to_owned(), value.serialize(Serializer)?);
Ok(())
}
fn end(self) -> Result<Value> {
Ok(Value::Mapping(self.map))
}
}
#[derive(Debug)]
pub struct SerializeStructVariant {
name: String,
map: Mapping,
}
impl ser::SerializeStructVariant for SerializeStructVariant {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let _ = self
.map
.insert(key.to_owned(), value.serialize(Serializer)?);
Ok(())
}
fn end(self) -> Result<Value> {
let mut map = Mapping::new();
let _ = map.insert(self.name, Value::Mapping(self.map));
Ok(Value::Mapping(map))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Value;
#[test]
fn test_serialization_recursion_limit() {
let mut root = Value::Sequence(vec![Value::Null]);
for _ in 0..200 {
root = Value::Sequence(vec![root]);
}
let config = SerializerConfig::default().max_depth(128);
let result = to_string_with_config(&root, &config);
match result {
Err(Error::RecursionLimitExceeded { depth }) => assert!(depth > 128),
_ => panic!("Expected RecursionLimitExceeded error, got {:?}", result),
}
}
}