use std::fmt::Write as _;
use crate::{
dynamic::{
message::DynamicMessage,
value::{MapKey, Value},
},
field::{FieldDescriptor, Kind},
};
#[derive(Debug, Clone)]
pub struct FormatOptions {
pub(crate) pretty: bool,
pub(crate) skip_unknown_fields: bool,
pub(crate) expand_any: bool,
pub(crate) skip_default_fields: bool,
pub(crate) print_message_fields_in_index_order: bool,
}
impl Default for FormatOptions {
fn default() -> Self {
Self::new()
}
}
impl FormatOptions {
#[must_use]
pub const fn new() -> Self {
Self {
pretty: false,
skip_unknown_fields: false,
expand_any: true,
skip_default_fields: false,
print_message_fields_in_index_order: false,
}
}
#[must_use]
pub const fn pretty(mut self, yes: bool) -> Self {
self.pretty = yes;
self
}
#[must_use]
pub const fn skip_unknown_fields(mut self, yes: bool) -> Self {
self.skip_unknown_fields = yes;
self
}
#[must_use]
pub const fn expand_any(mut self, yes: bool) -> Self {
self.expand_any = yes;
self
}
#[must_use]
pub const fn skip_default_fields(mut self, yes: bool) -> Self {
self.skip_default_fields = yes;
self
}
#[must_use]
pub const fn print_message_fields_in_index_order(mut self, yes: bool) -> Self {
self.print_message_fields_in_index_order = yes;
self
}
}
pub(super) fn format_message(
msg: &DynamicMessage,
options: &FormatOptions,
out: &mut String,
indent: usize,
) {
let descriptor = msg.descriptor();
let mut fields: Vec<FieldDescriptor> = descriptor.fields().collect();
if !options.print_message_fields_in_index_order {
fields.sort_by_key(|f| f.number());
}
let mut first = true;
for field in fields {
if !msg.has_field(&field) {
continue;
}
if options.skip_default_fields {
let v = msg.get_field(&field);
let kind = field.kind();
if v.is_default(&kind) {
continue;
}
}
let value = msg.get_field(&field);
format_field(&field, value.as_ref(), options, out, indent, &mut first);
}
if !options.skip_unknown_fields {
for (number, set) in msg.unknown_fields() {
for entry in set.iter() {
write_indent(out, options, indent, &mut first);
let _ = write!(out, "{number}: ");
let _ = write!(out, "{}", format_unknown(&entry.data));
end_field(out, options);
}
}
}
}
fn format_field(
field: &FieldDescriptor,
value: &Value,
options: &FormatOptions,
out: &mut String,
indent: usize,
first: &mut bool,
) {
if field.is_list() {
if let Value::List(items) = value {
for item in items {
write_indent(out, options, indent, first);
let _ = write!(out, "{}", field.name());
write_value(field, item, options, out, indent);
end_field(out, options);
}
}
return;
}
if field.is_map() {
if let Value::Map(entries) = value {
let mut sorted: Vec<_> = entries.iter().collect();
sorted.sort_by(|a, b| a.0.cmp(b.0));
for (k, v) in sorted {
write_indent(out, options, indent, first);
let _ = write!(out, "{} ", field.name());
out.push('{');
if options.pretty {
out.push('\n');
push_indent(out, indent + 1);
}
let _ = write!(out, "key: {}", format_map_key(k));
if options.pretty {
out.push('\n');
push_indent(out, indent + 1);
} else {
out.push(' ');
}
let kind = match crate::dynamic::value::map_entry_kinds(field) {
Some((_, vk)) => vk,
None => continue,
};
let _ = write!(out, "value: ");
format_scalar_value(&kind, v, options, out, indent + 1);
if options.pretty {
out.push('\n');
push_indent(out, indent);
}
out.push('}');
end_field(out, options);
}
}
return;
}
write_indent(out, options, indent, first);
let _ = write!(out, "{}", field.name());
write_value(field, value, options, out, indent);
end_field(out, options);
}
fn write_value(
field: &FieldDescriptor,
value: &Value,
options: &FormatOptions,
out: &mut String,
indent: usize,
) {
match (&field.kind(), value) {
(Kind::Message(_), Value::Message(m)) => {
out.push(' ');
out.push('{');
if options.pretty {
out.push('\n');
}
format_message(m, options, out, indent + 1);
if options.pretty {
push_indent(out, indent);
}
out.push('}');
}
(kind, _) => {
let _ = write!(out, ": ");
format_scalar_value(kind, value, options, out, indent);
}
}
}
fn format_scalar_value(
kind: &Kind,
value: &Value,
options: &FormatOptions,
out: &mut String,
indent: usize,
) {
match (kind, value) {
(Kind::Bool, Value::Bool(b)) => {
let _ = write!(out, "{}", if *b { "true" } else { "false" });
}
(Kind::Int32 | Kind::Sint32 | Kind::Sfixed32, Value::I32(v)) => {
let _ = write!(out, "{v}");
}
(Kind::Uint32 | Kind::Fixed32, Value::U32(v)) => {
let _ = write!(out, "{v}");
}
(Kind::Int64 | Kind::Sint64 | Kind::Sfixed64, Value::I64(v)) => {
let _ = write!(out, "{v}");
}
(Kind::Uint64 | Kind::Fixed64, Value::U64(v)) => {
let _ = write!(out, "{v}");
}
(Kind::Float, Value::F32(v)) => format_float_into(*v as f64, out),
(Kind::Double, Value::F64(v)) => format_float_into(*v, out),
(Kind::String, Value::String(s)) => {
out.push('"');
escape_into(s.as_bytes(), out, true);
out.push('"');
}
(Kind::Bytes, Value::Bytes(b)) => {
out.push('"');
escape_into(b.as_ref(), out, false);
out.push('"');
}
(Kind::Enum(d), Value::EnumNumber(n)) => match d.values().find(|v| v.number() == *n) {
Some(v) => {
let _ = write!(out, "{}", v.name());
}
None => {
let _ = write!(out, "{n}");
}
},
(Kind::Message(_), Value::Message(m)) => {
out.push('{');
if options.pretty {
out.push('\n');
}
format_message(m, options, out, indent + 1);
if options.pretty {
push_indent(out, indent);
}
out.push('}');
}
_ => {
let _ = write!(out, "/* mismatched */");
}
}
}
fn format_float_into(v: f64, out: &mut String) {
if v.is_nan() {
out.push_str("nan");
} else if v == f64::INFINITY {
out.push_str("inf");
} else if v == f64::NEG_INFINITY {
out.push_str("-inf");
} else if v == v.trunc() {
let _ = write!(out, "{v:.1}");
} else {
let _ = write!(out, "{v}");
}
}
fn escape_into(bytes: &[u8], out: &mut String, _utf8_string: bool) {
for &b in bytes {
match b {
b'\n' => out.push_str("\\n"),
b'\r' => out.push_str("\\r"),
b'\t' => out.push_str("\\t"),
b'\\' => out.push_str("\\\\"),
b'"' => out.push_str("\\\""),
b'\'' => out.push_str("\\'"),
0x20..=0x7e => out.push(b as char),
_ => {
let _ = write!(out, "\\{b:03o}");
}
}
}
}
fn format_map_key(key: &MapKey) -> String {
match key {
MapKey::Bool(b) => b.to_string(),
MapKey::I32(v) => v.to_string(),
MapKey::I64(v) => v.to_string(),
MapKey::U32(v) => v.to_string(),
MapKey::U64(v) => v.to_string(),
MapKey::String(s) => format!("\"{s}\""),
}
}
fn write_indent(out: &mut String, options: &FormatOptions, indent: usize, first: &mut bool) {
if options.pretty {
push_indent(out, indent);
} else if !*first {
out.push(' ');
}
*first = false;
}
fn end_field(out: &mut String, options: &FormatOptions) {
if options.pretty {
out.push('\n');
}
}
fn push_indent(out: &mut String, indent: usize) {
for _ in 0..indent {
out.push_str(" ");
}
}
fn format_unknown(data: &buffa::unknown_fields::UnknownFieldData) -> String {
use buffa::unknown_fields::UnknownFieldData;
match data {
UnknownFieldData::Varint(v) => v.to_string(),
UnknownFieldData::Fixed64(v) => v.to_string(),
UnknownFieldData::Fixed32(v) => v.to_string(),
UnknownFieldData::LengthDelimited(b) => {
let mut s = String::with_capacity(b.len() + 2);
s.push('"');
escape_into(b, &mut s, false);
s.push('"');
s
}
UnknownFieldData::Group(_) => "{ ... }".into(),
}
}