1use axum::{
7 extract::{Path, State},
8 http::StatusCode,
9 Json,
10};
11use mockforge_core::intelligent_behavior::{rules::StateMachine, visual_layout::VisualLayout};
12use mockforge_scenarios::ScenarioManifest;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::collections::HashMap;
16use tracing::error;
17
18use crate::management::ManagementState;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct StateMachineRequest {
26 pub state_machine: StateMachine,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub visual_layout: Option<VisualLayout>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TransitionRequest {
36 pub resource_id: String,
38 pub to_state: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub context: Option<HashMap<String, Value>>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct CreateInstanceRequest {
48 pub resource_id: String,
50 pub resource_type: String,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct StateMachineResponse {
57 pub state_machine: StateMachine,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub visual_layout: Option<VisualLayout>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct StateMachineListResponse {
67 pub state_machines: Vec<StateMachineInfo>,
69 pub total: usize,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct StateMachineInfo {
76 pub resource_type: String,
78 pub state_count: usize,
80 pub transition_count: usize,
82 pub sub_scenario_count: usize,
84 pub has_visual_layout: bool,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct StateInstanceResponse {
91 pub resource_id: String,
93 pub current_state: String,
95 pub resource_type: String,
97 pub history_count: usize,
99 pub state_data: HashMap<String, Value>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct StateInstanceListResponse {
106 pub instances: Vec<StateInstanceResponse>,
108 pub total: usize,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct NextStatesResponse {
115 pub next_states: Vec<String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ImportExportResponse {
122 pub state_machines: Vec<StateMachine>,
124 pub visual_layouts: HashMap<String, VisualLayout>,
126}
127
128pub async fn list_state_machines(
132 State(state): State<ManagementState>,
133) -> Result<Json<StateMachineListResponse>, StatusCode> {
134 let manager = state.state_machine_manager.read().await;
135
136 let machines = manager.list_state_machines().await;
138
139 let mut state_machine_list = Vec::new();
142 for (resource_type, sm) in machines.iter() {
143 let has_visual_layout = manager.get_visual_layout(resource_type).await.is_some();
144 state_machine_list.push(StateMachineInfo {
145 resource_type: resource_type.clone(),
146 state_count: sm.states.len(),
147 transition_count: sm.transitions.len(),
148 sub_scenario_count: sm.sub_scenarios.len(),
149 has_visual_layout,
150 });
151 }
152
153 Ok(Json(StateMachineListResponse {
154 state_machines: state_machine_list.clone(),
155 total: state_machine_list.len(),
156 }))
157}
158
159pub async fn get_state_machine(
161 State(state): State<ManagementState>,
162 Path(resource_type): Path<String>,
163) -> Result<Json<StateMachineResponse>, StatusCode> {
164 let manager = state.state_machine_manager.read().await;
165
166 let state_machine =
167 manager.get_state_machine(&resource_type).await.ok_or(StatusCode::NOT_FOUND)?;
168
169 let visual_layout = manager.get_visual_layout(&resource_type).await;
170
171 let state_machine_json =
174 serde_json::to_value(&state_machine).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
175 let state_machine: StateMachine = serde_json::from_value(state_machine_json)
176 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
177
178 let visual_layout: Option<VisualLayout> = visual_layout
179 .map(|layout| {
180 let layout_json =
181 serde_json::to_value(&layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
182 serde_json::from_value(layout_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
183 })
184 .transpose()
185 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
186
187 Ok(Json(StateMachineResponse {
188 state_machine,
189 visual_layout,
190 }))
191}
192
193pub async fn create_state_machine(
195 State(state): State<ManagementState>,
196 Json(request): Json<StateMachineRequest>,
197) -> Result<Json<StateMachineResponse>, StatusCode> {
198 let manager = state.state_machine_manager.write().await;
199
200 let state_machine_json = serde_json::to_value(&request.state_machine)
205 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
206
207 let mut manifest_json = serde_json::json!({
210 "manifest_version": "1.0",
211 "name": "api",
212 "version": "1.0.0",
213 "title": "API State Machine",
214 "description": "State machine created via API",
215 "author": "api",
216 "category": "other",
217 "compatibility": {
218 "min_version": "0.1.0",
219 "max_version": null
220 },
221 "files": [],
222 "state_machines": [state_machine_json],
223 "state_machine_graphs": {}
224 });
225
226 if let Some(layout) = &request.visual_layout {
227 let layout_json =
228 serde_json::to_value(layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
229 manifest_json["state_machine_graphs"][&request.state_machine.resource_type] = layout_json;
230 }
231
232 let manifest: ScenarioManifest =
233 serde_json::from_value(manifest_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
234
235 if let Some(sm) = manifest.state_machines.first() {
237 if let Err(e) = manager.validate_state_machine(sm) {
238 error!("Invalid state machine: {}", e);
239 return Err(StatusCode::BAD_REQUEST);
240 }
241 }
242
243 if let Err(e) = manager.load_from_manifest(&manifest).await {
244 error!("Failed to load state machine: {}", e);
245 return Err(StatusCode::INTERNAL_SERVER_ERROR);
246 }
247
248 if let Some(ref ws_tx) = state.ws_broadcast {
252 let event = crate::management_ws::MockEvent::state_machine_updated(
253 request.state_machine.resource_type.clone(),
254 request.state_machine.clone(),
255 );
256 let _ = ws_tx.send(event);
257 }
258
259 let state_machine_from_manager = manager
261 .get_state_machine(&request.state_machine.resource_type)
262 .await
263 .ok_or(StatusCode::NOT_FOUND)?;
264 let visual_layout_from_manager =
265 manager.get_visual_layout(&request.state_machine.resource_type).await;
266
267 let state_machine_json = serde_json::to_value(&state_machine_from_manager)
269 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
270 let state_machine: StateMachine = serde_json::from_value(state_machine_json)
271 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
272
273 let visual_layout: Option<VisualLayout> = visual_layout_from_manager
274 .map(|layout| {
275 let layout_json =
276 serde_json::to_value(&layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
277 serde_json::from_value(layout_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
278 })
279 .transpose()
280 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
281
282 Ok(Json(StateMachineResponse {
283 state_machine,
284 visual_layout,
285 }))
286}
287
288pub async fn delete_state_machine(
290 State(state): State<ManagementState>,
291 Path(resource_type): Path<String>,
292) -> Result<StatusCode, StatusCode> {
293 let manager = state.state_machine_manager.write().await;
294
295 let deleted = manager.delete_state_machine(&resource_type).await;
297
298 if !deleted {
299 return Err(StatusCode::NOT_FOUND);
300 }
301
302 if let Some(ref ws_tx) = state.ws_broadcast {
304 let event = crate::management_ws::MockEvent::state_machine_deleted(resource_type);
305 let _ = ws_tx.send(event);
306 }
307
308 Ok(StatusCode::NO_CONTENT)
309}
310
311pub async fn list_instances(
313 State(state): State<ManagementState>,
314) -> Result<Json<StateInstanceListResponse>, StatusCode> {
315 let manager = state.state_machine_manager.read().await;
316
317 let instances = manager.list_instances().await;
318
319 let instance_responses: Vec<StateInstanceResponse> = instances
320 .iter()
321 .map(|i| StateInstanceResponse {
322 resource_id: i.resource_id.clone(),
323 current_state: i.current_state.clone(),
324 resource_type: i.resource_type.clone(),
325 history_count: i.state_history.len(),
326 state_data: i.state_data.clone(),
327 })
328 .collect();
329
330 Ok(Json(StateInstanceListResponse {
331 instances: instance_responses,
332 total: instances.len(),
333 }))
334}
335
336pub async fn get_instance(
338 State(state): State<ManagementState>,
339 Path(resource_id): Path<String>,
340) -> Result<Json<StateInstanceResponse>, StatusCode> {
341 let manager = state.state_machine_manager.read().await;
342
343 let instance = manager.get_instance(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
344
345 Ok(Json(StateInstanceResponse {
346 resource_id: instance.resource_id,
347 current_state: instance.current_state,
348 resource_type: instance.resource_type,
349 history_count: instance.state_history.len(),
350 state_data: instance.state_data,
351 }))
352}
353
354pub async fn create_instance(
356 State(state): State<ManagementState>,
357 Json(request): Json<CreateInstanceRequest>,
358) -> Result<Json<StateInstanceResponse>, StatusCode> {
359 let manager = state.state_machine_manager.write().await;
360
361 if let Err(e) = manager.create_instance(&request.resource_id, &request.resource_type).await {
362 error!("Failed to create instance: {}", e);
363 return Err(StatusCode::BAD_REQUEST);
364 }
365
366 let instance = manager
367 .get_instance(&request.resource_id)
368 .await
369 .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
370
371 if let Some(ref ws_tx) = state.ws_broadcast {
373 let event = crate::management_ws::MockEvent::state_instance_created(
374 instance.resource_id.clone(),
375 instance.resource_type.clone(),
376 instance.current_state.clone(),
377 );
378 let _ = ws_tx.send(event);
379 }
380
381 Ok(Json(StateInstanceResponse {
382 resource_id: instance.resource_id,
383 current_state: instance.current_state,
384 resource_type: instance.resource_type,
385 history_count: instance.state_history.len(),
386 state_data: instance.state_data,
387 }))
388}
389
390pub async fn execute_transition(
392 State(state): State<ManagementState>,
393 Json(request): Json<TransitionRequest>,
394) -> Result<Json<StateInstanceResponse>, StatusCode> {
395 let manager = state.state_machine_manager.write().await;
396
397 if let Err(e) = manager
398 .execute_transition(&request.resource_id, &request.to_state, request.context)
399 .await
400 {
401 error!("Failed to execute transition: {}", e);
402 return Err(StatusCode::BAD_REQUEST);
403 }
404
405 let instance = manager.get_instance(&request.resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
406
407 let from_state = instance
409 .state_history
410 .last()
411 .map(|h| h.from_state.clone())
412 .unwrap_or_else(|| instance.current_state.clone());
413
414 if let Some(ref ws_tx) = state.ws_broadcast {
416 let event = crate::management_ws::MockEvent::state_transitioned(
417 instance.resource_id.clone(),
418 instance.resource_type.clone(),
419 from_state,
420 instance.current_state.clone(),
421 instance.state_data.clone(),
422 );
423 let _ = ws_tx.send(event);
424 }
425
426 Ok(Json(StateInstanceResponse {
427 resource_id: instance.resource_id,
428 current_state: instance.current_state,
429 resource_type: instance.resource_type,
430 history_count: instance.state_history.len(),
431 state_data: instance.state_data,
432 }))
433}
434
435pub async fn get_next_states(
437 State(state): State<ManagementState>,
438 Path(resource_id): Path<String>,
439) -> Result<Json<NextStatesResponse>, StatusCode> {
440 let manager = state.state_machine_manager.read().await;
441
442 let next_states =
443 manager.get_next_states(&resource_id).await.map_err(|_| StatusCode::NOT_FOUND)?;
444
445 Ok(Json(NextStatesResponse { next_states }))
446}
447
448pub async fn get_current_state(
450 State(state): State<ManagementState>,
451 Path(resource_id): Path<String>,
452) -> Result<Json<serde_json::Value>, StatusCode> {
453 let manager = state.state_machine_manager.read().await;
454
455 let current_state =
456 manager.get_current_state(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
457
458 Ok(Json(serde_json::json!({
459 "resource_id": resource_id,
460 "current_state": current_state
461 })))
462}
463
464pub async fn export_state_machines(
466 State(state): State<ManagementState>,
467) -> Result<Json<ImportExportResponse>, StatusCode> {
468 let manager = state.state_machine_manager.read().await;
469
470 let (state_machines_from_manager, visual_layouts_from_manager) = manager.export_all().await;
472
473 let state_machines: Vec<StateMachine> = state_machines_from_manager
475 .into_iter()
476 .map(|sm| {
477 let json = serde_json::to_value(&sm).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
478 serde_json::from_value(json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
479 })
480 .collect::<Result<Vec<_>, StatusCode>>()?;
481
482 let visual_layouts: HashMap<String, VisualLayout> = visual_layouts_from_manager
483 .into_iter()
484 .map(|(k, v)| {
485 let json = serde_json::to_value(&v).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
486 let layout: VisualLayout =
487 serde_json::from_value(json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
488 Ok((k, layout))
489 })
490 .collect::<Result<HashMap<_, _>, StatusCode>>()?;
491
492 Ok(Json(ImportExportResponse {
493 state_machines,
494 visual_layouts,
495 }))
496}
497
498pub async fn import_state_machines(
500 State(state): State<ManagementState>,
501 Json(request): Json<ImportExportResponse>,
502) -> Result<StatusCode, StatusCode> {
503 let manager = state.state_machine_manager.write().await;
504
505 let manifest_json = serde_json::json!({
508 "manifest_version": "1.0",
509 "name": "imported",
510 "version": "1.0.0",
511 "title": "Imported State Machines",
512 "description": "State machines imported via API",
513 "author": "api",
514 "category": "other",
515 "compatibility": {
516 "min_version": "0.1.0",
517 "max_version": null
518 },
519 "files": [],
520 "state_machines": request.state_machines,
521 "state_machine_graphs": request.visual_layouts
522 });
523
524 let manifest: ScenarioManifest =
525 serde_json::from_value(manifest_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
526
527 if let Err(e) = manager.load_from_manifest(&manifest).await {
528 error!("Failed to import state machines: {}", e);
529 return Err(StatusCode::BAD_REQUEST);
530 }
531
532 Ok(StatusCode::CREATED)
535}
536
537pub fn create_state_machine_routes() -> axum::Router<ManagementState> {
542 use axum::{
543 routing::{delete, get, post, put},
544 Router,
545 };
546
547 Router::new()
548 .route("/", get(list_state_machines))
550 .route("/", post(create_state_machine))
551 .route("/{resource_type}", get(get_state_machine))
552 .route("/{resource_type}", put(create_state_machine))
553 .route("/{resource_type}", delete(delete_state_machine))
554
555 .route("/instances", get(list_instances))
557 .route("/instances", post(create_instance))
558 .route("/instances/{resource_id}", get(get_instance))
559 .route("/instances/{resource_id}/state", get(get_current_state))
560 .route("/instances/{resource_id}/next-states", get(get_next_states))
561 .route("/instances/{resource_id}/transition", post(execute_transition))
562
563 .route("/export", get(export_state_machines))
565 .route("/import", post(import_state_machines))
566}