1use crate::error::{Result, ScenarioError};
7use crate::manifest::ScenarioManifest;
8use mockforge_core::intelligent_behavior::{
9 condition_evaluator::ConditionEvaluator, history::HistoryManager, rules::StateMachine,
10 sub_scenario::SubScenario, visual_layout::VisualLayout,
11};
12use serde_json::Value;
13use std::collections::HashMap;
14use std::sync::Arc;
15use tokio::sync::RwLock;
16use tracing::{debug, info, warn};
17use uuid::Uuid;
18
19#[derive(Debug, Clone)]
23pub struct StateInstance {
24 pub resource_id: String,
26
27 pub current_state: String,
29
30 pub resource_type: String,
32
33 pub state_history: Vec<StateHistoryEntry>,
35
36 pub state_data: HashMap<String, Value>,
38}
39
40#[derive(Debug, Clone)]
42pub struct StateHistoryEntry {
43 pub from_state: String,
45
46 pub to_state: String,
48
49 pub timestamp: chrono::DateTime<chrono::Utc>,
51
52 pub transition_id: Option<String>,
54}
55
56impl StateInstance {
57 pub fn new(
59 resource_id: impl Into<String>,
60 resource_type: impl Into<String>,
61 initial_state: impl Into<String>,
62 ) -> Self {
63 Self {
64 resource_id: resource_id.into(),
65 current_state: initial_state.into(),
66 resource_type: resource_type.into(),
67 state_history: Vec::new(),
68 state_data: HashMap::new(),
69 }
70 }
71
72 pub fn transition_to(&mut self, to_state: impl Into<String>, transition_id: Option<String>) {
74 let from_state = self.current_state.clone();
75 let to_state = to_state.into();
76
77 self.state_history.push(StateHistoryEntry {
78 from_state: from_state.clone(),
79 to_state: to_state.clone(),
80 timestamp: chrono::Utc::now(),
81 transition_id,
82 });
83
84 self.current_state = to_state;
85 }
86
87 pub fn current_state(&self) -> &str {
89 &self.current_state
90 }
91
92 pub fn set_data(&mut self, key: impl Into<String>, value: Value) {
94 self.state_data.insert(key.into(), value);
95 }
96
97 pub fn get_data(&self, key: &str) -> Option<&Value> {
99 self.state_data.get(key)
100 }
101}
102
103pub struct ScenarioStateMachineManager {
108 state_machines: Arc<RwLock<HashMap<String, StateMachine>>>,
110
111 instances: Arc<RwLock<HashMap<String, StateInstance>>>,
113
114 visual_layouts: Arc<RwLock<HashMap<String, VisualLayout>>>,
116
117 history_managers: Arc<RwLock<HashMap<String, HistoryManager>>>,
119}
120
121impl ScenarioStateMachineManager {
122 pub fn new() -> Self {
124 Self {
125 state_machines: Arc::new(RwLock::new(HashMap::new())),
126 instances: Arc::new(RwLock::new(HashMap::new())),
127 visual_layouts: Arc::new(RwLock::new(HashMap::new())),
128 history_managers: Arc::new(RwLock::new(HashMap::new())),
129 }
130 }
131
132 pub async fn load_from_manifest(&self, manifest: &ScenarioManifest) -> Result<()> {
137 info!(
138 "Loading {} state machines from scenario '{}'",
139 manifest.state_machines.len(),
140 manifest.name
141 );
142
143 let mut state_machines = self.state_machines.write().await;
144 let mut visual_layouts = self.visual_layouts.write().await;
145
146 for state_machine in &manifest.state_machines {
147 self.validate_state_machine(state_machine)?;
149
150 let resource_type = state_machine.resource_type.clone();
152 state_machines.insert(resource_type.clone(), state_machine.clone());
153
154 if let Some(layout) = &state_machine.visual_layout {
156 visual_layouts.insert(resource_type.clone(), layout.clone());
157 }
158
159 if let Some(layout) = manifest.state_machine_graphs.get(&resource_type) {
161 visual_layouts.insert(resource_type.clone(), layout.clone());
162 }
163
164 info!("Loaded state machine for resource type '{}'", resource_type);
165 }
166
167 Ok(())
168 }
169
170 pub fn validate_state_machine(&self, state_machine: &StateMachine) -> Result<()> {
178 if !state_machine.states.contains(&state_machine.initial_state) {
180 return Err(ScenarioError::InvalidManifest(format!(
181 "State machine '{}' has initial state '{}' that is not in states list",
182 state_machine.resource_type, state_machine.initial_state
183 )));
184 }
185
186 for transition in &state_machine.transitions {
188 if !state_machine.states.contains(&transition.from_state) {
189 return Err(ScenarioError::InvalidManifest(format!(
190 "State machine '{}' has transition from invalid state '{}'",
191 state_machine.resource_type, transition.from_state
192 )));
193 }
194
195 if !state_machine.states.contains(&transition.to_state) {
196 return Err(ScenarioError::InvalidManifest(format!(
197 "State machine '{}' has transition to invalid state '{}'",
198 state_machine.resource_type, transition.to_state
199 )));
200 }
201
202 if let Some(ref sub_scenario_id) = transition.sub_scenario_ref {
204 if state_machine.get_sub_scenario(sub_scenario_id).is_none() {
205 return Err(ScenarioError::InvalidManifest(format!(
206 "State machine '{}' references non-existent sub-scenario '{}'",
207 state_machine.resource_type, sub_scenario_id
208 )));
209 }
210 }
211 }
212
213 for sub_scenario in &state_machine.sub_scenarios {
215 self.validate_state_machine(&sub_scenario.state_machine)?;
216 }
217
218 Ok(())
219 }
220
221 pub async fn get_state_machine(&self, resource_type: &str) -> Option<StateMachine> {
223 let state_machines = self.state_machines.read().await;
224 state_machines.get(resource_type).cloned()
225 }
226
227 pub async fn get_visual_layout(&self, resource_type: &str) -> Option<VisualLayout> {
229 let layouts = self.visual_layouts.read().await;
230 layouts.get(resource_type).cloned()
231 }
232
233 pub async fn create_instance(
237 &self,
238 resource_id: impl Into<String>,
239 resource_type: impl Into<String>,
240 ) -> Result<()> {
241 let resource_id = resource_id.into();
242 let resource_type = resource_type.into();
243
244 let state_machine = self.get_state_machine(&resource_type).await.ok_or_else(|| {
246 ScenarioError::InvalidManifest(format!(
247 "No state machine found for resource type '{}'",
248 resource_type
249 ))
250 })?;
251
252 let instance = StateInstance::new(
254 resource_id.clone(),
255 resource_type.clone(),
256 state_machine.initial_state.clone(),
257 );
258
259 let mut instances = self.instances.write().await;
260 instances.insert(resource_id, instance);
261
262 Ok(())
263 }
264
265 pub async fn get_current_state(&self, resource_id: &str) -> Option<String> {
267 let instances = self.instances.read().await;
268 instances.get(resource_id).map(|i| i.current_state.clone())
269 }
270
271 pub async fn execute_transition(
276 &self,
277 resource_id: &str,
278 to_state: impl Into<String>,
279 context: Option<HashMap<String, Value>>,
280 ) -> Result<()> {
281 let to_state = to_state.into();
282 let mut instances = self.instances.write().await;
283
284 let instance = instances.get_mut(resource_id).ok_or_else(|| {
285 ScenarioError::InvalidManifest(format!(
286 "No state instance found for resource '{}'",
287 resource_id
288 ))
289 })?;
290
291 let state_machine =
293 self.get_state_machine(&instance.resource_type).await.ok_or_else(|| {
294 ScenarioError::InvalidManifest(format!(
295 "No state machine found for resource type '{}'",
296 instance.resource_type
297 ))
298 })?;
299
300 let transition = state_machine
302 .transitions
303 .iter()
304 .find(|t| t.from_state == instance.current_state && t.to_state == to_state);
305
306 let transition = transition.ok_or_else(|| {
307 ScenarioError::InvalidManifest(format!(
308 "No valid transition from '{}' to '{}' for resource '{}'",
309 instance.current_state, to_state, resource_id
310 ))
311 })?;
312
313 if let Some(ref condition_expr) = transition.condition_expression {
315 let mut evaluator = ConditionEvaluator::new();
316
317 if let Some(ref ctx) = context {
319 for (key, value) in ctx {
320 evaluator.set_variable(key.clone(), value.clone());
321 }
322 }
323
324 for (key, value) in &instance.state_data {
326 evaluator.set_variable(key.clone(), value.clone());
327 }
328
329 match evaluator.evaluate(condition_expr) {
331 Ok(true) => {
332 }
334 Ok(false) => {
335 return Err(ScenarioError::InvalidManifest(format!(
336 "Transition condition not met: {}",
337 condition_expr
338 )));
339 }
340 Err(e) => {
341 return Err(ScenarioError::InvalidManifest(format!(
342 "Error evaluating transition condition: {}",
343 e
344 )));
345 }
346 }
347 }
348
349 if let Some(ref sub_scenario_id) = transition.sub_scenario_ref {
351 if let Some(sub_scenario) = state_machine.get_sub_scenario(sub_scenario_id) {
352 debug!("Executing sub-scenario '{}' for transition", sub_scenario_id);
353
354 match self
356 .execute_sub_scenario(
357 sub_scenario,
358 &instance.state_data,
359 &sub_scenario.state_machine.resource_type,
360 )
361 .await
362 {
363 Ok(output_data) => {
364 for (sub_var, parent_var) in &sub_scenario.output_mapping {
366 if let Some(value) = output_data.get(sub_var) {
367 instance.state_data.insert(parent_var.clone(), value.clone());
368 debug!(
369 "Mapped sub-scenario output '{}' to parent variable '{}'",
370 sub_var, parent_var
371 );
372 }
373 }
374 }
375 Err(e) => {
376 warn!("Sub-scenario execution failed: {}", e);
377 }
380 }
381 }
382 }
383
384 instance.transition_to(
386 to_state.clone(),
387 Some(format!("{}-{}", instance.current_state, to_state)),
388 );
389
390 let mut history_managers = self.history_managers.write().await;
392 let history = history_managers
393 .entry(instance.resource_type.clone())
394 .or_insert_with(HistoryManager::new);
395 info!(
399 "Resource '{}' transitioned from '{}' to '{}'",
400 resource_id, instance.current_state, to_state
401 );
402
403 Ok(())
404 }
405
406 pub async fn list_instances(&self) -> Vec<StateInstance> {
408 let instances = self.instances.read().await;
409 instances.values().cloned().collect()
410 }
411
412 pub async fn get_instance(&self, resource_id: &str) -> Option<StateInstance> {
414 let instances = self.instances.read().await;
415 instances.get(resource_id).cloned()
416 }
417
418 pub async fn delete_instance(&self, resource_id: &str) -> bool {
420 let mut instances = self.instances.write().await;
421 instances.remove(resource_id).is_some()
422 }
423
424 pub async fn get_next_states(&self, resource_id: &str) -> Result<Vec<String>> {
428 let instances = self.instances.read().await;
429 let instance = instances.get(resource_id).ok_or_else(|| {
430 ScenarioError::InvalidManifest(format!(
431 "No state instance found for resource '{}'",
432 resource_id
433 ))
434 })?;
435
436 let state_machine =
437 self.get_state_machine(&instance.resource_type).await.ok_or_else(|| {
438 ScenarioError::InvalidManifest(format!(
439 "No state machine found for resource type '{}'",
440 instance.resource_type
441 ))
442 })?;
443
444 Ok(state_machine.next_states(&instance.current_state))
445 }
446
447 pub async fn set_visual_layout(&self, resource_type: &str, layout: VisualLayout) {
449 let mut layouts = self.visual_layouts.write().await;
450 layouts.insert(resource_type.to_string(), layout);
451 }
452
453 pub async fn clear(&self) {
455 let mut state_machines = self.state_machines.write().await;
456 let mut instances = self.instances.write().await;
457 let mut layouts = self.visual_layouts.write().await;
458 let mut history = self.history_managers.write().await;
459
460 state_machines.clear();
461 instances.clear();
462 layouts.clear();
463 history.clear();
464 }
465
466 pub async fn delete_state_machine(&self, resource_type: &str) -> bool {
471 let mut state_machines = self.state_machines.write().await;
472 let mut visual_layouts = self.visual_layouts.write().await;
473 let mut instances = self.instances.write().await;
474 let mut history_managers = self.history_managers.write().await;
475
476 let removed = state_machines.remove(resource_type).is_some();
478
479 visual_layouts.remove(resource_type);
481
482 instances.retain(|_, instance| instance.resource_type != resource_type);
484
485 history_managers.remove(resource_type);
487
488 if removed {
489 info!("Deleted state machine for resource type '{}'", resource_type);
490 }
491
492 removed
493 }
494
495 pub async fn list_state_machines(&self) -> Vec<(String, StateMachine)> {
499 let state_machines = self.state_machines.read().await;
500 state_machines.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
501 }
502
503 pub async fn export_all(&self) -> (Vec<StateMachine>, HashMap<String, VisualLayout>) {
508 let state_machines = self.state_machines.read().await;
509 let visual_layouts = self.visual_layouts.read().await;
510
511 let machines: Vec<StateMachine> = state_machines.values().cloned().collect();
512 let layouts: HashMap<String, VisualLayout> =
513 visual_layouts.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
514
515 (machines, layouts)
516 }
517
518 async fn execute_sub_scenario(
523 &self,
524 sub_scenario: &SubScenario,
525 parent_state_data: &HashMap<String, Value>,
526 sub_resource_type: &str,
527 ) -> Result<HashMap<String, Value>> {
528 let sub_instance_id = format!("sub-{}-{}", sub_scenario.id, Uuid::new_v4());
530
531 let mut sub_instance = StateInstance::new(
533 sub_instance_id.clone(),
534 sub_resource_type.to_string(),
535 sub_scenario.state_machine.initial_state.clone(),
536 );
537
538 for (parent_var, sub_var) in &sub_scenario.input_mapping {
540 let value = if parent_var.contains('.') {
543 let parts: Vec<&str> = parent_var.split('.').collect();
545 if parts.len() == 2 && parts[0] == "parent" {
546 parent_state_data.get(parts[1]).cloned()
547 } else {
548 parent_state_data.get(parent_var).cloned()
550 }
551 } else {
552 parent_state_data.get(parent_var).cloned()
553 };
554
555 if let Some(val) = value {
556 sub_instance.set_data(sub_var.clone(), val.clone());
557 debug!(
558 "Mapped parent variable '{}' to sub-scenario variable '{}'",
559 parent_var, sub_var
560 );
561 } else {
562 warn!(
563 "Parent variable '{}' not found in state data, skipping input mapping",
564 parent_var
565 );
566 }
567 }
568
569 {
571 let mut instances = self.instances.write().await;
572 instances.insert(sub_instance_id.clone(), sub_instance.clone());
573 }
574
575 let max_iterations = 100; let mut iteration = 0;
579
580 loop {
581 if iteration >= max_iterations {
582 warn!("Sub-scenario '{}' exceeded maximum iterations, stopping", sub_scenario.id);
583 break;
584 }
585 iteration += 1;
586
587 let current_state = sub_instance.current_state.clone();
589
590 let has_outgoing = sub_scenario
592 .state_machine
593 .transitions
594 .iter()
595 .any(|t| t.from_state == current_state);
596
597 if !has_outgoing {
598 debug!(
599 "Sub-scenario '{}' reached final state '{}'",
600 sub_scenario.id, current_state
601 );
602 break;
603 }
604
605 let possible_transitions: Vec<_> = sub_scenario
607 .state_machine
608 .transitions
609 .iter()
610 .filter(|t| t.from_state == current_state)
611 .collect();
612
613 if possible_transitions.is_empty() {
614 debug!(
615 "Sub-scenario '{}' has no valid transitions from state '{}', stopping",
616 sub_scenario.id, current_state
617 );
618 break;
619 }
620
621 let selected_transition = possible_transitions[0];
624 let next_state = selected_transition.to_state.clone();
625
626 if let Some(ref condition_expr) = selected_transition.condition_expression {
628 let mut evaluator = ConditionEvaluator::new();
629
630 for (key, value) in &sub_instance.state_data {
632 evaluator.set_variable(key.clone(), value.clone());
633 }
634
635 match evaluator.evaluate(condition_expr) {
637 Ok(true) => {
638 }
640 Ok(false) => {
641 debug!(
643 "Sub-scenario transition condition not met: {}, trying next transition",
644 condition_expr
645 );
646 if possible_transitions.len() > 1 {
647 let next_transition = possible_transitions[1];
649 let next_state = next_transition.to_state.clone();
650 sub_instance.transition_to(next_state, None);
651 } else {
652 break;
654 }
655 continue;
656 }
657 Err(e) => {
658 warn!(
659 "Error evaluating sub-scenario transition condition: {}, stopping",
660 e
661 );
662 break;
663 }
664 }
665 }
666
667 sub_instance.transition_to(next_state.clone(), None);
669 debug!(
670 "Sub-scenario '{}' transitioned from '{}' to '{}'",
671 sub_scenario.id, current_state, next_state
672 );
673
674 {
676 let mut instances = self.instances.write().await;
677 if let Some(stored) = instances.get_mut(&sub_instance_id) {
678 *stored = sub_instance.clone();
679 }
680 }
681 }
682
683 let output_data = sub_instance.state_data.clone();
685
686 {
688 let mut instances = self.instances.write().await;
689 instances.remove(&sub_instance_id);
690 }
691
692 info!(
693 "Sub-scenario '{}' completed after {} iterations, final state: '{}'",
694 sub_scenario.id, iteration, sub_instance.current_state
695 );
696
697 Ok(output_data)
698 }
699}
700
701impl Default for ScenarioStateMachineManager {
702 fn default() -> Self {
703 Self::new()
704 }
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710 use mockforge_core::intelligent_behavior::rules::{StateMachine, StateTransition};
711
712 fn create_test_state_machine() -> StateMachine {
713 StateMachine::new(
714 "order",
715 vec![
716 "pending".to_string(),
717 "processing".to_string(),
718 "shipped".to_string(),
719 ],
720 "pending",
721 )
722 .add_transition(StateTransition::new("pending", "processing"))
723 .add_transition(StateTransition::new("processing", "shipped"))
724 }
725
726 #[tokio::test]
727 async fn test_load_state_machine() {
728 let manager = ScenarioStateMachineManager::new();
729 let mut manifest = ScenarioManifest::new(
730 "test".to_string(),
731 "1.0.0".to_string(),
732 "Test".to_string(),
733 "Test scenario".to_string(),
734 );
735 manifest.state_machines.push(create_test_state_machine());
736
737 let result = manager.load_from_manifest(&manifest).await;
738 assert!(result.is_ok());
739
740 let state_machine = manager.get_state_machine("order").await;
741 assert!(state_machine.is_some());
742 assert_eq!(state_machine.unwrap().resource_type, "order");
743 }
744
745 #[tokio::test]
746 async fn test_create_and_transition() {
747 let manager = ScenarioStateMachineManager::new();
748 let mut manifest = ScenarioManifest::new(
749 "test".to_string(),
750 "1.0.0".to_string(),
751 "Test".to_string(),
752 "Test scenario".to_string(),
753 );
754 manifest.state_machines.push(create_test_state_machine());
755
756 manager.load_from_manifest(&manifest).await.unwrap();
757 manager.create_instance("order-1", "order").await.unwrap();
758
759 let state = manager.get_current_state("order-1").await;
760 assert_eq!(state, Some("pending".to_string()));
761
762 manager.execute_transition("order-1", "processing", None).await.unwrap();
763 let state = manager.get_current_state("order-1").await;
764 assert_eq!(state, Some("processing".to_string()));
765 }
766
767 #[tokio::test]
768 async fn test_conditional_transition() {
769 let manager = ScenarioStateMachineManager::new();
770 let state_machine = StateMachine::new(
771 "order",
772 vec![
773 "pending".to_string(),
774 "approved".to_string(),
775 "rejected".to_string(),
776 ],
777 "pending",
778 )
779 .add_transition(
780 StateTransition::new("pending", "approved").with_condition_expression("amount > 100"),
781 )
782 .add_transition(
783 StateTransition::new("pending", "rejected").with_condition_expression("amount <= 100"),
784 );
785
786 let mut manifest = ScenarioManifest::new(
787 "test".to_string(),
788 "1.0.0".to_string(),
789 "Test".to_string(),
790 "Test scenario".to_string(),
791 );
792 manifest.state_machines.push(state_machine);
793
794 manager.load_from_manifest(&manifest).await.unwrap();
795 manager.create_instance("order-1", "order").await.unwrap();
796
797 let mut context = HashMap::new();
799 context.insert("amount".to_string(), Value::Number(serde_json::Number::from(150)));
800 manager.execute_transition("order-1", "approved", Some(context)).await.unwrap();
801 assert_eq!(manager.get_current_state("order-1").await, Some("approved".to_string()));
802 }
803}