1use crate::console::RenderContext;
18use crate::highlighter::{Highlighter, JsonHighlighter};
19use crate::renderable::{Renderable, Segment};
20#[cfg(test)]
21use crate::text::Overflow;
22use crate::text::Text;
23use serde::Serialize;
24use serde_json::{self, Value};
25use std::fmt;
26use std::fs;
27use std::path::Path;
28
29#[derive(Debug)]
31pub enum JsonError {
32 Parse(serde_json::Error),
34 Serialize(serde_json::Error),
36 Io(std::io::Error),
38}
39
40impl fmt::Display for JsonError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 JsonError::Parse(e) => write!(f, "JSON parse error: {}", e),
44 JsonError::Serialize(e) => write!(f, "JSON serialize error: {}", e),
45 JsonError::Io(e) => write!(f, "IO error: {}", e),
46 }
47 }
48}
49
50impl std::error::Error for JsonError {}
51
52impl From<std::io::Error> for JsonError {
53 fn from(e: std::io::Error) -> Self {
54 JsonError::Io(e)
55 }
56}
57
58#[derive(Debug, Clone)]
62pub enum JsonIndent {
63 Compact,
65 Spaces(usize),
67 Custom(String),
69}
70
71impl Default for JsonIndent {
72 fn default() -> Self {
73 JsonIndent::Spaces(2)
74 }
75}
76
77impl From<usize> for JsonIndent {
78 fn from(n: usize) -> Self {
79 if n == 0 {
80 JsonIndent::Compact
81 } else {
82 JsonIndent::Spaces(n)
83 }
84 }
85}
86
87impl From<&str> for JsonIndent {
88 fn from(s: &str) -> Self {
89 JsonIndent::Custom(s.to_string())
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct JsonOptions {
96 pub indent: JsonIndent,
98 pub highlight: bool,
100 pub sort_keys: bool,
102 pub ensure_ascii: bool,
104 pub no_wrap: bool,
106}
107
108impl Default for JsonOptions {
109 fn default() -> Self {
110 JsonOptions {
111 indent: JsonIndent::Spaces(2),
112 highlight: true,
113 sort_keys: false,
114 ensure_ascii: false,
115 no_wrap: true, }
117 }
118}
119
120#[derive(Debug, Clone)]
151pub struct Json {
152 text: Text,
154 value: Value,
156 options: JsonOptions,
158}
159
160impl Json {
161 #[allow(clippy::should_implement_trait)]
171 pub fn from_str(json: &str) -> Result<Self, JsonError> {
172 Self::from_str_with_options(json, JsonOptions::default())
173 }
174
175 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, JsonError> {
191 let content = fs::read_to_string(path)?;
192 Self::from_str(&content)
193 }
194
195 pub fn from_data<T: Serialize>(data: &T) -> Result<Self, JsonError> {
205 let value = serde_json::to_value(data).map_err(JsonError::Serialize)?;
206 Self::from_value(value, JsonOptions::default())
207 }
208
209 pub fn from_str_with_options(json: &str, options: JsonOptions) -> Result<Self, JsonError> {
211 let value: Value = serde_json::from_str(json).map_err(JsonError::Parse)?;
212 Self::from_value(value, options)
213 }
214
215 fn from_value(value: Value, options: JsonOptions) -> Result<Self, JsonError> {
217 let text = Self::render_value(&value, &options);
218 Ok(Json {
219 text,
220 value,
221 options,
222 })
223 }
224
225 fn render_value(value: &Value, options: &JsonOptions) -> Text {
227 let value_to_render = if options.sort_keys {
229 sort_json_keys(value)
230 } else {
231 value.clone()
232 };
233
234 let formatted = format_json(&value_to_render, &options.indent, options.ensure_ascii);
236
237 let mut text = if options.highlight {
239 let highlighter = JsonHighlighter::new();
240 let spans = highlighter.highlight(&formatted);
241 Text::from_spans(spans)
242 } else {
243 Text::plain(formatted)
244 };
245
246 if options.no_wrap {
248 text = text.no_wrap();
249 }
250
251 text
252 }
253
254 fn rerender(&self) -> Self {
256 let text = Self::render_value(&self.value, &self.options);
257 Json {
258 text,
259 value: self.value.clone(),
260 options: self.options.clone(),
261 }
262 }
263
264 pub fn indent(mut self, spaces: usize) -> Self {
274 self.options.indent = JsonIndent::from(spaces);
275 self.rerender()
276 }
277
278 pub fn indent_with(mut self, indent_str: &str) -> Self {
286 self.options.indent = JsonIndent::Custom(indent_str.to_string());
287 self.rerender()
288 }
289
290 pub fn compact(mut self) -> Self {
299 self.options.indent = JsonIndent::Compact;
300 self.rerender()
301 }
302
303 pub fn sort_keys(mut self) -> Self {
312 self.options.sort_keys = true;
313 self.rerender()
314 }
315
316 pub fn ensure_ascii(mut self) -> Self {
327 self.options.ensure_ascii = true;
328 self.rerender()
329 }
330
331 pub fn no_highlight(mut self) -> Self {
339 self.options.highlight = false;
340 self.rerender()
341 }
342
343 pub fn wrap(mut self) -> Self {
351 self.options.no_wrap = false;
352 self.rerender()
353 }
354
355 pub fn plain_text(&self) -> String {
357 self.text.plain_text()
358 }
359}
360
361impl Renderable for Json {
362 fn render(&self, context: &RenderContext) -> Vec<Segment> {
363 self.text.render(context)
364 }
365}
366
367const ERR_SERIALIZE_COMPACT: &str = "Failed to serialize JSON value to string";
369const ERR_SERIALIZE_FORMATTED: &str = "Failed to serialize JSON value with custom formatter";
370const ERR_INVALID_UTF8: &str = "JSON serialization produced invalid UTF-8";
371
372fn format_json(value: &Value, indent: &JsonIndent, ensure_ascii: bool) -> String {
374 match indent {
375 JsonIndent::Compact => {
376 let result = serde_json::to_string(value).expect(ERR_SERIALIZE_COMPACT);
378 if ensure_ascii {
379 escape_non_ascii(&result)
380 } else {
381 result
382 }
383 }
384 JsonIndent::Spaces(n) => {
385 let indent_bytes = " ".repeat(*n).into_bytes();
386 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
387 let mut buf = Vec::new();
388 let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
389 value.serialize(&mut ser).expect(ERR_SERIALIZE_FORMATTED);
390 let result = String::from_utf8(buf).expect(ERR_INVALID_UTF8);
391
392 if ensure_ascii {
393 escape_non_ascii(&result)
394 } else {
395 result
396 }
397 }
398 JsonIndent::Custom(s) => {
399 let indent_bytes = s.as_bytes().to_vec();
400 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
401 let mut buf = Vec::new();
402 let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
403 value.serialize(&mut ser).expect(ERR_SERIALIZE_FORMATTED);
404 let result = String::from_utf8(buf).expect(ERR_INVALID_UTF8);
405
406 if ensure_ascii {
407 escape_non_ascii(&result)
408 } else {
409 result
410 }
411 }
412 }
413}
414
415fn escape_non_ascii(s: &str) -> String {
417 let mut result = String::with_capacity(s.len());
418 for c in s.chars() {
419 if c.is_ascii() {
420 result.push(c);
421 } else {
422 let code = c as u32;
424 if code <= 0xFFFF {
425 result.push_str(&format!("\\u{:04x}", code));
426 } else {
427 let code = code - 0x10000;
429 let high = 0xD800 + (code >> 10);
430 let low = 0xDC00 + (code & 0x3FF);
431 result.push_str(&format!("\\u{:04x}\\u{:04x}", high, low));
432 }
433 }
434 }
435 result
436}
437
438fn sort_json_keys(value: &Value) -> Value {
440 match value {
441 Value::Object(map) => {
442 let mut sorted: Vec<_> = map.iter().collect();
443 sorted.sort_by(|a, b| a.0.cmp(b.0));
444 let sorted_map: serde_json::Map<String, Value> = sorted
445 .into_iter()
446 .map(|(k, v)| (k.clone(), sort_json_keys(v)))
447 .collect();
448 Value::Object(sorted_map)
449 }
450 Value::Array(arr) => Value::Array(arr.iter().map(sort_json_keys).collect()),
451 _ => value.clone(),
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use std::io::Write;
459 use tempfile::NamedTempFile;
460
461 #[test]
462 fn test_json_from_str() {
463 let json = Json::from_str(r#"{"name": "Alice"}"#);
464 assert!(json.is_ok());
465 }
466
467 #[test]
468 fn test_json_invalid() {
469 let json = Json::from_str("not valid json");
470 assert!(json.is_err());
471 }
472
473 #[test]
474 fn test_json_from_data() {
475 #[derive(Serialize)]
476 struct User {
477 name: String,
478 age: u32,
479 }
480
481 let user = User {
482 name: "Bob".to_string(),
483 age: 25,
484 };
485 let json = Json::from_data(&user);
486 assert!(json.is_ok());
487 let text = json.unwrap().plain_text();
488 assert!(text.contains("Bob"));
489 assert!(text.contains("25"));
490 }
491
492 #[test]
493 fn test_json_sort_keys() {
494 let json = Json::from_str(r#"{"z": 1, "a": 2, "m": 3}"#)
495 .unwrap()
496 .sort_keys();
497 let text = json.plain_text();
498 let a_pos = text.find("\"a\"").unwrap();
499 let m_pos = text.find("\"m\"").unwrap();
500 let z_pos = text.find("\"z\"").unwrap();
501 assert!(a_pos < m_pos);
502 assert!(m_pos < z_pos);
503 }
504
505 #[test]
506 fn test_json_nested() {
507 let json = Json::from_str(r#"{"user": {"name": "Alice", "scores": [1, 2, 3]}}"#);
508 assert!(json.is_ok());
509 }
510
511 #[test]
512 fn test_json_types() {
513 let json = Json::from_str(
514 r#"{"str": "hello", "num": 42, "float": 3.14, "bool": true, "null": null}"#,
515 );
516 assert!(json.is_ok());
517 }
518
519 #[test]
520 fn test_json_indent_spaces() {
521 let json = Json::from_str(r#"{"a": 1}"#).unwrap().indent(4);
522 let text = json.plain_text();
523 assert!(text.contains(" \"a\""));
525 }
526
527 #[test]
528 fn test_json_indent_tab() {
529 let json = Json::from_str(r#"{"a": 1}"#).unwrap().indent_with("\t");
530 let text = json.plain_text();
531 assert!(text.contains("\t\"a\""));
533 }
534
535 #[test]
536 fn test_json_compact() {
537 let json = Json::from_str(r#"{"a": 1, "b": 2}"#).unwrap().compact();
538 let text = json.plain_text();
539 assert!(!text.contains('\n'));
541 assert_eq!(text, r#"{"a":1,"b":2}"#);
542 }
543
544 #[test]
545 fn test_json_ensure_ascii() {
546 let json = Json::from_str(r#"{"emoji": "😀"}"#).unwrap().ensure_ascii();
547 let text = json.plain_text();
548 assert!(text.contains("\\u"));
550 assert!(!text.contains("😀"));
551 }
552
553 #[test]
554 fn test_json_no_highlight() {
555 let json = Json::from_str(r#"{"a": 1}"#).unwrap().no_highlight();
556 assert!(json.plain_text().contains("\"a\""));
558 }
559
560 #[test]
561 fn test_json_no_wrap_default() {
562 let json = Json::from_str(r#"{"a": 1}"#).unwrap();
563 assert_eq!(json.text.overflow, Overflow::Visible);
565 }
566
567 #[test]
568 fn test_json_wrap() {
569 let json = Json::from_str(r#"{"a": 1}"#).unwrap().wrap();
570 assert_eq!(json.text.overflow, Overflow::Wrap);
572 }
573
574 #[test]
575 fn test_json_from_file() {
576 let mut temp_file = NamedTempFile::new().unwrap();
578 writeln!(temp_file, r#"{{"name": "test", "value": 42}}"#).unwrap();
579
580 let json = Json::from_file(temp_file.path());
581 assert!(json.is_ok());
582 let text = json.unwrap().plain_text();
583 assert!(text.contains("test"));
584 assert!(text.contains("42"));
585 }
586
587 #[test]
588 fn test_json_from_file_not_found() {
589 let json = Json::from_file("/nonexistent/path/to/file.json");
590 assert!(json.is_err());
591 }
592}