mockforge_http/management/
traffic_to_openapi.rs1#![allow(deprecated)]
4
5use serde::Deserialize;
19
20#[cfg(feature = "behavioral-cloning")]
24use super::ManagementState;
25#[cfg(feature = "behavioral-cloning")]
26use axum::{
27 extract::State,
28 http::StatusCode,
29 response::{IntoResponse, Json},
30};
31
32#[derive(Debug, Deserialize)]
34pub struct GenerateOpenApiFromTrafficRequest {
35 #[serde(default)]
37 pub database_path: Option<String>,
38 #[serde(default)]
40 pub since: Option<String>,
41 #[serde(default)]
43 pub until: Option<String>,
44 #[serde(default)]
46 pub path_pattern: Option<String>,
47 #[serde(default = "default_min_confidence")]
49 pub min_confidence: f64,
50}
51
52fn default_min_confidence() -> f64 {
53 0.7
54}
55
56#[cfg(feature = "behavioral-cloning")]
58pub(crate) async fn generate_openapi_from_traffic(
59 State(_state): State<ManagementState>,
60 Json(request): Json<GenerateOpenApiFromTrafficRequest>,
61) -> impl IntoResponse {
62 use chrono::{DateTime, Utc};
63 use mockforge_core::intelligent_behavior::{
64 openapi_generator::{OpenApiGenerationConfig, OpenApiSpecGenerator},
65 IntelligentBehaviorConfig,
66 };
67 use mockforge_recorder::{
68 database::RecorderDatabase,
69 openapi_export::{QueryFilters, RecordingsToOpenApi},
70 };
71 use std::path::PathBuf;
72
73 let db_path = if let Some(ref path) = request.database_path {
75 PathBuf::from(path)
76 } else {
77 std::env::current_dir()
78 .unwrap_or_else(|_| PathBuf::from("."))
79 .join("recordings.db")
80 };
81
82 let db = match RecorderDatabase::new(&db_path).await {
84 Ok(db) => db,
85 Err(e) => {
86 return (
87 StatusCode::BAD_REQUEST,
88 Json(serde_json::json!({
89 "error": "Database error",
90 "message": format!("Failed to open recorder database: {}", e)
91 })),
92 )
93 .into_response();
94 }
95 };
96
97 let since_dt = if let Some(ref since_str) = request.since {
99 match DateTime::parse_from_rfc3339(since_str) {
100 Ok(dt) => Some(dt.with_timezone(&Utc)),
101 Err(e) => {
102 return (
103 StatusCode::BAD_REQUEST,
104 Json(serde_json::json!({
105 "error": "Invalid date format",
106 "message": format!("Invalid --since format: {}. Use ISO 8601 format (e.g., 2025-01-01T00:00:00Z)", e)
107 })),
108 )
109 .into_response();
110 }
111 }
112 } else {
113 None
114 };
115
116 let until_dt = if let Some(ref until_str) = request.until {
117 match DateTime::parse_from_rfc3339(until_str) {
118 Ok(dt) => Some(dt.with_timezone(&Utc)),
119 Err(e) => {
120 return (
121 StatusCode::BAD_REQUEST,
122 Json(serde_json::json!({
123 "error": "Invalid date format",
124 "message": format!("Invalid --until format: {}. Use ISO 8601 format (e.g., 2025-01-01T00:00:00Z)", e)
125 })),
126 )
127 .into_response();
128 }
129 }
130 } else {
131 None
132 };
133
134 let query_filters = QueryFilters {
136 since: since_dt,
137 until: until_dt,
138 path_pattern: request.path_pattern.clone(),
139 min_status_code: None,
140 max_requests: Some(1000),
141 };
142
143 let exchanges_from_recorder =
148 match RecordingsToOpenApi::query_http_exchanges(&db, Some(query_filters)).await {
149 Ok(exchanges) => exchanges,
150 Err(e) => {
151 return (
152 StatusCode::INTERNAL_SERVER_ERROR,
153 Json(serde_json::json!({
154 "error": "Query error",
155 "message": format!("Failed to query HTTP exchanges: {}", e)
156 })),
157 )
158 .into_response();
159 }
160 };
161
162 if exchanges_from_recorder.is_empty() {
163 return (
164 StatusCode::NOT_FOUND,
165 Json(serde_json::json!({
166 "error": "No exchanges found",
167 "message": "No HTTP exchanges found matching the specified filters"
168 })),
169 )
170 .into_response();
171 }
172
173 use mockforge_core::intelligent_behavior::openapi_generator::HttpExchange as LocalHttpExchange;
175 let exchanges: Vec<LocalHttpExchange> = exchanges_from_recorder
176 .into_iter()
177 .map(|e| LocalHttpExchange {
178 method: e.method,
179 path: e.path,
180 query_params: e.query_params,
181 headers: e.headers,
182 body: e.body,
183 body_encoding: e.body_encoding,
184 status_code: e.status_code,
185 response_headers: e.response_headers,
186 response_body: e.response_body,
187 response_body_encoding: e.response_body_encoding,
188 timestamp: e.timestamp,
189 })
190 .collect();
191
192 let behavior_config = IntelligentBehaviorConfig::default();
194 let gen_config = OpenApiGenerationConfig {
195 min_confidence: request.min_confidence,
196 behavior_model: Some(behavior_config.behavior_model),
197 };
198
199 let generator = OpenApiSpecGenerator::new(gen_config);
201 let result = match generator.generate_from_exchanges(exchanges).await {
202 Ok(result) => result,
203 Err(e) => {
204 return (
205 StatusCode::INTERNAL_SERVER_ERROR,
206 Json(serde_json::json!({
207 "error": "Generation error",
208 "message": format!("Failed to generate OpenAPI spec: {}", e)
209 })),
210 )
211 .into_response();
212 }
213 };
214
215 let spec_json = if let Some(ref raw) = result.spec.raw_document {
217 raw.clone()
218 } else {
219 match serde_json::to_value(&result.spec.spec) {
220 Ok(json) => json,
221 Err(e) => {
222 return (
223 StatusCode::INTERNAL_SERVER_ERROR,
224 Json(serde_json::json!({
225 "error": "Serialization error",
226 "message": format!("Failed to serialize OpenAPI spec: {}", e)
227 })),
228 )
229 .into_response();
230 }
231 }
232 };
233
234 let response = serde_json::json!({
236 "spec": spec_json,
237 "metadata": {
238 "requests_analyzed": result.metadata.requests_analyzed,
239 "paths_inferred": result.metadata.paths_inferred,
240 "path_confidence": result.metadata.path_confidence,
241 "generated_at": result.metadata.generated_at.to_rfc3339(),
242 "duration_ms": result.metadata.duration_ms,
243 }
244 });
245
246 Json(response).into_response()
247}