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