Skip to main content

sem_core/format/
json.rs

1use crate::parser::differ::DiffResult;
2use serde_json::{json, Value};
3
4pub fn diff_json_value(result: &DiffResult) -> Value {
5    let changes: Vec<Value> = result
6        .changes
7        .iter()
8        .map(|c| {
9            json!({
10                "entityId": c.entity_id,
11                "changeType": c.change_type,
12                "entityType": c.entity_type,
13                "entityName": c.entity_name,
14                "startLine": c.start_line,
15                "endLine": c.end_line,
16                "oldStartLine": c.old_start_line,
17                "oldEndLine": c.old_end_line,
18                "oldEntityName": c.old_entity_name,
19                "filePath": c.file_path,
20                "oldFilePath": c.old_file_path,
21                "oldParentId": c.old_parent_id,
22                "beforeContent": c.before_content,
23                "afterContent": c.after_content,
24                "commitSha": c.commit_sha,
25                "author": c.author,
26                "structuralChange": c.structural_change,
27            })
28        })
29        .collect();
30
31    json!({
32        "summary": {
33            "fileCount": result.file_count,
34            "added": result.added_count,
35            "modified": result.modified_count,
36            "deleted": result.deleted_count,
37            "moved": result.moved_count,
38            "renamed": result.renamed_count,
39            "reordered": result.reordered_count,
40            "orphan": result.orphan_count,
41            "total": result.changes.len(),
42        },
43        "changes": changes,
44    })
45}
46
47pub fn format_diff_json(result: &DiffResult) -> String {
48    serde_json::to_string(&diff_json_value(result)).unwrap_or_default()
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::model::change::{ChangeType, SemanticChange};
55
56    #[test]
57    fn diff_json_value_matches_cli_envelope() {
58        let result = DiffResult {
59            changes: vec![SemanticChange {
60                id: "internal-change-id".to_string(),
61                entity_id: "src/lib.rs::function::foo".to_string(),
62                change_type: ChangeType::Modified,
63                entity_type: "function".to_string(),
64                entity_name: "foo".to_string(),
65                entity_line: 12,
66                start_line: 12,
67                end_line: 12,
68                old_start_line: None,
69                old_end_line: None,
70                parent_name: Some("module".to_string()),
71                file_path: "src/lib.rs".to_string(),
72                old_entity_name: Some("bar".to_string()),
73                old_file_path: Some("src/old.rs".to_string()),
74                old_parent_id: Some("old-parent".to_string()),
75                before_content: Some("fn bar() {}".to_string()),
76                after_content: Some("fn foo() {}".to_string()),
77                commit_sha: Some("abc123".to_string()),
78                author: Some("Ada".to_string()),
79                timestamp: Some("2026-05-26".to_string()),
80                structural_change: Some(true),
81            }],
82            file_count: 1,
83            added_count: 0,
84            modified_count: 1,
85            deleted_count: 0,
86            moved_count: 0,
87            renamed_count: 0,
88            reordered_count: 0,
89            orphan_count: 0,
90            total_entities_before: 1,
91            total_entities_after: 1,
92        };
93
94        let value = diff_json_value(&result);
95
96        assert_eq!(
97            value,
98            json!({
99                "summary": {
100                    "fileCount": 1,
101                    "added": 0,
102                    "modified": 1,
103                    "deleted": 0,
104                    "moved": 0,
105                    "renamed": 0,
106                    "reordered": 0,
107                    "orphan": 0,
108                    "total": 1,
109                },
110                "changes": [{
111                    "entityId": "src/lib.rs::function::foo",
112                    "changeType": "modified",
113                    "entityType": "function",
114                    "entityName": "foo",
115                    "startLine": 12,
116                    "endLine": 12,
117                    "oldStartLine": null,
118                    "oldEndLine": null,
119                    "oldEntityName": "bar",
120                    "filePath": "src/lib.rs",
121                    "oldFilePath": "src/old.rs",
122                    "oldParentId": "old-parent",
123                    "beforeContent": "fn bar() {}",
124                    "afterContent": "fn foo() {}",
125                    "commitSha": "abc123",
126                    "author": "Ada",
127                    "structuralChange": true,
128                }],
129            })
130        );
131    }
132}