1use std::fmt::Write;
2
3use rquickjs::class::Trace;
4use rquickjs::{Error, Result, Type, Value};
5
6#[derive(Default, Clone, Debug)]
7#[non_exhaustive]
8struct FormatArgs {
9 key: Option<bool>,
10}
11
12impl FormatArgs {
13 pub fn is_key(&self) -> bool {
14 self.key.unwrap_or(false)
15 }
16
17 pub fn with_key(self) -> Self {
18 Self {
19 key: Some(true),
20 ..self
21 }
22 }
23}
24
25#[derive(Clone, Debug, Trace)]
31pub struct Formatter {
32 max_depth: usize,
33}
34
35impl Default for Formatter {
36 fn default() -> Self {
37 Self::builder().build()
38 }
39}
40
41impl Formatter {
42 pub fn builder() -> FormatterBuilder {
43 FormatterBuilder::default()
44 }
45
46 pub fn format(&self, out: &mut impl Write, value: Value<'_>) -> Result<()> {
47 self._format(out, value, FormatArgs::default(), 0)
48 }
49
50 fn _format(
53 &self,
54 out: &mut impl Write,
55 value: Value<'_>,
56 args: FormatArgs,
57 depth: usize,
58 ) -> Result<()> {
59 match value.type_of() {
60 Type::String => {
61 write!(
62 out,
63 "{}",
64 value
65 .into_string()
66 .ok_or(Error::new_from_js("value", "string"))?
67 .to_string()?
68 )
69 .map_err(|_| Error::Unknown)?;
70 }
71 Type::Int => {
72 write!(
73 out,
74 "{}",
75 value.as_int().ok_or(Error::new_from_js("value", "int"))?
76 )
77 .map_err(|_| Error::Unknown)?;
78 }
79 Type::Bool => {
80 write!(
81 out,
82 "{}",
83 value.as_bool().ok_or(Error::new_from_js("value", "bool"))?
84 )
85 .map_err(|_| Error::Unknown)?;
86 }
87 Type::Float => {
88 write!(
89 out,
90 "{}",
91 value
92 .as_float()
93 .ok_or(Error::new_from_js("value", "float"))?
94 )
95 .map_err(|_| Error::Unknown)?;
96 }
97 Type::BigInt => {
98 write!(
99 out,
100 "{}n",
101 value
102 .into_big_int()
103 .ok_or(Error::new_from_js("value", "bigint"))?
104 .to_i64()?
105 )
106 .map_err(|_| Error::Unknown)?;
107 }
108 Type::Array => {
109 let array = value
110 .into_array()
111 .ok_or(Error::new_from_js("value", "array"))?;
112 if depth > self.max_depth {
113 write!(out, "[Array]").map_err(|_| Error::Unknown)?;
114 } else if args.is_key() {
115 for (i, element) in array.iter().enumerate() {
116 if i > 0 {
117 write!(out, ",").map_err(|_| Error::Unknown)?;
118 }
119 self._format(out, element?, FormatArgs::default().with_key(), depth + 1)?;
120 }
121 } else {
122 write!(out, "[ ").map_err(|_| Error::Unknown)?;
123 for (i, element) in array.iter().enumerate() {
124 if i > 0 {
125 write!(out, ", ").map_err(|_| Error::Unknown)?;
126 }
127 self._format(out, element?, FormatArgs::default(), depth + 1)?;
128 }
129 write!(out, " ]").map_err(|_| Error::Unknown)?;
130 }
131 }
132 Type::Object => {
133 if depth > self.max_depth {
134 write!(out, "[Object]").map_err(|_| Error::Unknown)?;
135 } else if args.is_key() {
136 write!(out, "[object Object]").map_err(|_| Error::Unknown)?;
137 } else {
138 let object = value
139 .into_object()
140 .ok_or(Error::new_from_js("value", "object"))?;
141 write!(out, "{{ ").map_err(|_| Error::Unknown)?;
142 for prop in object.props() {
143 let (key, val) = prop?;
144 self._format(out, key, FormatArgs::default().with_key(), depth + 1)?;
145 write!(out, ": ").map_err(|_| Error::Unknown)?;
146 self._format(out, val, FormatArgs::default(), depth + 1)?;
147 }
148 write!(out, " }}").map_err(|_| Error::Unknown)?;
149 }
150 }
151 Type::Symbol => {
152 let symbol = value
153 .as_symbol()
154 .ok_or(Error::new_from_js("value", "symbol"))?;
155 let description = match symbol.description()?.as_string() {
156 Some(description) => description.to_string()?,
157 None => String::default(),
158 };
159 write!(out, "Symbol({description})").map_err(|_| Error::Unknown)?;
160 }
161 Type::Function => {
162 let function = value
163 .as_function()
164 .ok_or(Error::new_from_js("value", "function"))?
165 .as_object()
166 .ok_or(Error::new_from_js("function", "object"))?;
167 let name: Option<String> = function.get("name").ok().and_then(|n| {
168 if n == "[object Object]" {
169 None
170 } else {
171 Some(n)
172 }
173 });
174 match name {
175 Some(name) => write!(out, "[Function: {name}]").map_err(|_| Error::Unknown)?,
176 None => write!(out, "[Function (anonymous)]").map_err(|_| Error::Unknown)?,
177 }
178 }
179 Type::Null => {
180 write!(out, "null",).map_err(|_| Error::Unknown)?;
181 }
182 Type::Undefined => {
183 write!(out, "undefined",).map_err(|_| Error::Unknown)?;
184 }
185 _ => {}
186 };
187
188 Ok(())
189 }
190}
191
192#[derive(Default, Clone, Debug)]
194#[non_exhaustive]
195pub struct FormatterBuilder {
196 max_depth: Option<usize>,
197}
198
199impl FormatterBuilder {
200 pub fn max_depth(self, max_depth: usize) -> Self {
205 Self {
206 max_depth: Some(max_depth),
207 ..self
208 }
209 }
210
211 pub fn build(self) -> Formatter {
213 Formatter {
214 max_depth: self.max_depth.unwrap_or(10),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use rquickjs_extra_test::test_with;
222
223 use super::*;
224
225 type StdString = std::string::String;
226
227 #[test]
228 fn format_string() {
229 test_with(|ctx| {
230 let formatter = Formatter::default();
231 let value = ctx.eval("'test'").unwrap();
232 let mut out = StdString::default();
233 formatter.format(&mut out, value).unwrap();
234 assert_eq!("test", out);
235 })
236 }
237
238 #[test]
239 fn format_int() {
240 test_with(|ctx| {
241 let formatter = Formatter::default();
242 let value = ctx.eval("true").unwrap();
243 let mut out = StdString::default();
244 formatter.format(&mut out, value).unwrap();
245 assert_eq!("true", out);
246 })
247 }
248
249 #[test]
250 fn format_bool() {
251 test_with(|ctx| {
252 let formatter = Formatter::default();
253 let value = ctx.eval("'test'").unwrap();
254 let mut out = StdString::default();
255 formatter.format(&mut out, value).unwrap();
256 assert_eq!("test", out);
257 })
258 }
259
260 #[test]
261 fn format_float() {
262 test_with(|ctx| {
263 let formatter = Formatter::default();
264 let value = ctx.eval("1.5").unwrap();
265 let mut out = StdString::default();
266 formatter.format(&mut out, value).unwrap();
267 assert_eq!("1.5", out);
268 })
269 }
270
271 #[test]
272 fn format_bigint() {
273 test_with(|ctx| {
274 let formatter = Formatter::default();
275 let value = ctx.eval("BigInt('9007199254740991')").unwrap();
276 let mut out = StdString::default();
277 formatter.format(&mut out, value).unwrap();
278 assert_eq!("9007199254740991n", out);
279 })
280 }
281
282 #[test]
283 fn format_array() {
284 test_with(|ctx| {
285 let formatter = Formatter::default();
286 let value = ctx.eval("[1,2,3]").unwrap();
287 let mut out = StdString::default();
288 formatter.format(&mut out, value).unwrap();
289 assert_eq!("[ 1, 2, 3 ]", out);
290 })
291 }
292
293 #[test]
294 fn format_array_max_depth() {
295 test_with(|ctx| {
296 let formatter = Formatter::builder().max_depth(1).build();
297 let value = ctx.eval("[1,[2,[3]]]").unwrap();
298 let mut out = StdString::default();
299 formatter.format(&mut out, value).unwrap();
300 assert_eq!("[ 1, [ 2, [Array] ] ]", out);
301 })
302 }
303
304 #[test]
305 fn format_object() {
306 test_with(|ctx| {
307 let formatter = Formatter::default();
308 let value = ctx.eval("const a = {'a':1}; a").unwrap();
309 let mut out = StdString::default();
310 formatter.format(&mut out, value).unwrap();
311 assert_eq!("{ a: 1 }", out);
312 })
313 }
314
315 #[test]
316 fn format_object_complex() {
317 test_with(|ctx| {
318 let formatter = Formatter::default();
319 let value = ctx
320 .eval("const a = {[['a','b']]:{'c': [{1: 'd'}]}}; a")
321 .unwrap();
322 let mut out = StdString::default();
323 formatter.format(&mut out, value).unwrap();
324 assert_eq!("{ a,b: { c: [ { 1: d } ] } }", out);
325 })
326 }
327
328 #[test]
329 fn format_object_max_depth() {
330 test_with(|ctx| {
331 let formatter = Formatter::builder().max_depth(1).build();
332 let value = ctx.eval("const a = {1:{2:{3:4}}}; a").unwrap();
333 let mut out = StdString::default();
334 formatter.format(&mut out, value).unwrap();
335 assert_eq!("{ 1: { 2: [Object] } }", out);
336 })
337 }
338
339 #[test]
340 fn format_symbol() {
341 test_with(|ctx| {
342 let formatter = Formatter::default();
343 let value = ctx.eval("Symbol('a')").unwrap();
344 let mut out = StdString::default();
345 formatter.format(&mut out, value).unwrap();
346 assert_eq!("Symbol(a)", out);
347 })
348 }
349
350 #[test]
351 fn format_function() {
352 test_with(|ctx| {
353 let formatter = Formatter::default();
354 let value = ctx.eval("const myfunc = () => {}; myfunc").unwrap();
355 let mut out = StdString::default();
356 formatter.format(&mut out, value).unwrap();
357 assert_eq!("[Function: myfunc]", out);
358 })
359 }
360
361 #[test]
362 fn format_undefined() {
363 test_with(|ctx| {
364 let formatter = Formatter::default();
365 let value = ctx.eval("undefined").unwrap();
366 let mut out = StdString::default();
367 formatter.format(&mut out, value).unwrap();
368 assert_eq!("undefined", out);
369 })
370 }
371
372 #[test]
373 fn format_null() {
374 test_with(|ctx| {
375 let formatter = Formatter::default();
376 let value = ctx.eval("null").unwrap();
377 let mut out = StdString::default();
378 formatter.format(&mut out, value).unwrap();
379 assert_eq!("null", out);
380 })
381 }
382}