use core::fmt::Write;
use super::string::{escape_bytes, escape_str};
use crate::unknown_fields::{UnknownFieldData, UnknownFields};
const UNKNOWN_LD_RECURSE_BUDGET: u32 = 10;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Last {
Open,
Name,
Value,
}
pub struct TextEncoder<'a> {
w: &'a mut dyn Write,
depth: u32,
pretty: bool,
emit_unknown: bool,
last: Last,
}
impl<'a> TextEncoder<'a> {
pub fn new(w: &'a mut dyn Write) -> Self {
Self {
w,
depth: 0,
pretty: false,
emit_unknown: false,
last: Last::Open,
}
}
pub fn new_pretty(w: &'a mut dyn Write) -> Self {
Self {
w,
depth: 0,
pretty: true,
emit_unknown: false,
last: Last::Open,
}
}
#[must_use]
pub fn emit_unknown(mut self, yes: bool) -> Self {
self.emit_unknown = yes;
self
}
fn prepare(&mut self, next: Last) -> core::fmt::Result {
let prev = self.last;
self.last = next;
if !self.pretty {
if prev == Last::Value && next == Last::Name {
self.w.write_char(' ')?;
}
return Ok(());
}
match (prev, next) {
(Last::Name, _) => {
}
(Last::Open, Last::Name) => {
if self.depth > 0 {
self.w.write_char('\n')?;
self.write_indent()?;
}
}
(Last::Value, Last::Name) | (Last::Value, Last::Value) => {
self.w.write_char('\n')?;
self.write_indent()?;
}
(Last::Open, Last::Value) | (Last::Open, Last::Open) | (Last::Value, Last::Open) => {
}
}
Ok(())
}
fn write_indent(&mut self) -> core::fmt::Result {
for _ in 0..self.depth {
self.w.write_str(" ")?;
}
Ok(())
}
pub fn write_field_name(&mut self, name: &str) -> core::fmt::Result {
self.prepare(Last::Name)?;
self.w.write_str(name)
}
pub fn write_extension_name(&mut self, name: &str) -> core::fmt::Result {
self.prepare(Last::Name)?;
self.w.write_char('[')?;
self.w.write_str(name)?;
self.w.write_char(']')
}
pub fn write_message<M: super::TextFormat>(&mut self, msg: &M) -> core::fmt::Result {
self.write_map_entry(|enc| msg.encode_text(enc))
}
#[doc(hidden)]
pub fn write_map_entry(
&mut self,
f: impl FnOnce(&mut Self) -> core::fmt::Result,
) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(" {")?;
self.depth += 1;
let outer_last = self.last;
self.last = Last::Open;
f(self)?;
self.depth -= 1;
if self.pretty && self.last != Last::Open {
self.w.write_char('\n')?;
self.write_indent()?;
}
self.last = outer_last;
self.w.write_char('}')
}
pub fn try_write_any_expanded(
&mut self,
type_url: &str,
value: &[u8],
) -> Result<bool, core::fmt::Error> {
let Some(entry) = crate::type_registry::global_text_any(type_url) else {
return Ok(false);
};
self.write_extension_name(type_url)?;
(entry.text_encode)(value, self)?;
Ok(true)
}
pub fn write_extension_fields(
&mut self,
extendee: &str,
fields: &UnknownFields,
) -> core::fmt::Result {
if fields.is_empty() {
return Ok(());
}
let mut seen = alloc::collections::BTreeSet::new();
for uf in fields.iter() {
if !seen.insert(uf.number) {
continue;
}
let Some(entry) = crate::type_registry::global_text_ext_by_number(extendee, uf.number)
else {
continue;
};
self.write_extension_name(entry.full_name)?;
(entry.text_encode)(uf.number, fields, self)?;
}
Ok(())
}
pub fn write_unknown_fields(&mut self, fields: &UnknownFields) -> core::fmt::Result {
if !self.emit_unknown {
return Ok(());
}
self.write_unknown_inner(fields, UNKNOWN_LD_RECURSE_BUDGET)
}
fn write_unknown_inner(&mut self, fields: &UnknownFields, budget: u32) -> core::fmt::Result {
for f in fields.iter() {
self.prepare(Last::Name)?;
write!(self.w, "{}", f.number)?;
match &f.data {
UnknownFieldData::Varint(v) => {
self.prepare(Last::Value)?;
write!(self.w, ": {v}")?;
}
UnknownFieldData::Fixed32(v) => {
self.prepare(Last::Value)?;
write!(self.w, ": 0x{v:x}")?;
}
UnknownFieldData::Fixed64(v) => {
self.prepare(Last::Value)?;
write!(self.w, ": 0x{v:x}")?;
}
UnknownFieldData::LengthDelimited(bytes) => {
if budget > 0 && !bytes.is_empty() {
if let Ok(inner) = UnknownFields::decode_from_slice(bytes) {
self.write_map_entry(|enc| {
enc.write_unknown_inner(&inner, budget - 1)
})?;
continue;
}
}
self.prepare(Last::Value)?;
self.w.write_str(": ")?;
escape_bytes(bytes, self.w)?;
}
UnknownFieldData::Group(inner) => {
self.write_map_entry(|enc| enc.write_unknown_inner(inner, budget))?;
}
}
}
Ok(())
}
pub fn write_i32(&mut self, v: i32) -> core::fmt::Result {
self.prepare(Last::Value)?;
write!(self.w, ": {v}")
}
pub fn write_i64(&mut self, v: i64) -> core::fmt::Result {
self.prepare(Last::Value)?;
write!(self.w, ": {v}")
}
pub fn write_u32(&mut self, v: u32) -> core::fmt::Result {
self.prepare(Last::Value)?;
write!(self.w, ": {v}")
}
pub fn write_u64(&mut self, v: u64) -> core::fmt::Result {
self.prepare(Last::Value)?;
write!(self.w, ": {v}")
}
pub fn write_f32(&mut self, v: f32) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(": ")?;
write_float(self.w, v as f64)
}
pub fn write_f64(&mut self, v: f64) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(": ")?;
write_float(self.w, v)
}
pub fn write_bool(&mut self, v: bool) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(if v { ": true" } else { ": false" })
}
pub fn write_string(&mut self, v: &str) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(": ")?;
escape_str(v, self.w)
}
pub fn write_bytes(&mut self, v: &[u8]) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(": ")?;
escape_bytes(v, self.w)
}
pub fn write_enum_name(&mut self, name: &str) -> core::fmt::Result {
self.prepare(Last::Value)?;
self.w.write_str(": ")?;
self.w.write_str(name)
}
pub fn write_enum_number(&mut self, v: i32) -> core::fmt::Result {
self.write_i32(v)
}
}
fn write_float(w: &mut dyn Write, v: f64) -> core::fmt::Result {
if v.is_nan() {
w.write_str("nan")
} else if v.is_infinite() {
if v > 0.0 {
w.write_str("inf")
} else {
w.write_str("-inf")
}
} else {
write!(w, "{v}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
#[test]
fn single_line_scalars() {
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s);
enc.write_field_name("a").unwrap();
enc.write_i32(42).unwrap();
enc.write_field_name("b").unwrap();
enc.write_string("hello").unwrap();
assert_eq!(s, r#"a: 42 b: "hello""#);
}
#[test]
fn pretty_scalars() {
let mut s = String::new();
let mut enc = TextEncoder::new_pretty(&mut s);
enc.write_field_name("a").unwrap();
enc.write_i32(42).unwrap();
enc.write_field_name("b").unwrap();
enc.write_i32(7).unwrap();
assert_eq!(s, "a: 42\nb: 7");
}
#[test]
fn all_scalar_types() {
#[rustfmt::skip]
let cases: &[(&str, &str)] = &[
("i32", "f: -7"),
("i64", "f: 9000000000"),
("u32", "f: 42"),
("u64", "f: 18000000000000000000"),
("bool", "f: true"),
("str", r#"f: "hi""#),
("bytes", r#"f: "\377""#),
("enum", "f: FOO_BAR"),
];
for &(which, want) in cases {
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s);
enc.write_field_name("f").unwrap();
match which {
"i32" => enc.write_i32(-7).unwrap(),
"i64" => enc.write_i64(9_000_000_000).unwrap(),
"u32" => enc.write_u32(42).unwrap(),
"u64" => enc.write_u64(18_000_000_000_000_000_000).unwrap(),
"bool" => enc.write_bool(true).unwrap(),
"str" => enc.write_string("hi").unwrap(),
"bytes" => enc.write_bytes(&[0xFF]).unwrap(),
"enum" => enc.write_enum_name("FOO_BAR").unwrap(),
_ => unreachable!(),
}
assert_eq!(s, want, "type: {which}");
}
}
#[test]
fn float_specials() {
#[rustfmt::skip]
let cases: &[(f64, &str)] = &[
(1.5, "f: 1.5"),
(0.0, "f: 0"),
(f64::NAN, "f: nan"),
(f64::INFINITY, "f: inf"),
(f64::NEG_INFINITY, "f: -inf"),
];
for &(v, want) in cases {
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s);
enc.write_field_name("f").unwrap();
enc.write_f64(v).unwrap();
assert_eq!(s, want, "value: {v}");
}
}
#[test]
fn extension_name() {
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s);
enc.write_extension_name("pkg.ext").unwrap();
enc.write_i32(1).unwrap();
assert_eq!(s, "[pkg.ext]: 1");
}
#[test]
fn unknown_fields_default_noop() {
use crate::unknown_fields::UnknownField;
let mut fields = UnknownFields::new();
fields.push(UnknownField {
number: 1001,
data: UnknownFieldData::Varint(42),
});
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s);
enc.write_unknown_fields(&fields).unwrap();
assert_eq!(s, ""); }
#[test]
fn unknown_fields_all_wire_types() {
use crate::unknown_fields::UnknownField;
use alloc::vec;
let mut group_inner = UnknownFields::new();
group_inner.push(UnknownField {
number: 1,
data: UnknownFieldData::Varint(7),
});
let mut fields = UnknownFields::new();
fields.push(UnknownField {
number: 1001,
data: UnknownFieldData::Varint(42),
});
fields.push(UnknownField {
number: 1002,
data: UnknownFieldData::Fixed32(0x3F80_0000),
});
fields.push(UnknownField {
number: 1003,
data: UnknownFieldData::Fixed64(0xDEAD_BEEF),
});
fields.push(UnknownField {
number: 1004,
data: UnknownFieldData::LengthDelimited(vec![0x01, 0xFF]),
});
fields.push(UnknownField {
number: 1005,
data: UnknownFieldData::Group(group_inner),
});
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s).emit_unknown(true);
enc.write_unknown_fields(&fields).unwrap();
assert_eq!(
s,
r#"1001: 42 1002: 0x3f800000 1003: 0xdeadbeef 1004: "\001\377" 1005 {1: 7}"#
);
}
#[test]
fn unknown_fields_after_known_fields() {
use crate::unknown_fields::UnknownField;
let mut fields = UnknownFields::new();
fields.push(UnknownField {
number: 99,
data: UnknownFieldData::Varint(1),
});
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s).emit_unknown(true);
enc.write_field_name("known").unwrap();
enc.write_i32(5).unwrap();
enc.write_unknown_fields(&fields).unwrap();
assert_eq!(s, "known: 5 99: 1");
}
#[test]
fn unknown_ld_heuristic_prints_nested() {
use crate::unknown_fields::UnknownField;
use alloc::vec;
let mut fields = UnknownFields::new();
fields.push(UnknownField {
number: 1003,
data: UnknownFieldData::LengthDelimited(vec![0x08, 0x6F]),
});
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s).emit_unknown(true);
enc.write_unknown_fields(&fields).unwrap();
assert_eq!(s, "1003 {1: 111}");
}
#[test]
fn unknown_ld_heuristic_falls_back_to_bytes() {
use crate::unknown_fields::UnknownField;
#[rustfmt::skip]
let cases: &[(&[u8], &str)] = &[
(&[], r#"1: """#), (&[0x01, 0xFF], r#"1: "\001\377""#), (&[0x08], r#"1: "\010""#), (b"hello", r#"1: "hello""#), ];
for &(bytes, want) in cases {
let mut fields = UnknownFields::new();
fields.push(UnknownField {
number: 1,
data: UnknownFieldData::LengthDelimited(bytes.to_vec()),
});
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s).emit_unknown(true);
enc.write_unknown_fields(&fields).unwrap();
assert_eq!(s, want, "bytes: {bytes:02X?}");
}
}
#[test]
fn unknown_ld_heuristic_budget_caps_depth() {
use crate::unknown_fields::UnknownField;
use alloc::vec;
let mut bytes = vec![0x08, 0x07];
for _ in 0..12 {
let len = bytes.len() as u8;
let mut wrapper = vec![0x0A, len]; wrapper.extend_from_slice(&bytes);
bytes = wrapper;
}
let mut fields = UnknownFields::new();
fields.push(UnknownField {
number: 99,
data: UnknownFieldData::LengthDelimited(bytes),
});
let mut s = String::new();
let mut enc = TextEncoder::new(&mut s).emit_unknown(true);
enc.write_unknown_fields(&fields).unwrap();
assert_eq!(s.matches('{').count(), 10, "output: {s}");
assert!(
s.contains(r#": ""#),
"expected bytes fallback at floor: {s}"
);
}
}