1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// =============================================================================
// #######
// ### ### F: state.rs
// ## ## ## ## P: AppCore-Runtime
// ## ##
// C: 2026/05/29 20:47:35 by dnettoRaw
// ## ## ## ## U: 2026/06/04 11:51:29 by dnettoRaw
// ########### S: 0.6.0
// =============================================================================
//! State contracts: registry plus a minimal deterministic state machine.
use crate::error::{RuntimeError, RuntimeResult};
use crate::ids::{EventName, StateName};
use crate::registry::NameRegistry;
/// Minimal runtime state contract.
pub trait RuntimeState {
/// Returns the stable state name.
fn name(&self) -> &StateName;
}
/// Explicit state transition definition.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateTransition {
/// Source state.
pub from: StateName,
/// Event that triggers the transition.
pub event: EventName,
/// Destination state.
pub to: StateName,
}
/// Minimal state machine based on explicit transitions.
#[derive(Debug, Clone)]
pub struct StateMachine {
current: StateName,
transitions: Vec<StateTransition>,
}
impl StateMachine {
/// Creates a state machine without registered transitions.
pub fn new(initial: StateName) -> Self {
Self {
current: initial,
transitions: Vec::new(),
}
}
/// Returns the current state.
pub fn current(&self) -> &StateName {
&self.current
}
/// Registers one deterministic transition, rejecting duplicate source/event pairs.
pub fn add_transition(&mut self, transition: StateTransition) -> RuntimeResult<()> {
let duplicated = self
.transitions
.iter()
.any(|existing| existing.from == transition.from && existing.event == transition.event);
if duplicated {
return Err(RuntimeError::DuplicateStateTransition);
}
self.transitions.push(transition);
Ok(())
}
/// Reports whether `event` can be applied from the current state.
pub fn can_apply(&self, event: &EventName) -> bool {
self.transitions
.iter()
.any(|transition| transition.from == self.current && &transition.event == event)
}
/// Applies `event` and returns the new current state.
pub fn apply(&mut self, event: &EventName) -> RuntimeResult<&StateName> {
let transition = self
.transitions
.iter()
.find(|transition| transition.from == self.current && &transition.event == event);
let Some(transition) = transition else {
return Err(RuntimeError::InvalidStateTransition);
};
self.current = transition.to.clone();
Ok(&self.current)
}
/// Returns all transitions in registration order.
pub fn transitions(&self) -> &[StateTransition] {
&self.transitions
}
}
/// Ordered registry of declared state names.
#[derive(Debug, Default)]
pub struct StateRegistry {
names: NameRegistry<StateName>,
}
impl StateRegistry {
/// Creates an empty state registry.
pub fn new() -> Self {
Self::default()
}
/// Registers a state name, rejecting duplicates.
pub fn register(&mut self, name: StateName) -> RuntimeResult<()> {
self.names.register(name, "state")
}
/// Reports whether a state name is registered.
pub fn contains(&self, name: &StateName) -> bool {
self.names.contains(name)
}
/// Returns the number of registered state names.
pub fn len(&self) -> usize {
self.names.len()
}
/// Reports whether no states are registered.
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
/// Returns state names in registration order.
pub fn list(&self) -> &[StateName] {
self.names.list()
}
}
#[cfg(test)]
#[path = "state_tests.rs"]
mod tests;