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