1use ripsed_core::diff::{OpResult, Summary};
2use ripsed_core::error::RipsedError;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct JsonResponse {
8 pub version: String,
9 pub success: bool,
10 pub dry_run: bool,
11 pub summary: Summary,
12 pub results: Vec<OpResult>,
13 pub errors: Vec<RipsedError>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct UndoResponse {
19 pub version: String,
20 pub success: bool,
21 pub undo: UndoSummary,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct UndoSummary {
26 pub operations_reverted: usize,
27 pub files_restored: usize,
28 pub log_entries_remaining: usize,
29}
30
31impl UndoResponse {
32 pub fn to_json(&self) -> String {
34 serde_json::to_string_pretty(self)
35 .unwrap_or_else(|_| r#"{"version":"1","success":false}"#.to_string())
36 }
37}
38
39impl JsonResponse {
40 pub fn success(dry_run: bool, summary: Summary, results: Vec<OpResult>) -> Self {
42 Self {
43 version: "1".to_string(),
44 success: true,
45 dry_run,
46 summary,
47 results,
48 errors: vec![],
49 }
50 }
51
52 pub fn error(errors: Vec<RipsedError>) -> Self {
54 Self {
55 version: "1".to_string(),
56 success: false,
57 dry_run: false,
58 summary: Summary::default(),
59 results: vec![],
60 errors,
61 }
62 }
63
64 pub fn to_json(&self) -> String {
66 serde_json::to_string_pretty(self).unwrap_or_else(|_| {
67 r#"{"version":"1","success":false,"errors":[{"code":"internal_error","message":"Failed to serialize response"}]}"#.to_string()
68 })
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use ripsed_core::diff::{Change, FileChanges};
76
77 #[test]
78 fn success_response_has_correct_fields() {
79 let summary = Summary {
80 files_matched: 3,
81 files_modified: 2,
82 total_replacements: 5,
83 };
84 let results = vec![OpResult {
85 operation_index: 0,
86 files: vec![FileChanges {
87 path: "src/lib.rs".into(),
88 changes: vec![Change {
89 line: 1,
90 before: "old".into(),
91 after: Some("new".into()),
92 context: None,
93 }],
94 }],
95 }];
96 let resp = JsonResponse::success(true, summary, results);
97 assert_eq!(resp.version, "1");
98 assert!(resp.success);
99 assert!(resp.dry_run);
100 assert_eq!(resp.summary.files_matched, 3);
101 assert_eq!(resp.results.len(), 1);
102 assert!(resp.errors.is_empty());
103 }
104
105 #[test]
106 fn error_response_has_correct_fields() {
107 let err = RipsedError::invalid_request("bad input", "fix it");
108 let resp = JsonResponse::error(vec![err]);
109 assert_eq!(resp.version, "1");
110 assert!(!resp.success);
111 assert!(!resp.dry_run);
112 assert_eq!(resp.summary, Summary::default());
113 assert!(resp.results.is_empty());
114 assert_eq!(resp.errors.len(), 1);
115 assert_eq!(resp.errors[0].message, "bad input");
116 }
117
118 #[test]
119 fn to_json_produces_valid_json() {
120 let resp = JsonResponse::success(false, Summary::default(), vec![]);
121 let json_str = resp.to_json();
122 let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
123 assert_eq!(parsed["version"], "1");
124 assert_eq!(parsed["success"], true);
125 assert_eq!(parsed["dry_run"], false);
126 }
127
128 #[test]
129 fn to_json_error_response_includes_errors() {
130 let err = RipsedError::internal_error("oops");
131 let resp = JsonResponse::error(vec![err]);
132 let json_str = resp.to_json();
133 let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
134 assert_eq!(parsed["success"], false);
135 assert_eq!(parsed["errors"][0]["code"], "internal_error");
136 }
137
138 #[test]
139 fn response_roundtrips_through_json() {
140 let resp = JsonResponse::success(
141 true,
142 Summary {
143 files_matched: 1,
144 files_modified: 0,
145 total_replacements: 2,
146 },
147 vec![],
148 );
149 let json_str = resp.to_json();
150 let deserialized: JsonResponse = serde_json::from_str(&json_str).unwrap();
151 assert_eq!(deserialized.version, "1");
152 assert!(deserialized.success);
153 assert!(deserialized.dry_run);
154 assert_eq!(deserialized.summary.total_replacements, 2);
155 }
156
157 #[test]
158 fn undo_response_serializes() {
159 let resp = UndoResponse {
160 version: "1".into(),
161 success: true,
162 undo: UndoSummary {
163 operations_reverted: 2,
164 files_restored: 3,
165 log_entries_remaining: 8,
166 },
167 };
168 let json = serde_json::to_value(&resp).unwrap();
169 assert_eq!(json["undo"]["operations_reverted"], 2);
170 assert_eq!(json["undo"]["files_restored"], 3);
171 assert_eq!(json["undo"]["log_entries_remaining"], 8);
172 }
173}