1use axum::{
10 extract::{Path, Query},
11 http::StatusCode,
12 response::{IntoResponse, Response},
13 Json,
14};
15use mockforge_core::{
16 ai_contract_diff::{CapturedRequest, ContractDiffAnalyzer, ContractDiffConfig},
17 openapi::OpenApiSpec,
18 request_capture::{get_global_capture_manager, CaptureQuery},
19 Error,
20};
21use serde::Deserialize;
22use serde_json::json;
23use std::collections::HashMap;
24
25fn error_response(error: Error) -> Response {
27 (
28 StatusCode::INTERNAL_SERVER_ERROR,
29 Json(json!({
30 "success": false,
31 "error": error.to_string()
32 })),
33 )
34 .into_response()
35}
36
37pub async fn upload_request(Json(payload): Json<UploadRequestPayload>) -> impl IntoResponse {
39 let request = CapturedRequest::new(&payload.method, &payload.path, "manual_upload")
40 .with_headers(payload.headers.unwrap_or_default())
41 .with_query_params(payload.query_params.unwrap_or_default());
42
43 let request = if let Some(body) = payload.body {
44 request.with_body(body)
45 } else {
46 request
47 };
48
49 let request = if let Some(status_code) = payload.status_code {
50 request.with_response(status_code, payload.response_body)
51 } else {
52 request
53 };
54
55 let capture_manager = match get_global_capture_manager() {
57 Some(manager) => manager,
58 None => {
59 return (
60 StatusCode::INTERNAL_SERVER_ERROR,
61 Json(json!({
62 "success": false,
63 "error": "Capture manager not initialized"
64 })),
65 )
66 .into_response();
67 }
68 };
69
70 match capture_manager.capture(request).await {
71 Ok(capture_id) => (
72 StatusCode::OK,
73 Json(json!({
74 "success": true,
75 "capture_id": capture_id,
76 "message": "Request captured successfully"
77 })),
78 )
79 .into_response(),
80 Err(e) => (
81 StatusCode::INTERNAL_SERVER_ERROR,
82 Json(json!({
83 "success": false,
84 "error": e.to_string()
85 })),
86 )
87 .into_response(),
88 }
89}
90
91pub async fn submit_request(Json(payload): Json<SubmitRequestPayload>) -> impl IntoResponse {
93 let request = CapturedRequest::new(&payload.method, &payload.path, "api_endpoint")
94 .with_headers(payload.headers.unwrap_or_default())
95 .with_query_params(payload.query_params.unwrap_or_default());
96
97 let request = if let Some(body) = payload.body {
98 request.with_body(body)
99 } else {
100 request
101 };
102
103 let request = if let Some(status_code) = payload.status_code {
104 request.with_response(status_code, payload.response_body)
105 } else {
106 request
107 };
108
109 let capture_manager = match get_global_capture_manager() {
111 Some(manager) => manager,
112 None => {
113 return (
114 StatusCode::INTERNAL_SERVER_ERROR,
115 Json(json!({
116 "success": false,
117 "error": "Capture manager not initialized"
118 })),
119 )
120 .into_response();
121 }
122 };
123
124 match capture_manager.capture(request).await {
125 Ok(capture_id) => (
126 StatusCode::OK,
127 Json(json!({
128 "success": true,
129 "capture_id": capture_id,
130 "message": "Request submitted successfully"
131 })),
132 )
133 .into_response(),
134 Err(e) => error_response(e),
135 }
136}
137
138pub async fn get_captured_requests(
140 Query(params): Query<HashMap<String, String>>,
141) -> impl IntoResponse {
142 let capture_manager = match get_global_capture_manager() {
143 Some(manager) => manager,
144 None => {
145 return (
146 StatusCode::INTERNAL_SERVER_ERROR,
147 Json(json!({
148 "success": false,
149 "error": "Capture manager not initialized"
150 })),
151 )
152 .into_response();
153 }
154 };
155
156 let query = CaptureQuery {
157 source: params.get("source").cloned(),
158 method: params.get("method").cloned(),
159 path_pattern: params.get("path_pattern").cloned(),
160 analyzed: params.get("analyzed").and_then(|s| s.parse().ok()),
161 limit: params.get("limit").and_then(|s| s.parse().ok()),
162 offset: params.get("offset").and_then(|s| s.parse().ok()),
163 ..Default::default()
164 };
165
166 let captures = capture_manager.query_captures(query).await;
167
168 (
169 StatusCode::OK,
170 Json(json!({
171 "success": true,
172 "count": captures.len(),
173 "captures": captures.iter().map(|(req, meta)| json!({
174 "id": meta.id,
175 "method": req.method,
176 "path": req.path,
177 "source": meta.source,
178 "captured_at": meta.captured_at,
179 "analyzed": meta.analyzed,
180 "query_params": req.query_params,
181 "headers": req.headers,
182 })).collect::<Vec<_>>()
183 })),
184 )
185 .into_response()
186}
187
188pub async fn get_captured_request(Path(capture_id): Path<String>) -> impl IntoResponse {
190 let capture_manager = match get_global_capture_manager() {
191 Some(manager) => manager,
192 None => {
193 return (
194 StatusCode::INTERNAL_SERVER_ERROR,
195 Json(json!({
196 "success": false,
197 "error": "Capture manager not initialized"
198 })),
199 )
200 .into_response();
201 }
202 };
203
204 let (request, metadata) = match capture_manager.get_capture(&capture_id).await {
205 Some(result) => result,
206 None => {
207 return (
208 StatusCode::NOT_FOUND,
209 Json(json!({
210 "success": false,
211 "error": format!("Capture not found: {}", capture_id)
212 })),
213 )
214 .into_response();
215 }
216 };
217
218 (
219 StatusCode::OK,
220 Json(json!({
221 "success": true,
222 "capture": {
223 "id": metadata.id,
224 "method": request.method,
225 "path": request.path,
226 "source": metadata.source,
227 "captured_at": metadata.captured_at,
228 "analyzed": metadata.analyzed,
229 "contract_id": metadata.contract_id,
230 "analysis_result_id": metadata.analysis_result_id,
231 "query_params": request.query_params,
232 "headers": request.headers,
233 "body": request.body,
234 "status_code": request.status_code,
235 "response_body": request.response_body,
236 "user_agent": request.user_agent,
237 "metadata": request.metadata,
238 }
239 })),
240 )
241 .into_response()
242}
243
244pub async fn analyze_captured_request(
246 Path(capture_id): Path<String>,
247 Json(payload): Json<AnalyzeRequestPayload>,
248) -> impl IntoResponse {
249 let capture_manager = match get_global_capture_manager() {
250 Some(manager) => manager,
251 None => {
252 return (
253 StatusCode::INTERNAL_SERVER_ERROR,
254 Json(json!({
255 "success": false,
256 "error": "Capture manager not initialized"
257 })),
258 )
259 .into_response();
260 }
261 };
262
263 let (request, _metadata) = match capture_manager.get_capture(&capture_id).await {
265 Some(result) => result,
266 None => {
267 return (
268 StatusCode::NOT_FOUND,
269 Json(json!({
270 "success": false,
271 "error": format!("Capture not found: {}", capture_id)
272 })),
273 )
274 .into_response();
275 }
276 };
277
278 let spec = match if let Some(spec_path) = &payload.spec_path {
280 OpenApiSpec::from_file(spec_path).await
281 } else if let Some(spec_content) = &payload.spec_content {
282 let format = if spec_content.trim_start().starts_with('{') {
284 None } else {
286 Some("yaml") };
288 OpenApiSpec::from_string(spec_content, format)
289 } else {
290 return (
291 StatusCode::BAD_REQUEST,
292 Json(json!({
293 "success": false,
294 "error": "Either spec_path or spec_content must be provided"
295 })),
296 )
297 .into_response();
298 } {
299 Ok(spec) => spec,
300 Err(e) => return error_response(e),
301 };
302
303 let config = payload.config.unwrap_or_else(ContractDiffConfig::default);
305 let analyzer = match ContractDiffAnalyzer::new(config) {
306 Ok(analyzer) => analyzer,
307 Err(e) => return error_response(e),
308 };
309
310 let result = match analyzer.analyze(&request, &spec).await {
312 Ok(result) => result,
313 Err(e) => return error_response(e),
314 };
315
316 let analysis_result_id = uuid::Uuid::new_v4().to_string();
318 let contract_id = payload.contract_id.unwrap_or_else(|| "default".to_string());
319 if let Err(e) = capture_manager
320 .mark_analyzed(&capture_id, &contract_id, &analysis_result_id)
321 .await
322 {
323 return error_response(e);
324 }
325
326 (
327 StatusCode::OK,
328 Json(json!({
329 "success": true,
330 "analysis_result_id": analysis_result_id,
331 "result": result
332 })),
333 )
334 .into_response()
335}
336
337pub async fn get_capture_statistics() -> impl IntoResponse {
339 let capture_manager = match get_global_capture_manager() {
340 Some(manager) => manager,
341 None => {
342 return (
343 StatusCode::INTERNAL_SERVER_ERROR,
344 Json(json!({
345 "success": false,
346 "error": "Capture manager not initialized"
347 })),
348 )
349 .into_response();
350 }
351 };
352
353 let stats = capture_manager.get_statistics().await;
354
355 (
356 StatusCode::OK,
357 Json(json!({
358 "success": true,
359 "statistics": stats
360 })),
361 )
362 .into_response()
363}
364
365pub async fn generate_patch_file(
367 Path(capture_id): Path<String>,
368 Json(payload): Json<GeneratePatchPayload>,
369) -> impl IntoResponse {
370 let capture_manager = match get_global_capture_manager() {
371 Some(manager) => manager,
372 None => {
373 return (
374 StatusCode::INTERNAL_SERVER_ERROR,
375 Json(json!({
376 "success": false,
377 "error": "Capture manager not initialized"
378 })),
379 )
380 .into_response();
381 }
382 };
383
384 let (request, _metadata) = match capture_manager.get_capture(&capture_id).await {
386 Some(result) => result,
387 None => {
388 return (
389 StatusCode::NOT_FOUND,
390 Json(json!({
391 "success": false,
392 "error": format!("Capture not found: {}", capture_id)
393 })),
394 )
395 .into_response();
396 }
397 };
398
399 let spec = match if let Some(spec_path) = &payload.spec_path {
401 OpenApiSpec::from_file(spec_path).await
402 } else if let Some(spec_content) = &payload.spec_content {
403 let format = if spec_content.trim_start().starts_with('{') {
404 None } else {
406 Some("yaml") };
408 OpenApiSpec::from_string(spec_content, format)
409 } else {
410 return (
411 StatusCode::BAD_REQUEST,
412 Json(json!({
413 "success": false,
414 "error": "Either spec_path or spec_content must be provided"
415 })),
416 )
417 .into_response();
418 } {
419 Ok(spec) => spec,
420 Err(e) => return error_response(e),
421 };
422
423 let config = payload.config.unwrap_or_else(ContractDiffConfig::default);
425 let analyzer = match ContractDiffAnalyzer::new(config) {
426 Ok(analyzer) => analyzer,
427 Err(e) => return error_response(e),
428 };
429
430 let result = match analyzer.analyze(&request, &spec).await {
432 Ok(result) => result,
433 Err(e) => return error_response(e),
434 };
435
436 if result.corrections.is_empty() {
438 return (
439 StatusCode::BAD_REQUEST,
440 Json(json!({
441 "success": false,
442 "error": "No corrections available to generate patch"
443 })),
444 )
445 .into_response();
446 }
447
448 let spec_version = if spec.spec.info.version.is_empty() {
449 "1.0.0".to_string()
450 } else {
451 spec.spec.info.version.clone()
452 };
453 let patch_file = analyzer.generate_patch_file(&result.corrections, &spec_version);
454
455 (
456 StatusCode::OK,
457 Json(json!({
458 "success": true,
459 "patch_file": patch_file,
460 "corrections_count": result.corrections.len()
461 })),
462 )
463 .into_response()
464}
465
466#[derive(Debug, Deserialize)]
468pub struct GeneratePatchPayload {
469 pub spec_path: Option<String>,
471
472 pub spec_content: Option<String>,
474
475 pub config: Option<ContractDiffConfig>,
477}
478
479#[derive(Debug, Deserialize)]
481pub struct UploadRequestPayload {
482 pub method: String,
483 pub path: String,
484 pub headers: Option<HashMap<String, String>>,
485 pub query_params: Option<HashMap<String, String>>,
486 pub body: Option<serde_json::Value>,
487 pub status_code: Option<u16>,
488 pub response_body: Option<serde_json::Value>,
489}
490
491#[derive(Debug, Deserialize)]
493pub struct SubmitRequestPayload {
494 pub method: String,
495 pub path: String,
496 pub headers: Option<HashMap<String, String>>,
497 pub query_params: Option<HashMap<String, String>>,
498 pub body: Option<serde_json::Value>,
499 pub status_code: Option<u16>,
500 pub response_body: Option<serde_json::Value>,
501}
502
503#[derive(Debug, Deserialize)]
505pub struct AnalyzeRequestPayload {
506 pub spec_path: Option<String>,
508
509 pub spec_content: Option<String>,
511
512 pub contract_id: Option<String>,
514
515 pub config: Option<ContractDiffConfig>,
517}