mockforge_http/management/
rule_explanations.rs1#![allow(deprecated)]
4
5use axum::{
21 extract::{Path, Query, State},
22 http::StatusCode,
23 response::{IntoResponse, Json},
24};
25use serde::Deserialize;
26
27use super::ManagementState;
28
29pub(crate) async fn list_rule_explanations(
31 State(state): State<ManagementState>,
32 Query(params): Query<std::collections::HashMap<String, String>>,
33) -> impl IntoResponse {
34 use mockforge_foundation::intelligent_behavior::rule_types::RuleType;
35
36 let explanations = state.rule_explanations.read().await;
37 let mut explanations_vec: Vec<_> = explanations.values().cloned().collect();
38
39 if let Some(rule_type_str) = params.get("rule_type") {
41 if let Ok(rule_type) = serde_json::from_str::<RuleType>(&format!("\"{}\"", rule_type_str)) {
42 explanations_vec.retain(|e| e.rule_type == rule_type);
43 }
44 }
45
46 if let Some(min_confidence_str) = params.get("min_confidence") {
48 if let Ok(min_confidence) = min_confidence_str.parse::<f64>() {
49 explanations_vec.retain(|e| e.confidence >= min_confidence);
50 }
51 }
52
53 explanations_vec.sort_by(|a, b| {
55 b.confidence
56 .partial_cmp(&a.confidence)
57 .unwrap_or(std::cmp::Ordering::Equal)
58 .then_with(|| b.generated_at.cmp(&a.generated_at))
59 });
60
61 Json(serde_json::json!({
62 "explanations": explanations_vec,
63 "total": explanations_vec.len(),
64 }))
65 .into_response()
66}
67
68pub(crate) async fn get_rule_explanation(
70 State(state): State<ManagementState>,
71 Path(rule_id): Path<String>,
72) -> impl IntoResponse {
73 let explanations = state.rule_explanations.read().await;
74
75 match explanations.get(&rule_id) {
76 Some(explanation) => Json(serde_json::json!({
77 "explanation": explanation,
78 }))
79 .into_response(),
80 None => (
81 StatusCode::NOT_FOUND,
82 Json(serde_json::json!({
83 "error": "Rule explanation not found",
84 "message": format!("No explanation found for rule ID: {}", rule_id)
85 })),
86 )
87 .into_response(),
88 }
89}
90
91#[derive(Debug, Deserialize)]
93pub struct LearnFromExamplesRequest {
94 pub examples: Vec<ExamplePairRequest>,
96 #[serde(default)]
98 pub config: Option<serde_json::Value>,
99}
100
101#[derive(Debug, Deserialize)]
103pub struct ExamplePairRequest {
104 pub request: serde_json::Value,
106 pub response: serde_json::Value,
108}
109
110pub(crate) async fn learn_from_examples(
115 State(state): State<ManagementState>,
116 Json(request): Json<LearnFromExamplesRequest>,
117) -> impl IntoResponse {
118 use mockforge_core::intelligent_behavior::{
119 config::{BehaviorModelConfig, IntelligentBehaviorConfig},
120 rule_generator::{ExamplePair, RuleGenerator},
121 };
122
123 if request.examples.is_empty() {
124 return (
125 StatusCode::BAD_REQUEST,
126 Json(serde_json::json!({
127 "error": "No examples provided",
128 "message": "At least one example pair is required"
129 })),
130 )
131 .into_response();
132 }
133
134 let example_pairs: Result<Vec<ExamplePair>, String> = request
136 .examples
137 .into_iter()
138 .enumerate()
139 .map(|(idx, ex)| {
140 let method = ex
142 .request
143 .get("method")
144 .and_then(|v| v.as_str())
145 .map(|s| s.to_string())
146 .unwrap_or_else(|| "GET".to_string());
147 let path = ex
148 .request
149 .get("path")
150 .and_then(|v| v.as_str())
151 .map(|s| s.to_string())
152 .unwrap_or_else(|| "/".to_string());
153 let request_body = ex.request.get("body").cloned();
154 let query_params = ex
155 .request
156 .get("query_params")
157 .and_then(|v| v.as_object())
158 .map(|obj| {
159 obj.iter()
160 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
161 .collect()
162 })
163 .unwrap_or_default();
164 let headers = ex
165 .request
166 .get("headers")
167 .and_then(|v| v.as_object())
168 .map(|obj| {
169 obj.iter()
170 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
171 .collect()
172 })
173 .unwrap_or_default();
174
175 let status = ex
177 .response
178 .get("status_code")
179 .or_else(|| ex.response.get("status"))
180 .and_then(|v| v.as_u64())
181 .map(|n| n as u16)
182 .unwrap_or(200);
183 let response_body = ex.response.get("body").cloned();
184
185 Ok(ExamplePair {
186 method,
187 path,
188 request: request_body,
189 status,
190 response: response_body,
191 query_params,
192 headers,
193 metadata: {
194 let mut meta = std::collections::HashMap::new();
195 meta.insert("source".to_string(), "api".to_string());
196 meta.insert("example_index".to_string(), idx.to_string());
197 meta
198 },
199 })
200 })
201 .collect();
202
203 let example_pairs = match example_pairs {
204 Ok(pairs) => pairs,
205 Err(e) => {
206 return (
207 StatusCode::BAD_REQUEST,
208 Json(serde_json::json!({
209 "error": "Invalid examples",
210 "message": e
211 })),
212 )
213 .into_response();
214 }
215 };
216
217 let behavior_config = if let Some(config_json) = request.config {
219 serde_json::from_value(config_json)
221 .unwrap_or_else(|_| IntelligentBehaviorConfig::default())
222 .behavior_model
223 } else {
224 BehaviorModelConfig::default()
225 };
226
227 let generator = RuleGenerator::new(behavior_config);
229
230 let (rules, explanations) =
232 match generator.generate_rules_with_explanations(example_pairs).await {
233 Ok(result) => result,
234 Err(e) => {
235 return (
236 StatusCode::INTERNAL_SERVER_ERROR,
237 Json(serde_json::json!({
238 "error": "Rule generation failed",
239 "message": format!("Failed to generate rules: {}", e)
240 })),
241 )
242 .into_response();
243 }
244 };
245
246 {
248 let mut stored_explanations = state.rule_explanations.write().await;
249 for explanation in &explanations {
250 stored_explanations.insert(explanation.rule_id.clone(), explanation.clone());
251 }
252 }
253
254 let response = serde_json::json!({
256 "success": true,
257 "rules_generated": {
258 "consistency_rules": rules.consistency_rules.len(),
259 "schemas": rules.schemas.len(),
260 "state_machines": rules.state_transitions.len(),
261 "system_prompt": !rules.system_prompt.is_empty(),
262 },
263 "explanations": explanations.iter().map(|e| serde_json::json!({
264 "rule_id": e.rule_id,
265 "rule_type": e.rule_type,
266 "confidence": e.confidence,
267 "reasoning": e.reasoning,
268 })).collect::<Vec<_>>(),
269 "total_explanations": explanations.len(),
270 });
271
272 Json(response).into_response()
273}