summaryrefslogtreecommitdiffstats
path: root/crates/core/tedge_mapper/src/operations.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/core/tedge_mapper/src/operations.rs')
-rw-r--r--crates/core/tedge_mapper/src/operations.rs272
1 files changed, 123 insertions, 149 deletions
diff --git a/crates/core/tedge_mapper/src/operations.rs b/crates/core/tedge_mapper/src/operations.rs
index b4dbe2a7..b3909aac 100644
--- a/crates/core/tedge_mapper/src/operations.rs
+++ b/crates/core/tedge_mapper/src/operations.rs
@@ -4,216 +4,183 @@ use std::{
path::{Path, PathBuf},
};
+use serde::Deserialize;
+
use crate::error::OperationsError;
/// Operations are derived by reading files subdirectories per cloud /etc/tedge/operations directory
/// Each operation is a file name in one of the subdirectories
/// The file name is the operation name
-type Cloud = String;
-type OperationName = String;
-type Operation = HashSet<OperationName>;
-type OperationsMap = HashMap<Cloud, Operation>;
+#[derive(Debug, Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub struct OnMessageExec {
+ command: Option<String>,
+ on_message: Option<String>,
+ topic: Option<String>,
+ user: Option<String>,
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub struct Operation {
+ #[serde(skip)]
+ name: String,
+ exec: Option<OnMessageExec>,
+}
+
+impl Operation {
+ pub fn exec(&self) -> Option<&OnMessageExec> {
+ self.exec.as_ref()
+ }
+
+ pub fn command(&self) -> Option<String> {
+ self.exec().and_then(|exec| exec.command.clone())
+ }
-#[derive(Debug, Clone, PartialEq)]
+ pub fn topic(&self) -> Option<String> {
+ self.exec().and_then(|exec| exec.topic.clone())
+ }
+}
+
+#[derive(Debug, Clone)]
pub struct Operations {
- cloud: PathBuf,
- operations: OperationsMap,
+ operations: Vec<Operation>,
+ operations_by_trigger: HashMap<String, usize>,
}
impl Operations {
- pub fn try_new(dir: impl AsRef<Path>) -> Result<Self, OperationsError> {
- let operations = get_operations(dir.as_ref())?;
+ pub fn new() -> Self {
+ Self {
+ operations: vec![],
+ operations_by_trigger: HashMap::new(),
+ }
+ }
- Ok(Self {
- cloud: dir.as_ref().to_path_buf(),
- operations,
- })
+ pub fn add(&mut self, operation: Operation) {
+ if let Some(detail) = operation.exec() {
+ if let Some(on_message) = &detail.on_message {
+ self.operations_by_trigger
+ .insert(on_message.clone(), self.operations.len());
+ }
+ }
+ self.operations.push(operation);
}
- pub fn get_operations_list(&self, cloud: &str) -> Vec<&str> {
+ pub fn try_new(dir: impl AsRef<Path>, cloud_name: &str) -> Result<Self, OperationsError> {
+ get_operations(dir.as_ref(), cloud_name)
+ }
+
+ pub fn get_operations_list(&self) -> Vec<String> {
self.operations
- .get(cloud)
- .map(|operations| operations.iter().map(|k| k.as_str()).collect())
- .unwrap_or_default()
+ .iter()
+ .map(|operation| operation.name.clone())
+ .collect::<Vec<String>>()
+ }
+
+ pub fn matching_smartrest_template(&self, operation_template: &str) -> Option<&Operation> {
+ self.operations_by_trigger
+ .get(operation_template)
+ .and_then(|index| self.operations.get(*index))
+ }
+
+ pub fn topics_for_operations(&self) -> HashSet<String> {
+ self.operations
+ .iter()
+ .filter_map(|operation| operation.topic())
+ .collect::<HashSet<String>>()
}
}
-fn get_clouds(dir: impl AsRef<Path>) -> Result<Vec<String>, OperationsError> {
- Ok(fs::read_dir(dir)?
+fn get_operations(dir: impl AsRef<Path>, cloud_name: &str) -> Result<Operations, OperationsError> {
+ let mut operations = Operations::new();
+
+ let path = dir.as_ref().join(&cloud_name);
+ let dir_entries = fs::read_dir(&path)?
.map(|entry| entry.map(|e| e.path()))
.collect::<Result<Vec<PathBuf>, _>>()?
.into_iter()
- .filter(|path| path.is_dir())
- .map(|path| {
- let filename = path.file_name();
- filename.unwrap().to_str().unwrap().to_string()
- })
- .collect())
-}
+ .filter(|path| path.is_file())
+ .collect::<Vec<PathBuf>>();
-fn get_operations(dir: impl AsRef<Path>) -> Result<OperationsMap, OperationsError> {
- let mut operations = OperationsMap::new();
- for cloud in get_clouds(&dir)? {
- let path = dir.as_ref().join(cloud.as_str());
- let operations_map = fs::read_dir(&path)?
- .map(|entry| entry.map(|e| e.path()))
- .collect::<Result<Vec<PathBuf>, _>>()?
- .into_iter()
- .filter(|path| path.is_file())
- .map(|path| {
- let filename = path.file_name();
- filename.unwrap().to_str().unwrap().to_string()
- })
- .collect();
- operations.insert(cloud, operations_map);
+ for path in dir_entries {
+ let mut details = match fs::read(&path) {
+ Ok(bytes) => toml::from_slice::<Operation>(bytes.as_slice())
+ .map_err(|e| OperationsError::TomlError(path.to_path_buf(), e))?,
+
+ Err(err) => return Err(OperationsError::FromIo(err)),
+ };
+
+ details.name = path
+ .file_name()
+ .and_then(|filename| filename.to_str())
+ .ok_or_else(|| OperationsError::InvalidOperationName(path.to_owned()))?
+ .to_owned();
+
+ operations.add(details);
}
Ok(operations)
}
#[cfg(test)]
mod tests {
+ use std::io::Write;
+
use super::*;
use test_case::test_case;
- #[test_case(0, false)]
- #[test_case(0, true)]
- #[test_case(2, false)]
- #[test_case(2, true)]
- fn get_clouds_tests(clouds_count: usize, files: bool) {
- let operations = TestOperations::builder().with_clouds(clouds_count);
-
- if files {
- operations.with_random_file_in_clouds_directory();
- }
-
- let operations = operations.build();
-
- let clouds = get_clouds(operations.temp_dir()).unwrap();
-
- assert_eq!(clouds.len(), clouds_count);
- }
-
- #[test_case(0, 0)]
- #[test_case(1, 1)]
- #[test_case(1, 5)]
- #[test_case(2, 5)]
- fn get_operations_all(clouds_count: usize, ops_count: usize) {
- let test_operations = TestOperations::builder()
- .with_clouds(clouds_count)
- .with_operations(ops_count)
- .build();
-
- let operations = get_operations(test_operations.temp_dir()).unwrap();
-
- assert_eq!(operations.len(), clouds_count);
- assert_eq!(
- operations.values().map(|ops| ops.len()).sum::<usize>(),
- ops_count * clouds_count
- );
- }
-
// Structs for state change with the builder pattern
- // Structs for Clouds
- struct Clouds(Vec<PathBuf>);
- struct NoClouds;
-
// Structs for Operations
struct Ops(Vec<PathBuf>);
struct NoOps;
- struct TestOperationsBuilder<C, O> {
+ struct TestOperationsBuilder<O> {
temp_dir: tempfile::TempDir,
- clouds: C,
operations: O,
}
- impl TestOperationsBuilder<NoClouds, NoOps> {
+ impl TestOperationsBuilder<NoOps> {
fn new() -> Self {
Self {
temp_dir: tempfile::tempdir().unwrap(),
- clouds: NoClouds,
operations: NoOps,
}
}
}
- impl<O> TestOperationsBuilder<NoClouds, O> {
- fn with_clouds(self, clouds_count: usize) -> TestOperationsBuilder<Clouds, O> {
- let Self {
- temp_dir,
- operations,
- ..
- } = self;
-
- let mut clouds = Vec::new();
- for i in 0..clouds_count {
- let cloud = temp_dir.as_ref().join(format!("cloud{}", i));
- fs::create_dir(&cloud).unwrap();
- clouds.push(cloud);
- }
-
- TestOperationsBuilder {
- temp_dir,
- clouds: Clouds(clouds),
- operations,
- }
- }
- }
-
- impl TestOperationsBuilder<Clouds, NoOps> {
- fn with_operations(self, operations_count: usize) -> TestOperationsBuilder<Clouds, Ops> {
- let Self {
- temp_dir, clouds, ..
- } = self;
+ impl TestOperationsBuilder<NoOps> {
+ fn with_operations(self, operations_count: usize) -> TestOperationsBuilder<Ops> {
+ let Self { temp_dir, .. } = self;
let mut operations = Vec::new();
- clouds.0.iter().for_each(|path| {
- for i in 0..operations_count {
- let file_path = path.join(format!("operation{}", i));
- fs::File::create(&file_path).unwrap();
- operations.push(file_path);
- }
- });
+ for i in 0..operations_count {
+ let file_path = temp_dir.path().join(format!("operation{}", i));
+ let mut file = fs::File::create(&file_path).unwrap();
+ file.write_all(
+ br#"[exec]
+ command = "echo"
+ on_message = "511""#,
+ )
+ .unwrap();
+ operations.push(file_path);
+ }
TestOperationsBuilder {
operations: Ops(operations),
temp_dir,
- clouds,
- }
- }
-
- fn build(self) -> TestOperations {
- let Self {
- temp_dir, clouds, ..
- } = self;
-
- TestOperations {
- temp_dir,
- clouds: clouds.0,
- operations: Vec::new(),
}
}
}
- impl<C, O> TestOperationsBuilder<C, O> {
- fn with_random_file_in_clouds_directory(&self) {
- let path = self.temp_dir.as_ref().join("cloudfile");
- fs::File::create(path).unwrap();
- }
- }
-
- impl TestOperationsBuilder<Clouds, Ops> {
+ impl TestOperationsBuilder<Ops> {
fn build(self) -> TestOperations {
let Self {
temp_dir,
- clouds,
operations,
} = self;
TestOperations {
temp_dir,
- clouds: clouds.0,
operations: operations.0,
}
}
@@ -221,21 +188,28 @@ mod tests {
struct TestOperations {
temp_dir: tempfile::TempDir,
- clouds: Vec<PathBuf>,
operations: Vec<PathBuf>,
}
impl TestOperations {
- fn builder() -> TestOperationsBuilder<NoClouds, NoOps> {
+ fn builder() -> TestOperationsBuilder<NoOps> {
TestOperationsBuilder::new()
}
fn temp_dir(&self) -> &tempfile::TempDir {
&self.temp_dir
}
+ }
- fn operations(&self) -> &Vec<PathBuf> {
- &self.operations
- }
+ #[test_case(0)]
+ #[test_case(1)]
+ #[test_case(5)]
+ fn get_operations_all(ops_count: usize) {
+ let test_operations = TestOperations::builder().with_operations(ops_count).build();
+
+ let operations = get_operations(test_operations.temp_dir(), "").unwrap();
+ dbg!(&operations);
+
+ assert_eq!(operations.operations.len(), ops_count);
}
}