awaken_contract/state/
command.rs1use std::ops::{Deref, DerefMut};
2
3use crate::StateError;
4use crate::model::{EffectSpec, ScheduledAction, ScheduledActionSpec, TypedEffect};
5
6use super::{MergeStrategy, MutationBatch};
7
8#[derive(Debug)]
15pub struct StateCommand {
16 pub patch: MutationBatch,
17 pub scheduled_actions: Vec<ScheduledAction>,
18 pub effects: Vec<TypedEffect>,
19}
20
21impl StateCommand {
22 pub fn new() -> Self {
23 Self {
24 patch: MutationBatch::new(),
25 scheduled_actions: Vec::new(),
26 effects: Vec::new(),
27 }
28 }
29
30 pub fn with_base_revision(mut self, revision: u64) -> Self {
31 self.patch = self.patch.with_base_revision(revision);
32 self
33 }
34
35 pub fn is_empty(&self) -> bool {
36 self.patch.is_empty() && self.scheduled_actions.is_empty() && self.effects.is_empty()
37 }
38
39 pub fn scheduled_actions(&self) -> &[ScheduledAction] {
41 &self.scheduled_actions
42 }
43
44 pub fn emit<E: EffectSpec>(&mut self, payload: E::Payload) -> Result<(), StateError> {
45 self.effects.push(TypedEffect::from_spec::<E>(&payload)?);
46 Ok(())
47 }
48
49 pub fn schedule_action<A: ScheduledActionSpec>(
50 &mut self,
51 payload: A::Payload,
52 ) -> Result<(), StateError> {
53 self.scheduled_actions.push(ScheduledAction::new(
54 A::PHASE,
55 A::KEY,
56 A::encode_payload(&payload)?,
57 ));
58 Ok(())
59 }
60
61 pub fn extend(&mut self, mut other: Self) -> Result<(), StateError> {
62 self.patch.extend(other.patch)?;
63 self.scheduled_actions.append(&mut other.scheduled_actions);
64 self.effects.append(&mut other.effects);
65 Ok(())
66 }
67
68 pub fn merge_parallel<F>(self, other: Self, strategy: F) -> Result<Self, StateError>
70 where
71 F: Fn(&str) -> MergeStrategy,
72 {
73 let patch = self.patch.merge_parallel(other.patch, strategy)?;
74 let mut scheduled_actions = self.scheduled_actions;
75 scheduled_actions.extend(other.scheduled_actions);
76 let mut effects = self.effects;
77 effects.extend(other.effects);
78 Ok(Self {
79 patch,
80 scheduled_actions,
81 effects,
82 })
83 }
84}
85
86impl Default for StateCommand {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92impl Deref for StateCommand {
93 type Target = MutationBatch;
94
95 fn deref(&self) -> &Self::Target {
96 &self.patch
97 }
98}
99
100impl DerefMut for StateCommand {
101 fn deref_mut(&mut self) -> &mut Self::Target {
102 &mut self.patch
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::model::{EffectSpec, Phase, ScheduledActionSpec};
109
110 use super::*;
111
112 struct TestAction;
113
114 impl ScheduledActionSpec for TestAction {
115 const KEY: &'static str = "test.action";
116 const PHASE: Phase = Phase::RunStart;
117 type Payload = String;
118 }
119
120 struct CustomEffect;
121
122 impl EffectSpec for CustomEffect {
123 const KEY: &'static str = "test.custom_effect";
124 type Payload = String;
125 }
126
127 #[test]
128 fn state_command_accumulates_actions_and_effects() {
129 let mut command = StateCommand::new();
130 command
131 .schedule_action::<TestAction>("go".into())
132 .expect("schedule should succeed");
133 command
134 .emit::<CustomEffect>("payload".into())
135 .expect("effect should encode");
136
137 assert!(!command.is_empty());
138 assert_eq!(command.scheduled_actions.len(), 1);
139 assert_eq!(command.effects.len(), 1);
140 }
141
142 #[test]
143 fn state_command_extend_merges_all() {
144 let mut left = StateCommand::new();
145 left.schedule_action::<TestAction>("left".into()).unwrap();
146
147 let mut right = StateCommand::new();
148 right.emit::<CustomEffect>("effect".into()).unwrap();
149
150 left.extend(right).unwrap();
151 assert_eq!(left.scheduled_actions.len(), 1);
152 assert_eq!(left.effects.len(), 1);
153 }
154
155 #[test]
156 fn state_command_new_is_empty() {
157 let cmd = StateCommand::new();
158 assert!(cmd.is_empty());
159 }
160
161 #[test]
162 fn state_command_merge_parallel_combines_all_fields() {
163 let mut left = StateCommand::new();
164 left.schedule_action::<TestAction>("left_action".into())
165 .unwrap();
166 left.emit::<CustomEffect>("left_effect".into()).unwrap();
167
168 let mut right = StateCommand::new();
169 right
170 .schedule_action::<TestAction>("right_action".into())
171 .unwrap();
172 right.emit::<CustomEffect>("right_effect".into()).unwrap();
173
174 let merged = left
175 .merge_parallel(right, |_| MergeStrategy::Commutative)
176 .unwrap();
177 assert_eq!(merged.scheduled_actions.len(), 2);
178 assert_eq!(merged.effects.len(), 2);
179 }
180}