1use crate::JpxEngine;
8use crate::error::{EngineError, Result};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct StatsResult {
32 pub root_type: String,
34 pub size_bytes: usize,
36 pub size_human: String,
38 pub depth: usize,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub length: Option<usize>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub key_count: Option<usize>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub fields: Option<Vec<FieldAnalysis>>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub type_distribution: Option<HashMap<String, usize>>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct FieldAnalysis {
60 pub name: String,
62 pub field_type: String,
64 pub null_count: usize,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub unique_count: Option<usize>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct PathInfo {
93 pub path: String,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub path_type: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub value: Option<Value>,
101}
102
103impl JpxEngine {
108 pub fn format_json(&self, input: &str, indent: usize) -> Result<String> {
131 let value: Value =
132 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
133
134 if indent == 0 {
135 serde_json::to_string(&value).map_err(|e| EngineError::Internal(e.to_string()))
136 } else {
137 let indent_bytes = vec![b' '; indent];
138 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
139 let mut buf = Vec::new();
140 let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
141 value
142 .serialize(&mut ser)
143 .map_err(|e| EngineError::Internal(e.to_string()))?;
144 String::from_utf8(buf).map_err(|e| EngineError::Internal(e.to_string()))
145 }
146 }
147
148 pub fn diff(&self, source: &str, target: &str) -> Result<Value> {
169 let source_val: Value =
170 serde_json::from_str(source).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
171 let target_val: Value =
172 serde_json::from_str(target).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
173
174 let patch = json_patch::diff(&source_val, &target_val);
175 serde_json::to_value(&patch).map_err(|e| EngineError::Internal(e.to_string()))
176 }
177
178 pub fn patch(&self, input: &str, patch: &str) -> Result<Value> {
199 let mut doc: Value =
200 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
201 let patch: json_patch::Patch =
202 serde_json::from_str(patch).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
203
204 json_patch::patch(&mut doc, &patch)
205 .map_err(|e| EngineError::evaluation_failed(e.to_string()))?;
206
207 Ok(doc)
208 }
209
210 pub fn merge(&self, input: &str, patch: &str) -> Result<Value> {
236 let mut doc: Value =
237 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
238 let patch_val: Value =
239 serde_json::from_str(patch).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
240
241 json_patch::merge(&mut doc, &patch_val);
242 Ok(doc)
243 }
244
245 pub fn keys(&self, input: &str, recursive: bool) -> Result<Vec<String>> {
268 let value: Value =
269 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
270
271 let mut keys = Vec::new();
272 if recursive {
273 extract_keys_recursive(&value, "", &mut keys);
274 } else if let Value::Object(map) = &value {
275 keys = map.keys().cloned().collect();
276 keys.sort();
277 }
278 Ok(keys)
279 }
280
281 pub fn paths(
302 &self,
303 input: &str,
304 include_types: bool,
305 include_values: bool,
306 ) -> Result<Vec<PathInfo>> {
307 let value: Value =
308 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
309
310 let mut paths = Vec::new();
311 extract_paths(&value, "", include_types, include_values, &mut paths);
312 Ok(paths)
313 }
314
315 pub fn stats(&self, input: &str) -> Result<StatsResult> {
336 let value: Value =
337 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
338
339 let size_bytes = input.len();
340 let depth = calculate_depth(&value);
341
342 let (length, key_count, fields, type_distribution) = match &value {
343 Value::Array(arr) => {
344 let type_dist = calculate_type_distribution(arr);
345 let field_analysis = if arr.iter().all(|v| v.is_object()) {
346 Some(analyze_array_fields(arr))
347 } else {
348 None
349 };
350 (Some(arr.len()), None, field_analysis, Some(type_dist))
351 }
352 Value::Object(map) => (None, Some(map.len()), None, None),
353 _ => (None, None, None, None),
354 };
355
356 Ok(StatsResult {
357 root_type: json_type_name(&value).to_string(),
358 size_bytes,
359 size_human: format_size(size_bytes),
360 depth,
361 length,
362 key_count,
363 fields,
364 type_distribution,
365 })
366 }
367}
368
369fn extract_keys_recursive(value: &Value, prefix: &str, keys: &mut Vec<String>) {
375 match value {
376 Value::Object(map) => {
377 for (k, v) in map {
378 let path = if prefix.is_empty() {
379 k.clone()
380 } else {
381 format!("{}.{}", prefix, k)
382 };
383 keys.push(path.clone());
384 extract_keys_recursive(v, &path, keys);
385 }
386 }
387 Value::Array(arr) => {
388 for (i, v) in arr.iter().enumerate() {
389 let path = format!("{}.{}", prefix, i);
390 extract_keys_recursive(v, &path, keys);
391 }
392 }
393 _ => {}
394 }
395}
396
397fn extract_paths(
399 value: &Value,
400 prefix: &str,
401 include_types: bool,
402 include_values: bool,
403 paths: &mut Vec<PathInfo>,
404) {
405 let current_path = if prefix.is_empty() {
406 "@".to_string()
407 } else {
408 prefix.to_string()
409 };
410
411 match value {
412 Value::Object(map) => {
413 paths.push(PathInfo {
414 path: current_path.clone(),
415 path_type: if include_types {
416 Some("object".to_string())
417 } else {
418 None
419 },
420 value: None,
421 });
422 for (k, v) in map {
423 let new_prefix = if prefix.is_empty() {
424 k.clone()
425 } else {
426 format!("{}.{}", prefix, k)
427 };
428 extract_paths(v, &new_prefix, include_types, include_values, paths);
429 }
430 }
431 Value::Array(arr) => {
432 paths.push(PathInfo {
433 path: current_path.clone(),
434 path_type: if include_types {
435 Some("array".to_string())
436 } else {
437 None
438 },
439 value: None,
440 });
441 for (i, v) in arr.iter().enumerate() {
442 let new_prefix = format!("{}.{}", prefix, i);
443 extract_paths(v, &new_prefix, include_types, include_values, paths);
444 }
445 }
446 _ => {
447 paths.push(PathInfo {
448 path: current_path,
449 path_type: if include_types {
450 Some(json_type_name(value).to_string())
451 } else {
452 None
453 },
454 value: if include_values {
455 Some(value.clone())
456 } else {
457 None
458 },
459 });
460 }
461 }
462}
463
464fn calculate_depth(value: &Value) -> usize {
466 match value {
467 Value::Object(map) => 1 + map.values().map(calculate_depth).max().unwrap_or(0),
468 Value::Array(arr) => 1 + arr.iter().map(calculate_depth).max().unwrap_or(0),
469 _ => 0,
470 }
471}
472
473fn json_type_name(value: &Value) -> &'static str {
475 match value {
476 Value::Null => "null",
477 Value::Bool(_) => "boolean",
478 Value::Number(_) => "number",
479 Value::String(_) => "string",
480 Value::Array(_) => "array",
481 Value::Object(_) => "object",
482 }
483}
484
485fn calculate_type_distribution(arr: &[Value]) -> HashMap<String, usize> {
487 let mut dist = HashMap::new();
488 for item in arr {
489 *dist.entry(json_type_name(item).to_string()).or_insert(0) += 1;
490 }
491 dist
492}
493
494fn analyze_array_fields(arr: &[Value]) -> Vec<FieldAnalysis> {
496 let mut field_types: HashMap<String, HashMap<String, usize>> = HashMap::new();
497 let mut field_null_counts: HashMap<String, usize> = HashMap::new();
498 let mut field_values: HashMap<String, Vec<Value>> = HashMap::new();
499
500 for item in arr {
501 if let Value::Object(map) = item {
502 for (k, v) in map {
503 let types = field_types.entry(k.clone()).or_default();
504 *types.entry(json_type_name(v).to_string()).or_insert(0) += 1;
505
506 if v.is_null() {
507 *field_null_counts.entry(k.clone()).or_insert(0) += 1;
508 }
509
510 let values = field_values.entry(k.clone()).or_default();
512 if values.len() < 100 && !values.contains(v) {
513 values.push(v.clone());
514 }
515 }
516 }
517 }
518
519 let mut fields: Vec<FieldAnalysis> = field_types
520 .into_iter()
521 .map(|(name, types)| {
522 let predominant_type = types
523 .into_iter()
524 .max_by_key(|(_, count)| *count)
525 .map(|(t, _)| t)
526 .unwrap_or_else(|| "unknown".to_string());
527
528 let null_count = field_null_counts.get(&name).copied().unwrap_or(0);
529 let unique_count = field_values.get(&name).map(|v| v.len());
530
531 FieldAnalysis {
532 name,
533 field_type: predominant_type,
534 null_count,
535 unique_count,
536 }
537 })
538 .collect();
539
540 fields.sort_by(|a, b| a.name.cmp(&b.name));
541 fields
542}
543
544fn format_size(bytes: usize) -> String {
546 const KB: usize = 1024;
547 const MB: usize = KB * 1024;
548 const GB: usize = MB * 1024;
549
550 if bytes >= GB {
551 format!("{:.2} GB", bytes as f64 / GB as f64)
552 } else if bytes >= MB {
553 format!("{:.2} MB", bytes as f64 / MB as f64)
554 } else if bytes >= KB {
555 format!("{:.2} KB", bytes as f64 / KB as f64)
556 } else {
557 format!("{} bytes", bytes)
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use crate::JpxEngine;
564 use serde_json::json;
565
566 #[test]
567 fn test_format_json() {
568 let engine = JpxEngine::new();
569
570 let formatted = engine.format_json(r#"{"a":1,"b":2}"#, 2).unwrap();
571 assert!(formatted.contains('\n'));
572
573 let compact = engine.format_json(r#"{"a":1,"b":2}"#, 0).unwrap();
574 assert!(!compact.contains('\n'));
575 }
576
577 #[test]
578 fn test_diff() {
579 let engine = JpxEngine::new();
580
581 let patch = engine.diff(r#"{"a": 1}"#, r#"{"a": 2}"#).unwrap();
582
583 let patch_arr = patch.as_array().unwrap();
584 assert!(!patch_arr.is_empty());
585 }
586
587 #[test]
588 fn test_patch() {
589 let engine = JpxEngine::new();
590
591 let result = engine
592 .patch(
593 r#"{"a": 1}"#,
594 r#"[{"op": "replace", "path": "/a", "value": 2}]"#,
595 )
596 .unwrap();
597
598 assert_eq!(result, json!({"a": 2}));
599 }
600
601 #[test]
602 fn test_merge() {
603 let engine = JpxEngine::new();
604
605 let result = engine
606 .merge(r#"{"a": 1, "b": 2}"#, r#"{"b": 3, "c": 4}"#)
607 .unwrap();
608
609 assert_eq!(result, json!({"a": 1, "b": 3, "c": 4}));
610 }
611
612 #[test]
613 fn test_keys() {
614 let engine = JpxEngine::new();
615
616 let keys = engine.keys(r#"{"a": 1, "b": {"c": 2}}"#, false).unwrap();
617 assert_eq!(keys, vec!["a", "b"]);
618
619 let recursive_keys = engine.keys(r#"{"a": 1, "b": {"c": 2}}"#, true).unwrap();
620 assert!(recursive_keys.contains(&"b.c".to_string()));
621 }
622
623 #[test]
624 fn test_paths() {
625 let engine = JpxEngine::new();
626
627 let paths = engine.paths(r#"{"a": 1}"#, true, false).unwrap();
628 assert!(!paths.is_empty());
629 }
630
631 #[test]
632 fn test_stats() {
633 let engine = JpxEngine::new();
634
635 let stats = engine.stats(r#"[1, 2, 3]"#).unwrap();
636 assert_eq!(stats.root_type, "array");
637 assert_eq!(stats.length, Some(3));
638 }
639
640 #[test]
645 fn test_format_json_invalid_json() {
646 let engine = JpxEngine::new();
647 let result = engine.format_json("not json", 2);
648 assert!(result.is_err());
649 }
650
651 #[test]
652 fn test_format_json_indent_4() {
653 let engine = JpxEngine::new();
654 let formatted = engine.format_json(r#"{"a":1}"#, 4).unwrap();
655 let lines: Vec<&str> = formatted.lines().collect();
657 assert!(lines.len() > 1);
658 assert!(lines[1].starts_with(" "));
659 }
660
661 #[test]
662 fn test_format_json_preserves_data() {
663 let engine = JpxEngine::new();
664 let input = r#"{"name":"alice","age":30,"active":true,"score":null}"#;
665 let formatted = engine.format_json(input, 2).unwrap();
666 let original: serde_json::Value = serde_json::from_str(input).unwrap();
667 let roundtripped: serde_json::Value = serde_json::from_str(&formatted).unwrap();
668 assert_eq!(original, roundtripped);
669 }
670
671 #[test]
676 fn test_diff_identical() {
677 let engine = JpxEngine::new();
678 let patch = engine.diff(r#"{"a":1,"b":2}"#, r#"{"a":1,"b":2}"#).unwrap();
679 let patch_arr = patch.as_array().unwrap();
680 assert!(patch_arr.is_empty());
681 }
682
683 #[test]
684 fn test_diff_added_key() {
685 let engine = JpxEngine::new();
686 let patch = engine.diff(r#"{"a":1}"#, r#"{"a":1,"b":2}"#).unwrap();
687 let patch_arr = patch.as_array().unwrap();
688 assert!(!patch_arr.is_empty());
689 let has_add = patch_arr
690 .iter()
691 .any(|op| op.get("op").and_then(|v| v.as_str()) == Some("add"));
692 assert!(has_add, "Expected an 'add' operation in the patch");
693 }
694
695 #[test]
696 fn test_diff_removed_key() {
697 let engine = JpxEngine::new();
698 let patch = engine.diff(r#"{"a":1,"b":2}"#, r#"{"a":1}"#).unwrap();
699 let patch_arr = patch.as_array().unwrap();
700 assert!(!patch_arr.is_empty());
701 let has_remove = patch_arr
702 .iter()
703 .any(|op| op.get("op").and_then(|v| v.as_str()) == Some("remove"));
704 assert!(has_remove, "Expected a 'remove' operation in the patch");
705 }
706
707 #[test]
708 fn test_diff_nested_change() {
709 let engine = JpxEngine::new();
710 let patch = engine.diff(r#"{"a":{"b":1}}"#, r#"{"a":{"b":2}}"#).unwrap();
711 let patch_arr = patch.as_array().unwrap();
712 assert!(!patch_arr.is_empty());
713 let targets_nested = patch_arr.iter().any(|op| {
715 op.get("path")
716 .and_then(|v| v.as_str())
717 .map(|p| p.contains("/a/b"))
718 .unwrap_or(false)
719 });
720 assert!(targets_nested, "Expected a patch operation targeting /a/b");
721 }
722
723 #[test]
724 fn test_diff_invalid_json() {
725 let engine = JpxEngine::new();
726 let result = engine.diff("not json", r#"{"a":1}"#);
727 assert!(result.is_err());
728 }
729
730 #[test]
735 fn test_patch_add_operation() {
736 let engine = JpxEngine::new();
737 let result = engine
738 .patch(r#"{"a":1}"#, r#"[{"op":"add","path":"/b","value":2}]"#)
739 .unwrap();
740 assert_eq!(result, json!({"a": 1, "b": 2}));
741 }
742
743 #[test]
744 fn test_patch_remove_operation() {
745 let engine = JpxEngine::new();
746 let result = engine
747 .patch(r#"{"a":1,"b":2}"#, r#"[{"op":"remove","path":"/b"}]"#)
748 .unwrap();
749 assert_eq!(result, json!({"a": 1}));
750 }
751
752 #[test]
753 fn test_patch_invalid_patch() {
754 let engine = JpxEngine::new();
755 let result = engine.patch(r#"{"a":1}"#, r#"not a patch"#);
756 assert!(result.is_err());
757 }
758
759 #[test]
760 fn test_patch_empty_patch() {
761 let engine = JpxEngine::new();
762 let result = engine.patch(r#"{"a":1,"b":2}"#, r#"[]"#).unwrap();
763 assert_eq!(result, json!({"a": 1, "b": 2}));
764 }
765
766 #[test]
771 fn test_merge_overlapping_keys() {
772 let engine = JpxEngine::new();
773 let result = engine
774 .merge(r#"{"a":1,"b":2}"#, r#"{"a":10,"b":20}"#)
775 .unwrap();
776 assert_eq!(result, json!({"a": 10, "b": 20}));
777 }
778
779 #[test]
780 fn test_merge_null_removes_key() {
781 let engine = JpxEngine::new();
782 let result = engine.merge(r#"{"a":1,"b":2}"#, r#"{"b":null}"#).unwrap();
783 assert_eq!(result, json!({"a": 1}));
784 }
785
786 #[test]
787 fn test_merge_nested_objects() {
788 let engine = JpxEngine::new();
789 let result = engine
790 .merge(r#"{"a":{"x":1,"y":2},"b":3}"#, r#"{"a":{"y":20,"z":30}}"#)
791 .unwrap();
792 assert_eq!(result, json!({"a": {"x": 1, "y": 20, "z": 30}, "b": 3}));
793 }
794
795 #[test]
796 fn test_merge_invalid_json() {
797 let engine = JpxEngine::new();
798 let result = engine.merge("not json", r#"{"a":1}"#);
799 assert!(result.is_err());
800 }
801
802 #[test]
807 fn test_keys_empty_object() {
808 let engine = JpxEngine::new();
809 let keys = engine.keys(r#"{}"#, false).unwrap();
810 assert!(keys.is_empty());
811 }
812
813 #[test]
814 fn test_keys_recursive_nested() {
815 let engine = JpxEngine::new();
816 let keys = engine.keys(r#"{"a":{"b":{"c":1}},"d":2}"#, true).unwrap();
817 assert!(keys.contains(&"a".to_string()));
818 assert!(keys.contains(&"a.b".to_string()));
819 assert!(keys.contains(&"a.b.c".to_string()));
820 assert!(keys.contains(&"d".to_string()));
821 }
822
823 #[test]
824 fn test_keys_non_object_non_recursive() {
825 let engine = JpxEngine::new();
826 let keys = engine.keys(r#"[1, 2, 3]"#, false).unwrap();
828 assert!(keys.is_empty());
829 }
830
831 #[test]
836 fn test_paths_with_types_and_values() {
837 let engine = JpxEngine::new();
838 let paths = engine
839 .paths(r#"{"name":"alice","age":30}"#, true, true)
840 .unwrap();
841 let name_path = paths.iter().find(|p| p.path == "name").unwrap();
843 assert_eq!(name_path.path_type, Some("string".to_string()));
844 assert_eq!(name_path.value, Some(json!("alice")));
845 let age_path = paths.iter().find(|p| p.path == "age").unwrap();
847 assert_eq!(age_path.path_type, Some("number".to_string()));
848 assert_eq!(age_path.value, Some(json!(30)));
849 }
850
851 #[test]
852 fn test_paths_root_is_at_sign() {
853 let engine = JpxEngine::new();
854 let paths = engine.paths(r#"{"a":1}"#, false, false).unwrap();
855 assert!(!paths.is_empty());
856 assert_eq!(paths[0].path, "@");
857 }
858
859 #[test]
860 fn test_paths_array_indices() {
861 let engine = JpxEngine::new();
862 let paths = engine.paths(r#"[10, 20, 30]"#, true, true).unwrap();
863 assert_eq!(paths[0].path, "@");
865 assert_eq!(paths[0].path_type, Some("array".to_string()));
866 let index_paths: Vec<&str> = paths.iter().map(|p| p.path.as_str()).collect();
868 assert!(index_paths.contains(&".0"));
869 assert!(index_paths.contains(&".1"));
870 assert!(index_paths.contains(&".2"));
871 }
872
873 #[test]
878 fn test_stats_object() {
879 let engine = JpxEngine::new();
880 let stats = engine
881 .stats(r#"{"name":"alice","age":30,"active":true}"#)
882 .unwrap();
883 assert_eq!(stats.root_type, "object");
884 assert_eq!(stats.key_count, Some(3));
885 assert!(stats.length.is_none());
886 }
887
888 #[test]
889 fn test_stats_empty_array() {
890 let engine = JpxEngine::new();
891 let stats = engine.stats(r#"[]"#).unwrap();
892 assert_eq!(stats.root_type, "array");
893 assert_eq!(stats.length, Some(0));
894 assert_eq!(stats.depth, 1);
895 }
896
897 #[test]
898 fn test_stats_array_of_objects_fields() {
899 let engine = JpxEngine::new();
900 let stats = engine
901 .stats(r#"[{"name":"alice","age":30},{"name":"bob","age":25}]"#)
902 .unwrap();
903 assert_eq!(stats.root_type, "array");
904 assert_eq!(stats.length, Some(2));
905 let fields = stats.fields.unwrap();
906 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
907 assert!(field_names.contains(&"name"));
908 assert!(field_names.contains(&"age"));
909 }
910
911 #[test]
912 fn test_stats_nested_depth() {
913 let engine = JpxEngine::new();
914 let stats = engine.stats(r#"{"a":{"b":{"c":{"d":1}}}}"#).unwrap();
916 assert_eq!(stats.depth, 4);
917 }
918
919 #[test]
920 fn test_stats_invalid_json() {
921 let engine = JpxEngine::new();
922 let result = engine.stats("not json");
923 assert!(result.is_err());
924 }
925
926 #[test]
931 fn test_calculate_depth_primitive() {
932 use super::calculate_depth;
933 assert_eq!(calculate_depth(&json!(42)), 0);
934 assert_eq!(calculate_depth(&json!("hello")), 0);
935 assert_eq!(calculate_depth(&json!(true)), 0);
936 assert_eq!(calculate_depth(&json!(null)), 0);
937 }
938
939 #[test]
940 fn test_calculate_depth_nested() {
941 use super::calculate_depth;
942 let value = json!({"a": {"b": {"c": 1}}});
944 assert_eq!(calculate_depth(&value), 3);
945 }
946
947 #[test]
948 fn test_format_size_bytes() {
949 use super::format_size;
950 assert_eq!(format_size(100), "100 bytes");
951 assert_eq!(format_size(0), "0 bytes");
952 assert_eq!(format_size(1023), "1023 bytes");
953 }
954
955 #[test]
956 fn test_format_size_kb() {
957 use super::format_size;
958 let result = format_size(1024);
959 assert!(
960 result.contains("KB"),
961 "Expected KB in '{}' for 1024 bytes",
962 result
963 );
964 let result = format_size(2048);
965 assert!(
966 result.contains("KB"),
967 "Expected KB in '{}' for 2048 bytes",
968 result
969 );
970 }
971
972 #[test]
973 fn test_format_size_mb() {
974 use super::format_size;
975 let result = format_size(1024 * 1024);
976 assert!(result.contains("MB"), "Expected MB in '{}' for 1MB", result);
977 let result = format_size(2 * 1024 * 1024);
978 assert!(result.contains("MB"), "Expected MB in '{}' for 2MB", result);
979 }
980}