mockforge_recorder/
openapi_export.rs1use crate::{
7 database::RecorderDatabase,
8 models::{Protocol, RecordedExchange, RecordedRequest, RecordedResponse},
9 query::{execute_query, QueryFilter},
10 Result,
11};
12use chrono::{DateTime, Utc};
13use mockforge_core::intelligent_behavior::openapi_generator::HttpExchange;
14
15pub struct RecordingsToOpenApi;
17
18impl RecordingsToOpenApi {
19 pub fn convert_exchange(exchange: &RecordedExchange) -> HttpExchange {
21 HttpExchange {
22 method: exchange.request.method.clone(),
23 path: exchange.request.path.clone(),
24 query_params: exchange.request.query_params.clone(),
25 headers: exchange.request.headers.clone(),
26 body: exchange.request.body.clone(),
27 body_encoding: exchange.request.body_encoding.clone(),
28 status_code: exchange.response.as_ref().map(|r| r.status_code),
29 response_headers: exchange.response.as_ref().map(|r| r.headers.clone()),
30 response_body: exchange.response.as_ref().and_then(|r| r.body.clone()),
31 response_body_encoding: exchange.response.as_ref().map(|r| r.body_encoding.clone()),
32 timestamp: exchange.request.timestamp,
33 }
34 }
35
36 pub fn convert_request_response(
38 request: &RecordedRequest,
39 response: Option<&RecordedResponse>,
40 ) -> HttpExchange {
41 HttpExchange {
42 method: request.method.clone(),
43 path: request.path.clone(),
44 query_params: request.query_params.clone(),
45 headers: request.headers.clone(),
46 body: request.body.clone(),
47 body_encoding: request.body_encoding.clone(),
48 status_code: response.map(|r| r.status_code),
49 response_headers: response.map(|r| r.headers.clone()),
50 response_body: response.and_then(|r| r.body.clone()),
51 response_body_encoding: response.map(|r| r.body_encoding.clone()),
52 timestamp: request.timestamp,
53 }
54 }
55
56 pub fn convert_exchanges(exchanges: &[RecordedExchange]) -> Vec<HttpExchange> {
58 exchanges.iter().map(Self::convert_exchange).collect()
59 }
60
61 pub async fn query_http_exchanges(
66 db: &RecorderDatabase,
67 filters: Option<QueryFilters>,
68 ) -> Result<Vec<HttpExchange>> {
69 let mut query_filter = QueryFilter {
70 protocol: Some(Protocol::Http),
71 limit: filters.as_ref().and_then(|f| f.max_requests).map(|n| n as i32).or(Some(1000)),
72 ..Default::default()
73 };
74
75 if let Some(ref filters) = filters {
77 if let Some(ref path_pattern) = filters.path_pattern {
78 query_filter.path = Some(path_pattern.clone());
79 }
80
81 if let Some(status) = filters.min_status_code {
82 query_filter.status_code = Some(status);
83 }
84 }
85
86 let query_result = execute_query(db, query_filter).await?;
88
89 let mut exchanges = Self::convert_exchanges(&query_result.exchanges);
91
92 if let Some(ref filters) = filters {
94 if let Some(since) = filters.since {
95 exchanges.retain(|e| e.timestamp >= since);
96 }
97
98 if let Some(until) = filters.until {
99 exchanges.retain(|e| e.timestamp <= until);
100 }
101 }
102
103 Ok(exchanges)
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct QueryFilters {
110 pub since: Option<DateTime<Utc>>,
112
113 pub until: Option<DateTime<Utc>>,
115
116 pub path_pattern: Option<String>,
118
119 pub min_status_code: Option<i32>,
121
122 pub max_requests: Option<usize>,
124}
125
126impl Default for QueryFilters {
127 fn default() -> Self {
128 Self {
129 since: None,
130 until: None,
131 path_pattern: None,
132 min_status_code: None,
133 max_requests: Some(1000),
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::models::{RecordedRequest, RecordedResponse};
142
143 #[test]
144 fn test_convert_exchange() {
145 let request = RecordedRequest {
146 id: "test-123".to_string(),
147 protocol: Protocol::Http,
148 timestamp: Utc::now(),
149 method: "GET".to_string(),
150 path: "/api/test".to_string(),
151 query_params: None,
152 headers: "{}".to_string(),
153 body: None,
154 body_encoding: "utf8".to_string(),
155 client_ip: None,
156 trace_id: None,
157 span_id: None,
158 duration_ms: None,
159 status_code: Some(200),
160 tags: None,
161 };
162
163 let response = Some(RecordedResponse {
164 request_id: "test-123".to_string(),
165 status_code: 200,
166 headers: "{}".to_string(),
167 body: Some(r#"{"result": "ok"}"#.to_string()),
168 body_encoding: "utf8".to_string(),
169 size_bytes: 15,
170 timestamp: Utc::now(),
171 });
172
173 let exchange = RecordedExchange { request, response };
174 let http_exchange = RecordingsToOpenApi::convert_exchange(&exchange);
175
176 assert_eq!(http_exchange.method, "GET");
177 assert_eq!(http_exchange.path, "/api/test");
178 assert_eq!(http_exchange.status_code, Some(200));
179 }
180}