modality_mutator_protocol/
lib.rs1#![deny(warnings, clippy::all)]
5
6pub mod params_attributes;
7
8pub mod actuator;
9pub mod attrs;
10#[cfg(feature = "proptest_strategies")]
11pub use modality_api::proptest_strategies;
12#[cfg(feature = "cbor")]
13pub use modality_api::protocol as cbor;
14pub mod descriptor;
15
16pub mod mutator {
17 use async_trait::async_trait;
18
19 use crate::actuator::MutatorActuator;
20 use crate::attrs::{AttrKey, AttrVal};
21 use crate::descriptor::MutatorDescriptor;
22 use std::collections::BTreeMap;
23
24 pub trait ActuatorDescriptor: MutatorActuator + MutatorDescriptor {}
25
26 pub struct CombinedMutator<A, D>
27 where
28 A: MutatorActuator,
29 D: MutatorDescriptor,
30 {
31 actuator: A,
32 descriptor: D,
33 }
34
35 impl<A: MutatorActuator, D: MutatorDescriptor> CombinedMutator<A, D> {
36 pub fn new(actuator: A, descriptor: D) -> Self {
37 CombinedMutator {
38 actuator,
39 descriptor,
40 }
41 }
42 pub fn actuator_ref(&self) -> &A {
43 &self.actuator
44 }
45 pub fn actuator_mut(&mut self) -> &mut A {
46 &mut self.actuator
47 }
48 pub fn descriptor_ref(&self) -> &D {
49 &self.descriptor
50 }
51 pub fn descriptor_mut(&mut self) -> &mut D {
52 &mut self.descriptor
53 }
54 }
55
56 #[async_trait]
57 impl<A: MutatorActuator + Send + Sync, D: MutatorDescriptor + Send + Sync> MutatorActuator
58 for CombinedMutator<A, D>
59 {
60 async fn inject(
61 &mut self,
62 mutation_id: uuid::Uuid,
63 params: BTreeMap<AttrKey, AttrVal>,
64 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
65 self.actuator.inject(mutation_id, params).await
66 }
67
68 async fn reset(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
69 self.actuator.reset().await
70 }
71 }
72
73 impl<A: MutatorActuator, D: MutatorDescriptor> MutatorDescriptor for CombinedMutator<A, D> {
74 fn get_description_attributes(&self) -> Box<dyn Iterator<Item = (AttrKey, AttrVal)> + '_> {
75 self.descriptor.get_description_attributes()
76 }
77 }
78
79 impl<A: MutatorActuator + Send + Sync, D: MutatorDescriptor + Send + Sync> ActuatorDescriptor
80 for CombinedMutator<A, D>
81 {
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use crate::actuator::MutatorActuator;
88 use crate::attrs::{AttrKey, AttrType, AttrVal};
89 use crate::descriptor::owned::{
90 MutatorOperation, OrganizationCustomMetadata, OwnedMutatorDescriptor,
91 OwnedMutatorParamDescriptor,
92 };
93 use crate::descriptor::MutatorDescriptor;
94 use crate::mutator::CombinedMutator;
95 use async_trait::async_trait;
96 use std::collections::{BTreeMap, HashMap};
97 use uuid::Uuid;
98
99 pub struct OwnedValueMutator {
100 mutation_active: bool,
101 last_natural: i64,
102 inner: i64,
103 }
104 impl OwnedValueMutator {
105 pub fn new(initial: i64) -> Self {
106 OwnedValueMutator {
107 mutation_active: false,
108 last_natural: initial,
109 inner: initial,
110 }
111 }
112 pub fn current(&self) -> i64 {
113 self.inner
114 }
115 }
116
117 #[async_trait]
118 impl MutatorActuator for OwnedValueMutator {
119 async fn inject(
120 &mut self,
121 mutation_id: Uuid,
122 params: BTreeMap<AttrKey, AttrVal>,
123 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
124 let mutation_id_as_integer = i128::from_le_bytes(*mutation_id.as_bytes());
125 let v = if let Some((_k, v)) = params.into_iter().next() {
126 v
127 } else {
128 tracing::error!(
129 mutation.id = mutation_id_as_integer,
130 mutation.success = false,
131 name = "modality.mutation.injected",
132 "Expected exactly one one parameter",
133 );
134 return Ok(());
135 };
136 if let AttrVal::Integer(i) = v {
137 tracing::info!(
138 mutation.id = mutation_id_as_integer,
139 mutation.success = true,
140 name = "modality.mutation.injected"
141 );
142 if !self.mutation_active {
143 self.last_natural = self.inner;
144 }
145 self.inner = i;
146 self.mutation_active = true;
147 } else {
148 tracing::error!(
149 mutation.id = mutation_id_as_integer,
150 mutation.success = false,
151 name = "modality.mutation.injected",
152 "Expected an integer-type parameter",
153 );
154 panic!("Unexpected param of value {v:?}");
155 }
156 Ok(())
157 }
158
159 async fn reset(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
160 self.inner = self.last_natural;
161 Ok(())
162 }
163 }
164
165 #[tokio::test]
166 async fn description_and_actuation_in_one_spot() {
167 let ovm = OwnedValueMutator::new(5);
168
169 let mut combined_mutator = CombinedMutator::new(
170 ovm,
171 OwnedMutatorDescriptor {
172 name: Some("foo".into()),
173 description: None,
174 layer: None,
175 group: None,
176 operation: Some(MutatorOperation::SetToValue),
177 statefulness: None,
178 organization_custom_metadata: Some(
179 OrganizationCustomMetadata::new(
180 "some_jerks".to_owned(),
181 std::iter::once(("fleet".to_owned(), AttrVal::Integer(99))).collect(),
182 )
183 .unwrap(),
184 ),
185 params: vec![OwnedMutatorParamDescriptor::new(
186 AttrType::Integer,
187 MutatorOperation::SetToValue.name().to_owned(),
188 )
189 .unwrap()],
190 },
191 );
192 let all_attrs: HashMap<AttrKey, AttrVal> =
193 combined_mutator.get_description_attributes().collect();
194 assert!(!all_attrs.is_empty());
195 assert_eq!(
196 &AttrVal::String("foo".into()),
197 all_attrs.get(&AttrKey::from("mutator.name")).unwrap()
198 );
199 assert_eq!(
200 &AttrVal::String("set_to_value".into()),
201 all_attrs.get(&AttrKey::from("mutator.operation")).unwrap()
202 );
203 assert_eq!(
204 &AttrVal::Integer(99.into()),
205 all_attrs
206 .get(&AttrKey::from("mutator.some_jerks.fleet"))
207 .unwrap()
208 );
209
210 assert_eq!(
211 &AttrVal::String("Integer".into()),
212 all_attrs
213 .get(&AttrKey::from("mutator.params.set_to_value.value_type"))
214 .unwrap()
215 );
216
217 assert_eq!(5, combined_mutator.actuator_ref().current());
218 combined_mutator
219 .inject(
220 uuid::Uuid::nil(),
221 std::iter::once((
222 AttrKey::from(MutatorOperation::SetToValue.name()),
223 AttrVal::Integer(42),
224 ))
225 .collect(),
226 )
227 .await
228 .unwrap();
229 assert_eq!(42, combined_mutator.actuator_ref().current());
230 combined_mutator.reset().await.unwrap();
231 assert_eq!(5, combined_mutator.actuator_ref().current());
232 }
233}