1use axum::{
7 extract::{Path, Query, State},
8 http::StatusCode,
9 response::IntoResponse,
10 Json,
11};
12use mockforge_core::intelligent_behavior::{rules::StateMachine, visual_layout::VisualLayout};
13use mockforge_scenarios::{
14 state_machine::{ScenarioStateMachineManager, StateInstance},
15 ScenarioManifest,
16};
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use std::collections::HashMap;
20use std::sync::Arc;
21use tokio::sync::RwLock;
22use tracing::{debug, error, info, warn};
23
24use crate::management::ManagementState;
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct StateMachineRequest {
32 pub state_machine: StateMachine,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub visual_layout: Option<VisualLayout>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct TransitionRequest {
42 pub resource_id: String,
44 pub to_state: String,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub context: Option<HashMap<String, Value>>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CreateInstanceRequest {
54 pub resource_id: String,
56 pub resource_type: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct StateMachineResponse {
63 pub state_machine: StateMachine,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub visual_layout: Option<VisualLayout>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct StateMachineListResponse {
73 pub state_machines: Vec<StateMachineInfo>,
75 pub total: usize,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct StateMachineInfo {
82 pub resource_type: String,
84 pub state_count: usize,
86 pub transition_count: usize,
88 pub sub_scenario_count: usize,
90 pub has_visual_layout: bool,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct StateInstanceResponse {
97 pub resource_id: String,
99 pub current_state: String,
101 pub resource_type: String,
103 pub history_count: usize,
105 pub state_data: HashMap<String, Value>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct StateInstanceListResponse {
112 pub instances: Vec<StateInstanceResponse>,
114 pub total: usize,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct NextStatesResponse {
121 pub next_states: Vec<String>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ImportExportResponse {
128 pub state_machines: Vec<StateMachine>,
130 pub visual_layouts: HashMap<String, VisualLayout>,
132}
133
134pub async fn list_state_machines(
138 State(state): State<ManagementState>,
139) -> Result<Json<StateMachineListResponse>, StatusCode> {
140 let manager = state.state_machine_manager.read().await;
141
142 let machines = manager.list_state_machines().await;
144
145 let state_machine_list: Vec<_> = machines
146 .iter()
147 .map(|(resource_type, sm)| StateMachineInfo {
148 resource_type: resource_type.clone(),
149 state_count: sm.states.len(),
150 transition_count: sm.transitions.len(),
151 sub_scenario_count: sm.sub_scenarios.len(),
152 has_visual_layout: sm.visual_layout.is_some(),
153 })
154 .collect();
155
156 Ok(Json(StateMachineListResponse {
157 state_machines: state_machine_list.clone(),
158 total: state_machine_list.len(),
159 }))
160}
161
162pub async fn get_state_machine(
164 State(state): State<ManagementState>,
165 Path(resource_type): Path<String>,
166) -> Result<Json<StateMachineResponse>, StatusCode> {
167 let manager = state.state_machine_manager.read().await;
168
169 let state_machine =
170 manager.get_state_machine(&resource_type).await.ok_or(StatusCode::NOT_FOUND)?;
171
172 let visual_layout = manager.get_visual_layout(&resource_type).await;
173
174 Ok(Json(StateMachineResponse {
175 state_machine,
176 visual_layout,
177 }))
178}
179
180pub async fn create_state_machine(
182 State(state): State<ManagementState>,
183 Json(request): Json<StateMachineRequest>,
184) -> Result<Json<StateMachineResponse>, StatusCode> {
185 let mut manager = state.state_machine_manager.write().await;
186
187 if let Err(e) = manager.validate_state_machine(&request.state_machine) {
189 error!("Invalid state machine: {}", e);
190 return Err(StatusCode::BAD_REQUEST);
191 }
192
193 let mut manifest = ScenarioManifest::new(
196 "api".to_string(),
197 "1.0.0".to_string(),
198 "API State Machine".to_string(),
199 "State machine created via API".to_string(),
200 );
201 manifest.state_machines.push(request.state_machine.clone());
202
203 if let Some(layout) = &request.visual_layout {
204 manifest
205 .state_machine_graphs
206 .insert(request.state_machine.resource_type.clone(), layout.clone());
207 }
208
209 if let Err(e) = manager.load_from_manifest(&manifest).await {
210 error!("Failed to load state machine: {}", e);
211 return Err(StatusCode::INTERNAL_SERVER_ERROR);
212 }
213
214 let visual_layout = request.visual_layout.clone();
216 if let Some(layout) = &visual_layout {
217 manager
218 .set_visual_layout(&request.state_machine.resource_type, layout.clone())
219 .await;
220 }
221
222 if let Some(ref ws_tx) = state.ws_broadcast {
224 let event = crate::management_ws::MockEvent::state_machine_updated(
225 request.state_machine.resource_type.clone(),
226 request.state_machine.clone(),
227 );
228 let _ = ws_tx.send(event);
229 }
230
231 Ok(Json(StateMachineResponse {
232 state_machine: request.state_machine,
233 visual_layout,
234 }))
235}
236
237pub async fn delete_state_machine(
239 State(state): State<ManagementState>,
240 Path(resource_type): Path<String>,
241) -> Result<StatusCode, StatusCode> {
242 let mut manager = state.state_machine_manager.write().await;
243
244 let deleted = manager.delete_state_machine(&resource_type).await;
246
247 if !deleted {
248 return Err(StatusCode::NOT_FOUND);
249 }
250
251 if let Some(ref ws_tx) = state.ws_broadcast {
253 let event = crate::management_ws::MockEvent::state_machine_deleted(resource_type);
254 let _ = ws_tx.send(event);
255 }
256
257 Ok(StatusCode::NO_CONTENT)
258}
259
260pub async fn list_instances(
262 State(state): State<ManagementState>,
263) -> Result<Json<StateInstanceListResponse>, StatusCode> {
264 let manager = state.state_machine_manager.read().await;
265
266 let instances = manager.list_instances().await;
267
268 let instance_responses: Vec<StateInstanceResponse> = instances
269 .iter()
270 .map(|i| StateInstanceResponse {
271 resource_id: i.resource_id.clone(),
272 current_state: i.current_state.clone(),
273 resource_type: i.resource_type.clone(),
274 history_count: i.state_history.len(),
275 state_data: i.state_data.clone(),
276 })
277 .collect();
278
279 Ok(Json(StateInstanceListResponse {
280 instances: instance_responses,
281 total: instances.len(),
282 }))
283}
284
285pub async fn get_instance(
287 State(state): State<ManagementState>,
288 Path(resource_id): Path<String>,
289) -> Result<Json<StateInstanceResponse>, StatusCode> {
290 let manager = state.state_machine_manager.read().await;
291
292 let instance = manager.get_instance(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
293
294 Ok(Json(StateInstanceResponse {
295 resource_id: instance.resource_id,
296 current_state: instance.current_state,
297 resource_type: instance.resource_type,
298 history_count: instance.state_history.len(),
299 state_data: instance.state_data,
300 }))
301}
302
303pub async fn create_instance(
305 State(state): State<ManagementState>,
306 Json(request): Json<CreateInstanceRequest>,
307) -> Result<Json<StateInstanceResponse>, StatusCode> {
308 let manager = state.state_machine_manager.write().await;
309
310 if let Err(e) = manager.create_instance(&request.resource_id, &request.resource_type).await {
311 error!("Failed to create instance: {}", e);
312 return Err(StatusCode::BAD_REQUEST);
313 }
314
315 let instance = manager
316 .get_instance(&request.resource_id)
317 .await
318 .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
319
320 if let Some(ref ws_tx) = state.ws_broadcast {
322 let event = crate::management_ws::MockEvent::state_instance_created(
323 instance.resource_id.clone(),
324 instance.resource_type.clone(),
325 instance.current_state.clone(),
326 );
327 let _ = ws_tx.send(event);
328 }
329
330 Ok(Json(StateInstanceResponse {
331 resource_id: instance.resource_id,
332 current_state: instance.current_state,
333 resource_type: instance.resource_type,
334 history_count: instance.state_history.len(),
335 state_data: instance.state_data,
336 }))
337}
338
339pub async fn execute_transition(
341 State(state): State<ManagementState>,
342 Json(request): Json<TransitionRequest>,
343) -> Result<Json<StateInstanceResponse>, StatusCode> {
344 let manager = state.state_machine_manager.write().await;
345
346 if let Err(e) = manager
347 .execute_transition(&request.resource_id, &request.to_state, request.context)
348 .await
349 {
350 error!("Failed to execute transition: {}", e);
351 return Err(StatusCode::BAD_REQUEST);
352 }
353
354 let instance = manager.get_instance(&request.resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
355
356 let from_state = instance
358 .state_history
359 .last()
360 .map(|h| h.from_state.clone())
361 .unwrap_or_else(|| instance.current_state.clone());
362
363 if let Some(ref ws_tx) = state.ws_broadcast {
365 let event = crate::management_ws::MockEvent::state_transitioned(
366 instance.resource_id.clone(),
367 instance.resource_type.clone(),
368 from_state,
369 instance.current_state.clone(),
370 instance.state_data.clone(),
371 );
372 let _ = ws_tx.send(event);
373 }
374
375 Ok(Json(StateInstanceResponse {
376 resource_id: instance.resource_id,
377 current_state: instance.current_state,
378 resource_type: instance.resource_type,
379 history_count: instance.state_history.len(),
380 state_data: instance.state_data,
381 }))
382}
383
384pub async fn get_next_states(
386 State(state): State<ManagementState>,
387 Path(resource_id): Path<String>,
388) -> Result<Json<NextStatesResponse>, StatusCode> {
389 let manager = state.state_machine_manager.read().await;
390
391 let next_states =
392 manager.get_next_states(&resource_id).await.map_err(|_| StatusCode::NOT_FOUND)?;
393
394 Ok(Json(NextStatesResponse { next_states }))
395}
396
397pub async fn get_current_state(
399 State(state): State<ManagementState>,
400 Path(resource_id): Path<String>,
401) -> Result<Json<serde_json::Value>, StatusCode> {
402 let manager = state.state_machine_manager.read().await;
403
404 let current_state =
405 manager.get_current_state(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
406
407 Ok(Json(serde_json::json!({
408 "resource_id": resource_id,
409 "current_state": current_state
410 })))
411}
412
413pub async fn export_state_machines(
415 State(state): State<ManagementState>,
416) -> Result<Json<ImportExportResponse>, StatusCode> {
417 let manager = state.state_machine_manager.read().await;
418
419 let (state_machines, visual_layouts) = manager.export_all().await;
421
422 Ok(Json(ImportExportResponse {
423 state_machines,
424 visual_layouts,
425 }))
426}
427
428pub async fn import_state_machines(
430 State(state): State<ManagementState>,
431 Json(request): Json<ImportExportResponse>,
432) -> Result<StatusCode, StatusCode> {
433 let mut manager = state.state_machine_manager.write().await;
434
435 let mut manifest = ScenarioManifest::new(
437 "imported".to_string(),
438 "1.0.0".to_string(),
439 "Imported State Machines".to_string(),
440 "State machines imported via API".to_string(),
441 );
442 manifest.state_machines = request.state_machines.clone();
443 manifest.state_machine_graphs = request.visual_layouts.clone();
444
445 if let Err(e) = manager.load_from_manifest(&manifest).await {
446 error!("Failed to import state machines: {}", e);
447 return Err(StatusCode::BAD_REQUEST);
448 }
449
450 for (resource_type, layout) in request.visual_layouts {
452 manager.set_visual_layout(&resource_type, layout).await;
453 }
454
455 Ok(StatusCode::CREATED)
456}
457
458pub fn create_state_machine_routes() -> axum::Router<ManagementState> {
463 use axum::routing::{delete, get, post, put};
464
465 axum::Router::new()
466 .route("/", get(list_state_machines))
468 .route("/", post(create_state_machine))
469 .route("/:resource_type", get(get_state_machine))
470 .route("/:resource_type", put(create_state_machine))
471 .route("/:resource_type", delete(delete_state_machine))
472
473 .route("/instances", get(list_instances))
475 .route("/instances", post(create_instance))
476 .route("/instances/:resource_id", get(get_instance))
477 .route("/instances/:resource_id/state", get(get_current_state))
478 .route("/instances/:resource_id/next-states", get(get_next_states))
479 .route("/instances/:resource_id/transition", post(execute_transition))
480
481 .route("/export", get(export_state_machines))
483 .route("/import", post(import_state_machines))
484}