#![deny(warnings, clippy::all)]
pub mod params_attributes;
pub mod actuator;
pub mod attrs;
#[cfg(feature = "proptest_strategies")]
pub use modality_api::proptest_strategies;
#[cfg(feature = "cbor")]
pub use modality_api::protocol as cbor;
pub mod descriptor;
pub mod mutator {
use async_trait::async_trait;
use crate::actuator::MutatorActuator;
use crate::attrs::{AttrKey, AttrVal};
use crate::descriptor::MutatorDescriptor;
use std::collections::BTreeMap;
pub trait ActuatorDescriptor: MutatorActuator + MutatorDescriptor {}
pub struct CombinedMutator<A, D>
where
A: MutatorActuator,
D: MutatorDescriptor,
{
actuator: A,
descriptor: D,
}
impl<A: MutatorActuator, D: MutatorDescriptor> CombinedMutator<A, D> {
pub fn new(actuator: A, descriptor: D) -> Self {
CombinedMutator {
actuator,
descriptor,
}
}
pub fn actuator_ref(&self) -> &A {
&self.actuator
}
pub fn actuator_mut(&mut self) -> &mut A {
&mut self.actuator
}
pub fn descriptor_ref(&self) -> &D {
&self.descriptor
}
pub fn descriptor_mut(&mut self) -> &mut D {
&mut self.descriptor
}
}
#[async_trait]
impl<A: MutatorActuator + Send + Sync, D: MutatorDescriptor + Send + Sync> MutatorActuator
for CombinedMutator<A, D>
{
async fn inject(
&mut self,
mutation_id: uuid::Uuid,
params: BTreeMap<AttrKey, AttrVal>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.actuator.inject(mutation_id, params).await
}
async fn reset(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.actuator.reset().await
}
}
impl<A: MutatorActuator, D: MutatorDescriptor> MutatorDescriptor for CombinedMutator<A, D> {
fn get_description_attributes(&self) -> Box<dyn Iterator<Item = (AttrKey, AttrVal)> + '_> {
self.descriptor.get_description_attributes()
}
}
impl<A: MutatorActuator + Send + Sync, D: MutatorDescriptor + Send + Sync> ActuatorDescriptor
for CombinedMutator<A, D>
{
}
}
#[cfg(test)]
mod tests {
use crate::actuator::MutatorActuator;
use crate::attrs::{AttrKey, AttrType, AttrVal};
use crate::descriptor::owned::{
MutatorOperation, OrganizationCustomMetadata, OwnedMutatorDescriptor,
OwnedMutatorParamDescriptor,
};
use crate::descriptor::MutatorDescriptor;
use crate::mutator::CombinedMutator;
use async_trait::async_trait;
use std::collections::{BTreeMap, HashMap};
use uuid::Uuid;
pub struct OwnedValueMutator {
mutation_active: bool,
last_natural: i64,
inner: i64,
}
impl OwnedValueMutator {
pub fn new(initial: i64) -> Self {
OwnedValueMutator {
mutation_active: false,
last_natural: initial,
inner: initial,
}
}
pub fn current(&self) -> i64 {
self.inner
}
}
#[async_trait]
impl MutatorActuator for OwnedValueMutator {
async fn inject(
&mut self,
mutation_id: Uuid,
params: BTreeMap<AttrKey, AttrVal>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mutation_id_as_integer = i128::from_le_bytes(*mutation_id.as_bytes());
let v = if let Some((_k, v)) = params.into_iter().next() {
v
} else {
tracing::error!(
mutation.id = mutation_id_as_integer,
mutation.success = false,
name = "modality.mutation.injected",
"Expected exactly one one parameter",
);
return Ok(());
};
if let AttrVal::Integer(i) = v {
tracing::info!(
mutation.id = mutation_id_as_integer,
mutation.success = true,
name = "modality.mutation.injected"
);
if !self.mutation_active {
self.last_natural = self.inner;
}
self.inner = i;
self.mutation_active = true;
} else {
tracing::error!(
mutation.id = mutation_id_as_integer,
mutation.success = false,
name = "modality.mutation.injected",
"Expected an integer-type parameter",
);
panic!("Unexpected param of value {v:?}");
}
Ok(())
}
async fn reset(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.inner = self.last_natural;
Ok(())
}
}
#[tokio::test]
async fn description_and_actuation_in_one_spot() {
let ovm = OwnedValueMutator::new(5);
let mut combined_mutator = CombinedMutator::new(
ovm,
OwnedMutatorDescriptor {
name: Some("foo".into()),
description: None,
layer: None,
group: None,
operation: Some(MutatorOperation::SetToValue),
statefulness: None,
organization_custom_metadata: Some(
OrganizationCustomMetadata::new(
"some_jerks".to_owned(),
std::iter::once(("fleet".to_owned(), AttrVal::Integer(99))).collect(),
)
.unwrap(),
),
params: vec![OwnedMutatorParamDescriptor::new(
AttrType::Integer,
MutatorOperation::SetToValue.name().to_owned(),
)
.unwrap()],
},
);
let all_attrs: HashMap<AttrKey, AttrVal> =
combined_mutator.get_description_attributes().collect();
assert!(!all_attrs.is_empty());
assert_eq!(
&AttrVal::String("foo".into()),
all_attrs.get(&AttrKey::from("mutator.name")).unwrap()
);
assert_eq!(
&AttrVal::String("set_to_value".into()),
all_attrs.get(&AttrKey::from("mutator.operation")).unwrap()
);
assert_eq!(
&AttrVal::Integer(99.into()),
all_attrs
.get(&AttrKey::from("mutator.some_jerks.fleet"))
.unwrap()
);
assert_eq!(
&AttrVal::String("Integer".into()),
all_attrs
.get(&AttrKey::from("mutator.params.set_to_value.value_type"))
.unwrap()
);
assert_eq!(5, combined_mutator.actuator_ref().current());
combined_mutator
.inject(
uuid::Uuid::nil(),
std::iter::once((
AttrKey::from(MutatorOperation::SetToValue.name()),
AttrVal::Integer(42),
))
.collect(),
)
.await
.unwrap();
assert_eq!(42, combined_mutator.actuator_ref().current());
combined_mutator.reset().await.unwrap();
assert_eq!(5, combined_mutator.actuator_ref().current());
}
}