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 dump_with_schema(&msg, &schema)
23}
24
25pub fn dump_json_with(schema: &Schema, buf: &[u8]) -> Result<String> {
34 let msg = Message::parse(buf)?;
35 if msg.schema_id() != schema.id() {
36 return Err(Error::SchemaIdMismatch {
37 message: msg.schema_id(),
38 expected: schema.id(),
39 });
40 }
41 dump_with_schema(&msg, schema)
42}
43
44fn dump_with_schema(msg: &Message<'_>, schema: &Schema) -> Result<String> {
45 let resolver = Resolver::identity(schema)?;
46 let budget = Budget::new(msg.suggested_budget());
51 let root = msg.root_bounded(&resolver, &budget)?;
52 let mut out = String::new();
53 write_struct(&mut out, schema, &root, 0)?;
54 Ok(out)
55}
56
57fn write_struct(out: &mut String, schema: &Schema, sr: &StructReader, depth: u32) -> Result<()> {
58 if depth > MAX_DUMP_DEPTH {
59 return Err(Error::DepthLimitExceeded);
60 }
61 out.push('{');
62 let mut first = true;
63 for field in &sr.struct_def().fields {
64 if let Some(value) = sr.get_or_default(field.id)? {
67 if !first {
68 out.push(',');
69 }
70 first = false;
71 json_string(out, &field.name);
72 out.push(':');
73 write_value(out, schema, &field.ty, &value, depth)?;
74 }
75 }
76 out.push('}');
77 Ok(())
78}
79
80fn write_value(
81 out: &mut String,
82 schema: &Schema,
83 ty: &Type,
84 value: &Ref,
85 depth: u32,
86) -> Result<()> {
87 if depth > MAX_DUMP_DEPTH {
88 return Err(Error::DepthLimitExceeded);
89 }
90 match value {
91 Ref::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
92 Ref::U8(x) => {
93 let _ = write!(out, "{x}");
94 }
95 Ref::U16(x) => {
96 let _ = write!(out, "{x}");
97 }
98 Ref::U32(x) => {
99 let _ = write!(out, "{x}");
100 }
101 Ref::U64(x) => {
102 let _ = write!(out, "{x}");
103 }
104 Ref::I8(x) => {
105 let _ = write!(out, "{x}");
106 }
107 Ref::I16(x) => {
108 let _ = write!(out, "{x}");
109 }
110 Ref::I32(x) => {
111 let _ = write!(out, "{x}");
112 }
113 Ref::I64(x) => {
114 let _ = write!(out, "{x}");
115 }
116 Ref::F32(x) => write_float(out, f64::from(*x)),
117 Ref::F64(x) => write_float(out, *x),
118 Ref::Str(s) => json_string(out, s),
119 Ref::Bytes(b) => {
120 out.push('"');
121 for byte in *b {
122 let _ = write!(out, "{byte:02x}");
123 }
124 out.push('"');
125 }
126 Ref::Enum(v) => {
127 let name = match ty {
128 Type::Enum(i) => schema.enum_def_unchecked(*i).name_of(*v),
129 _ => None,
130 };
131 match name {
132 Some(n) => json_string(out, n),
133 None => {
134 let _ = write!(out, "{v}");
135 }
136 }
137 }
138 Ref::Struct(sr) => write_struct(out, schema, sr, depth + 1)?,
139 Ref::List(lr) => {
140 let elem_ty = match ty {
141 Type::List(e) => e.as_ref(),
142 _ => return Err(Error::Internal("list value with non-list schema type")),
143 };
144 out.push('[');
145 for i in 0..lr.len() {
146 if i > 0 {
147 out.push(',');
148 }
149 let elem = lr.get(i)?;
150 write_value(out, schema, elem_ty, &elem, depth + 1)?;
151 }
152 out.push(']');
153 }
154 Ref::Map(mr) => {
155 let val_ty = match ty {
158 Type::Map(_, v) => v.as_ref(),
159 _ => return Err(Error::Internal("map value with non-map schema type")),
160 };
161 out.push('{');
162 for i in 0..mr.len() {
163 if i > 0 {
164 out.push(',');
165 }
166 let (k, v) = mr.get(i)?;
167 write_map_key(out, &k);
168 out.push(':');
169 write_value(out, schema, val_ty, &v, depth + 1)?;
170 }
171 out.push('}');
172 }
173 Ref::Union(u) => {
174 let variants = match ty {
177 Type::Union(vs) => vs,
178 _ => return Err(Error::Internal("union value with non-union schema type")),
179 };
180 let tag = u.tag();
181 let vty = variants.get(tag as usize).ok_or(Error::BadUnionTag(tag))?;
182 out.push('{');
183 json_string(out, &tag.to_string());
184 out.push(':');
185 write_value(out, schema, vty, &u.value()?, depth + 1)?;
186 out.push('}');
187 }
188 }
189 Ok(())
190}
191
192fn write_map_key(out: &mut String, key: &Ref) {
194 match key {
195 Ref::Str(s) => json_string(out, s),
196 Ref::Bool(b) => json_string(out, if *b { "true" } else { "false" }),
197 Ref::U8(x) => json_string(out, &x.to_string()),
198 Ref::U16(x) => json_string(out, &x.to_string()),
199 Ref::U32(x) => json_string(out, &x.to_string()),
200 Ref::U64(x) => json_string(out, &x.to_string()),
201 Ref::I8(x) => json_string(out, &x.to_string()),
202 Ref::I16(x) => json_string(out, &x.to_string()),
203 Ref::I32(x) => json_string(out, &x.to_string()),
204 Ref::I64(x) => json_string(out, &x.to_string()),
205 Ref::Enum(x) => json_string(out, &x.to_string()),
206 _ => json_string(out, key.kind()),
208 }
209}
210
211fn write_float(out: &mut String, x: f64) {
212 if x.is_finite() {
213 let _ = write!(out, "{x}");
214 } else {
215 out.push_str("null");
217 }
218}
219
220fn json_string(out: &mut String, s: &str) {
221 out.push('"');
222 for c in s.chars() {
223 match c {
224 '"' => out.push_str("\\\""),
225 '\\' => out.push_str("\\\\"),
226 '\n' => out.push_str("\\n"),
227 '\r' => out.push_str("\\r"),
228 '\t' => out.push_str("\\t"),
229 c if (c as u32) < 0x20 => {
230 let _ = write!(out, "\\u{:04x}", c as u32);
231 }
232 c => out.push(c),
233 }
234 }
235 out.push('"');
236}