use std::fmt::Write as _;
use crate::error::{Error, Result};
use crate::message::{Budget, Message, Ref, StructReader};
use crate::resolve::Resolver;
use crate::schema::{Schema, Type};
const MAX_DUMP_DEPTH: u32 = 128;
pub fn dump_json(buf: &[u8]) -> Result<String> {
let msg = Message::parse(buf)?;
let schema = msg.writer_schema()?.ok_or(Error::NoInlineSchema)?;
dump_with_schema(&msg, &schema)
}
pub fn dump_json_with(schema: &Schema, buf: &[u8]) -> Result<String> {
let msg = Message::parse(buf)?;
if msg.schema_id() != schema.id() {
return Err(Error::SchemaIdMismatch {
message: msg.schema_id(),
expected: schema.id(),
});
}
dump_with_schema(&msg, schema)
}
fn dump_with_schema(msg: &Message<'_>, schema: &Schema) -> Result<String> {
let resolver = Resolver::identity(schema)?;
let budget = Budget::new(msg.suggested_budget());
let root = msg.root_bounded(&resolver, &budget)?;
let mut out = String::new();
write_struct(&mut out, schema, &root, 0)?;
Ok(out)
}
fn write_struct(out: &mut String, schema: &Schema, sr: &StructReader, depth: u32) -> Result<()> {
if depth > MAX_DUMP_DEPTH {
return Err(Error::DepthLimitExceeded);
}
out.push('{');
let mut first = true;
for field in &sr.struct_def().fields {
if let Some(value) = sr.get_or_default(field.id)? {
if !first {
out.push(',');
}
first = false;
json_string(out, &field.name);
out.push(':');
write_value(out, schema, &field.ty, &value, depth)?;
}
}
out.push('}');
Ok(())
}
fn write_value(
out: &mut String,
schema: &Schema,
ty: &Type,
value: &Ref,
depth: u32,
) -> Result<()> {
if depth > MAX_DUMP_DEPTH {
return Err(Error::DepthLimitExceeded);
}
match value {
Ref::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Ref::U8(x) => {
let _ = write!(out, "{x}");
}
Ref::U16(x) => {
let _ = write!(out, "{x}");
}
Ref::U32(x) => {
let _ = write!(out, "{x}");
}
Ref::U64(x) => {
let _ = write!(out, "{x}");
}
Ref::I8(x) => {
let _ = write!(out, "{x}");
}
Ref::I16(x) => {
let _ = write!(out, "{x}");
}
Ref::I32(x) => {
let _ = write!(out, "{x}");
}
Ref::I64(x) => {
let _ = write!(out, "{x}");
}
Ref::F32(x) => write_float(out, f64::from(*x)),
Ref::F64(x) => write_float(out, *x),
Ref::Str(s) => json_string(out, s),
Ref::Bytes(b) => {
out.push('"');
for byte in *b {
let _ = write!(out, "{byte:02x}");
}
out.push('"');
}
Ref::Enum(v) => {
let name = match ty {
Type::Enum(i) => schema.enum_def_unchecked(*i).name_of(*v),
_ => None,
};
match name {
Some(n) => json_string(out, n),
None => {
let _ = write!(out, "{v}");
}
}
}
Ref::Struct(sr) => write_struct(out, schema, sr, depth + 1)?,
Ref::List(lr) => {
let elem_ty = match ty {
Type::List(e) => e.as_ref(),
_ => return Err(Error::Internal("list value with non-list schema type")),
};
out.push('[');
for i in 0..lr.len() {
if i > 0 {
out.push(',');
}
let elem = lr.get(i)?;
write_value(out, schema, elem_ty, &elem, depth + 1)?;
}
out.push(']');
}
Ref::Map(mr) => {
let val_ty = match ty {
Type::Map(_, v) => v.as_ref(),
_ => return Err(Error::Internal("map value with non-map schema type")),
};
out.push('{');
for i in 0..mr.len() {
if i > 0 {
out.push(',');
}
let (k, v) = mr.get(i)?;
write_map_key(out, &k);
out.push(':');
write_value(out, schema, val_ty, &v, depth + 1)?;
}
out.push('}');
}
Ref::Union(u) => {
let variants = match ty {
Type::Union(vs) => vs,
_ => return Err(Error::Internal("union value with non-union schema type")),
};
let tag = u.tag();
let vty = variants.get(tag as usize).ok_or(Error::BadUnionTag(tag))?;
out.push('{');
json_string(out, &tag.to_string());
out.push(':');
write_value(out, schema, vty, &u.value()?, depth + 1)?;
out.push('}');
}
}
Ok(())
}
fn write_map_key(out: &mut String, key: &Ref) {
match key {
Ref::Str(s) => json_string(out, s),
Ref::Bool(b) => json_string(out, if *b { "true" } else { "false" }),
Ref::U8(x) => json_string(out, &x.to_string()),
Ref::U16(x) => json_string(out, &x.to_string()),
Ref::U32(x) => json_string(out, &x.to_string()),
Ref::U64(x) => json_string(out, &x.to_string()),
Ref::I8(x) => json_string(out, &x.to_string()),
Ref::I16(x) => json_string(out, &x.to_string()),
Ref::I32(x) => json_string(out, &x.to_string()),
Ref::I64(x) => json_string(out, &x.to_string()),
Ref::Enum(x) => json_string(out, &x.to_string()),
_ => json_string(out, key.kind()),
}
}
fn write_float(out: &mut String, x: f64) {
if x.is_finite() {
let _ = write!(out, "{x}");
} else {
out.push_str("null");
}
}
fn json_string(out: &mut String, s: &str) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.push(c),
}
}
out.push('"');
}