modality_mutator_protocol/
lib.rs

1//! Standard-library-required traits and utilities for modality mutator implementation
2//! The core traits are `descriptor::InfallibleFlatMutatorDescriptor` and `actuator::MutatorActuator`,
3
4#![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}