1use std::fmt::Write as _;
6
7use crate::error::{Error, Result};
8use crate::message::{Budget, Message, Ref, StructReader};
9use crate::resolve::Resolver;
10use crate::schema::{Schema, Type};
11
12const MAX_DUMP_DEPTH: u32 = 128;
16
17pub fn dump_json(buf: &[u8]) -> Result<String> {
20 let msg = Message::parse(buf)?;
21 let schema = msg.writer_schema()?.ok_or(Error::NoInlineSchema)?;
22 let resolver = Resolver::identity(&schema)?;
23 let budget = Budget::new(msg.suggested_budget());
28 let root = msg.root_bounded(&resolver, &budget)?;
29 let mut out = String::new();
30 write_struct(&mut out, &schema, &root, 0)?;
31 Ok(out)
32}
33
34fn write_struct(out: &mut String, schema: &Schema, sr: &StructReader, depth: u32) -> Result<()> {
35 if depth > MAX_DUMP_DEPTH {
36 return Err(Error::DepthLimitExceeded);
37 }
38 out.push('{');
39 let mut first = true;
40 for field in &sr.struct_def().fields {
41 if let Some(value) = sr.get_or_default(field.id)? {
44 if !first {
45 out.push(',');
46 }
47 first = false;
48 json_string(out, &field.name);
49 out.push(':');
50 write_value(out, schema, &field.ty, &value, depth)?;
51 }
52 }
53 out.push('}');
54 Ok(())
55}
56
57fn write_value(
58 out: &mut String,
59 schema: &Schema,
60 ty: &Type,
61 value: &Ref,
62 depth: u32,
63) -> Result<()> {
64 if depth > MAX_DUMP_DEPTH {
65 return Err(Error::DepthLimitExceeded);
66 }
67 match value {
68 Ref::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
69 Ref::U8(x) => {
70 let _ = write!(out, "{x}");
71 }
72 Ref::U16(x) => {
73 let _ = write!(out, "{x}");
74 }
75 Ref::U32(x) => {
76 let _ = write!(out, "{x}");
77 }
78 Ref::U64(x) => {
79 let _ = write!(out, "{x}");
80 }
81 Ref::I8(x) => {
82 let _ = write!(out, "{x}");
83 }
84 Ref::I16(x) => {
85 let _ = write!(out, "{x}");
86 }
87 Ref::I32(x) => {
88 let _ = write!(out, "{x}");
89 }
90 Ref::I64(x) => {
91 let _ = write!(out, "{x}");
92 }
93 Ref::F32(x) => write_float(out, f64::from(*x)),
94 Ref::F64(x) => write_float(out, *x),
95 Ref::Str(s) => json_string(out, s),
96 Ref::Bytes(b) => {
97 out.push('"');
98 for byte in *b {
99 let _ = write!(out, "{byte:02x}");
100 }
101 out.push('"');
102 }
103 Ref::Enum(v) => {
104 let name = match ty {
105 Type::Enum(i) => schema.enum_def_unchecked(*i).name_of(*v),
106 _ => None,
107 };
108 match name {
109 Some(n) => json_string(out, n),
110 None => {
111 let _ = write!(out, "{v}");
112 }
113 }
114 }
115 Ref::Struct(sr) => write_struct(out, schema, sr, depth + 1)?,
116 Ref::List(lr) => {
117 let elem_ty = match ty {
118 Type::List(e) => e.as_ref(),
119 _ => return Err(Error::Internal("list value with non-list schema type")),
120 };
121 out.push('[');
122 for i in 0..lr.len() {
123 if i > 0 {
124 out.push(',');
125 }
126 let elem = lr.get(i)?;
127 write_value(out, schema, elem_ty, &elem, depth + 1)?;
128 }
129 out.push(']');
130 }
131 Ref::Map(mr) => {
132 let val_ty = match ty {
135 Type::Map(_, v) => v.as_ref(),
136 _ => return Err(Error::Internal("map value with non-map schema type")),
137 };
138 out.push('{');
139 for i in 0..mr.len() {
140 if i > 0 {
141 out.push(',');
142 }
143 let (k, v) = mr.get(i)?;
144 write_map_key(out, &k);
145 out.push(':');
146 write_value(out, schema, val_ty, &v, depth + 1)?;
147 }
148 out.push('}');
149 }
150 Ref::Union(u) => {
151 let variants = match ty {
154 Type::Union(vs) => vs,
155 _ => return Err(Error::Internal("union value with non-union schema type")),
156 };
157 let tag = u.tag();
158 let vty = variants.get(tag as usize).ok_or(Error::BadUnionTag(tag))?;
159 out.push('{');
160 json_string(out, &tag.to_string());
161 out.push(':');
162 write_value(out, schema, vty, &u.value()?, depth + 1)?;
163 out.push('}');
164 }
165 }
166 Ok(())
167}
168
169fn write_map_key(out: &mut String, key: &Ref) {
171 match key {
172 Ref::Str(s) => json_string(out, s),
173 Ref::Bool(b) => json_string(out, if *b { "true" } else { "false" }),
174 Ref::U8(x) => json_string(out, &x.to_string()),
175 Ref::U16(x) => json_string(out, &x.to_string()),
176 Ref::U32(x) => json_string(out, &x.to_string()),
177 Ref::U64(x) => json_string(out, &x.to_string()),
178 Ref::I8(x) => json_string(out, &x.to_string()),
179 Ref::I16(x) => json_string(out, &x.to_string()),
180 Ref::I32(x) => json_string(out, &x.to_string()),
181 Ref::I64(x) => json_string(out, &x.to_string()),
182 Ref::Enum(x) => json_string(out, &x.to_string()),
183 _ => json_string(out, key.kind()),
185 }
186}
187
188fn write_float(out: &mut String, x: f64) {
189 if x.is_finite() {
190 let _ = write!(out, "{x}");
191 } else {
192 out.push_str("null");
194 }
195}
196
197fn json_string(out: &mut String, s: &str) {
198 out.push('"');
199 for c in s.chars() {
200 match c {
201 '"' => out.push_str("\\\""),
202 '\\' => out.push_str("\\\\"),
203 '\n' => out.push_str("\\n"),
204 '\r' => out.push_str("\\r"),
205 '\t' => out.push_str("\\t"),
206 c if (c as u32) < 0x20 => {
207 let _ = write!(out, "\\u{:04x}", c as u32);
208 }
209 c => out.push(c),
210 }
211 }
212 out.push('"');
213}