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 mut state_machine_list = Vec::new();
148 for (resource_type, sm) in machines.iter() {
149 let has_visual_layout = manager.get_visual_layout(resource_type).await.is_some();
150 state_machine_list.push(StateMachineInfo {
151 resource_type: resource_type.clone(),
152 state_count: sm.states.len(),
153 transition_count: sm.transitions.len(),
154 sub_scenario_count: sm.sub_scenarios.len(),
155 has_visual_layout,
156 });
157 }
158
159 Ok(Json(StateMachineListResponse {
160 state_machines: state_machine_list.clone(),
161 total: state_machine_list.len(),
162 }))
163}
164
165pub async fn get_state_machine(
167 State(state): State<ManagementState>,
168 Path(resource_type): Path<String>,
169) -> Result<Json<StateMachineResponse>, StatusCode> {
170 let manager = state.state_machine_manager.read().await;
171
172 let state_machine =
173 manager.get_state_machine(&resource_type).await.ok_or(StatusCode::NOT_FOUND)?;
174
175 let visual_layout = manager.get_visual_layout(&resource_type).await;
176
177 let state_machine_json =
180 serde_json::to_value(&state_machine).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
181 let state_machine: StateMachine = serde_json::from_value(state_machine_json)
182 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
183
184 let visual_layout: Option<VisualLayout> = visual_layout
185 .map(|layout| {
186 let layout_json =
187 serde_json::to_value(&layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
188 serde_json::from_value(layout_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
189 })
190 .transpose()
191 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
192
193 Ok(Json(StateMachineResponse {
194 state_machine,
195 visual_layout,
196 }))
197}
198
199pub async fn create_state_machine(
201 State(state): State<ManagementState>,
202 Json(request): Json<StateMachineRequest>,
203) -> Result<Json<StateMachineResponse>, StatusCode> {
204 let mut manager = state.state_machine_manager.write().await;
205
206 let state_machine_json = serde_json::to_value(&request.state_machine)
211 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
212
213 let mut manifest_json = serde_json::json!({
216 "manifest_version": "1.0",
217 "name": "api",
218 "version": "1.0.0",
219 "title": "API State Machine",
220 "description": "State machine created via API",
221 "author": "api",
222 "category": "other",
223 "compatibility": {
224 "min_version": "0.1.0",
225 "max_version": null
226 },
227 "files": [],
228 "state_machines": [state_machine_json],
229 "state_machine_graphs": {}
230 });
231
232 if let Some(layout) = &request.visual_layout {
233 let layout_json =
234 serde_json::to_value(layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
235 manifest_json["state_machine_graphs"][&request.state_machine.resource_type] = layout_json;
236 }
237
238 let manifest: ScenarioManifest =
239 serde_json::from_value(manifest_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
240
241 if let Some(ref sm) = manifest.state_machines.first() {
243 if let Err(e) = manager.validate_state_machine(sm) {
244 error!("Invalid state machine: {}", e);
245 return Err(StatusCode::BAD_REQUEST);
246 }
247 }
248
249 if let Err(e) = manager.load_from_manifest(&manifest).await {
250 error!("Failed to load state machine: {}", e);
251 return Err(StatusCode::INTERNAL_SERVER_ERROR);
252 }
253
254 if let Some(ref ws_tx) = state.ws_broadcast {
258 let event = crate::management_ws::MockEvent::state_machine_updated(
259 request.state_machine.resource_type.clone(),
260 request.state_machine.clone(),
261 );
262 let _ = ws_tx.send(event);
263 }
264
265 let state_machine_from_manager = manager
267 .get_state_machine(&request.state_machine.resource_type)
268 .await
269 .ok_or(StatusCode::NOT_FOUND)?;
270 let visual_layout_from_manager =
271 manager.get_visual_layout(&request.state_machine.resource_type).await;
272
273 let state_machine_json = serde_json::to_value(&state_machine_from_manager)
275 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
276 let state_machine: StateMachine = serde_json::from_value(state_machine_json)
277 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
278
279 let visual_layout: Option<VisualLayout> = visual_layout_from_manager
280 .map(|layout| {
281 let layout_json =
282 serde_json::to_value(&layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
283 serde_json::from_value(layout_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
284 })
285 .transpose()
286 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
287
288 Ok(Json(StateMachineResponse {
289 state_machine,
290 visual_layout,
291 }))
292}
293
294pub async fn delete_state_machine(
296 State(state): State<ManagementState>,
297 Path(resource_type): Path<String>,
298) -> Result<StatusCode, StatusCode> {
299 let mut manager = state.state_machine_manager.write().await;
300
301 let deleted = manager.delete_state_machine(&resource_type).await;
303
304 if !deleted {
305 return Err(StatusCode::NOT_FOUND);
306 }
307
308 if let Some(ref ws_tx) = state.ws_broadcast {
310 let event = crate::management_ws::MockEvent::state_machine_deleted(resource_type);
311 let _ = ws_tx.send(event);
312 }
313
314 Ok(StatusCode::NO_CONTENT)
315}
316
317pub async fn list_instances(
319 State(state): State<ManagementState>,
320) -> Result<Json<StateInstanceListResponse>, StatusCode> {
321 let manager = state.state_machine_manager.read().await;
322
323 let instances = manager.list_instances().await;
324
325 let instance_responses: Vec<StateInstanceResponse> = instances
326 .iter()
327 .map(|i| StateInstanceResponse {
328 resource_id: i.resource_id.clone(),
329 current_state: i.current_state.clone(),
330 resource_type: i.resource_type.clone(),
331 history_count: i.state_history.len(),
332 state_data: i.state_data.clone(),
333 })
334 .collect();
335
336 Ok(Json(StateInstanceListResponse {
337 instances: instance_responses,
338 total: instances.len(),
339 }))
340}
341
342pub async fn get_instance(
344 State(state): State<ManagementState>,
345 Path(resource_id): Path<String>,
346) -> Result<Json<StateInstanceResponse>, StatusCode> {
347 let manager = state.state_machine_manager.read().await;
348
349 let instance = manager.get_instance(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
350
351 Ok(Json(StateInstanceResponse {
352 resource_id: instance.resource_id,
353 current_state: instance.current_state,
354 resource_type: instance.resource_type,
355 history_count: instance.state_history.len(),
356 state_data: instance.state_data,
357 }))
358}
359
360pub async fn create_instance(
362 State(state): State<ManagementState>,
363 Json(request): Json<CreateInstanceRequest>,
364) -> Result<Json<StateInstanceResponse>, StatusCode> {
365 let manager = state.state_machine_manager.write().await;
366
367 if let Err(e) = manager.create_instance(&request.resource_id, &request.resource_type).await {
368 error!("Failed to create instance: {}", e);
369 return Err(StatusCode::BAD_REQUEST);
370 }
371
372 let instance = manager
373 .get_instance(&request.resource_id)
374 .await
375 .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
376
377 if let Some(ref ws_tx) = state.ws_broadcast {
379 let event = crate::management_ws::MockEvent::state_instance_created(
380 instance.resource_id.clone(),
381 instance.resource_type.clone(),
382 instance.current_state.clone(),
383 );
384 let _ = ws_tx.send(event);
385 }
386
387 Ok(Json(StateInstanceResponse {
388 resource_id: instance.resource_id,
389 current_state: instance.current_state,
390 resource_type: instance.resource_type,
391 history_count: instance.state_history.len(),
392 state_data: instance.state_data,
393 }))
394}
395
396pub async fn execute_transition(
398 State(state): State<ManagementState>,
399 Json(request): Json<TransitionRequest>,
400) -> Result<Json<StateInstanceResponse>, StatusCode> {
401 let manager = state.state_machine_manager.write().await;
402
403 if let Err(e) = manager
404 .execute_transition(&request.resource_id, &request.to_state, request.context)
405 .await
406 {
407 error!("Failed to execute transition: {}", e);
408 return Err(StatusCode::BAD_REQUEST);
409 }
410
411 let instance = manager.get_instance(&request.resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
412
413 let from_state = instance
415 .state_history
416 .last()
417 .map(|h| h.from_state.clone())
418 .unwrap_or_else(|| instance.current_state.clone());
419
420 if let Some(ref ws_tx) = state.ws_broadcast {
422 let event = crate::management_ws::MockEvent::state_transitioned(
423 instance.resource_id.clone(),
424 instance.resource_type.clone(),
425 from_state,
426 instance.current_state.clone(),
427 instance.state_data.clone(),
428 );
429 let _ = ws_tx.send(event);
430 }
431
432 Ok(Json(StateInstanceResponse {
433 resource_id: instance.resource_id,
434 current_state: instance.current_state,
435 resource_type: instance.resource_type,
436 history_count: instance.state_history.len(),
437 state_data: instance.state_data,
438 }))
439}
440
441pub async fn get_next_states(
443 State(state): State<ManagementState>,
444 Path(resource_id): Path<String>,
445) -> Result<Json<NextStatesResponse>, StatusCode> {
446 let manager = state.state_machine_manager.read().await;
447
448 let next_states =
449 manager.get_next_states(&resource_id).await.map_err(|_| StatusCode::NOT_FOUND)?;
450
451 Ok(Json(NextStatesResponse { next_states }))
452}
453
454pub async fn get_current_state(
456 State(state): State<ManagementState>,
457 Path(resource_id): Path<String>,
458) -> Result<Json<serde_json::Value>, StatusCode> {
459 let manager = state.state_machine_manager.read().await;
460
461 let current_state =
462 manager.get_current_state(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
463
464 Ok(Json(serde_json::json!({
465 "resource_id": resource_id,
466 "current_state": current_state
467 })))
468}
469
470pub async fn export_state_machines(
472 State(state): State<ManagementState>,
473) -> Result<Json<ImportExportResponse>, StatusCode> {
474 let manager = state.state_machine_manager.read().await;
475
476 let (state_machines_from_manager, visual_layouts_from_manager) = manager.export_all().await;
478
479 let state_machines: Vec<StateMachine> = state_machines_from_manager
481 .into_iter()
482 .map(|sm| {
483 let json = serde_json::to_value(&sm).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
484 serde_json::from_value(json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
485 })
486 .collect::<Result<Vec<_>, StatusCode>>()?;
487
488 let visual_layouts: HashMap<String, VisualLayout> = visual_layouts_from_manager
489 .into_iter()
490 .map(|(k, v)| {
491 let json = serde_json::to_value(&v).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
492 let layout: VisualLayout =
493 serde_json::from_value(json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
494 Ok((k, layout))
495 })
496 .collect::<Result<HashMap<_, _>, StatusCode>>()?;
497
498 Ok(Json(ImportExportResponse {
499 state_machines,
500 visual_layouts,
501 }))
502}
503
504pub async fn import_state_machines(
506 State(state): State<ManagementState>,
507 Json(request): Json<ImportExportResponse>,
508) -> Result<StatusCode, StatusCode> {
509 let mut manager = state.state_machine_manager.write().await;
510
511 let manifest_json = serde_json::json!({
514 "manifest_version": "1.0",
515 "name": "imported",
516 "version": "1.0.0",
517 "title": "Imported State Machines",
518 "description": "State machines imported via API",
519 "author": "api",
520 "category": "other",
521 "compatibility": {
522 "min_version": "0.1.0",
523 "max_version": null
524 },
525 "files": [],
526 "state_machines": request.state_machines,
527 "state_machine_graphs": request.visual_layouts
528 });
529
530 let manifest: ScenarioManifest =
531 serde_json::from_value(manifest_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
532
533 if let Err(e) = manager.load_from_manifest(&manifest).await {
534 error!("Failed to import state machines: {}", e);
535 return Err(StatusCode::BAD_REQUEST);
536 }
537
538 Ok(StatusCode::CREATED)
541}
542
543pub fn create_state_machine_routes() -> axum::Router<ManagementState> {
548 use axum::{
549 routing::{delete, get, post, put},
550 Router,
551 };
552
553 Router::new()
554 .route("/", get(list_state_machines))
556 .route("/", post(create_state_machine))
557 .route("/{resource_type}", get(get_state_machine))
558 .route("/{resource_type}", put(create_state_machine))
559 .route("/{resource_type}", delete(delete_state_machine))
560
561 .route("/instances", get(list_instances))
563 .route("/instances", post(create_instance))
564 .route("/instances/{resource_id}", get(get_instance))
565 .route("/instances/{resource_id}/state", get(get_current_state))
566 .route("/instances/{resource_id}/next-states", get(get_next_states))
567 .route("/instances/{resource_id}/transition", post(execute_transition))
568
569 .route("/export", get(export_state_machines))
571 .route("/import", post(import_state_machines))
572}