summaryrefslogtreecommitdiffstats
path: root/crates/core/json_sm/src/messages.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/core/json_sm/src/messages.rs')
-rw-r--r--crates/core/json_sm/src/messages.rs627
1 files changed, 627 insertions, 0 deletions
diff --git a/crates/core/json_sm/src/messages.rs b/crates/core/json_sm/src/messages.rs
new file mode 100644
index 00000000..8e09bd71
--- /dev/null
+++ b/crates/core/json_sm/src/messages.rs
@@ -0,0 +1,627 @@
+use crate::{error::SoftwareError, software::*};
+use nanoid::nanoid;
+use serde::{Deserialize, Serialize};
+
+/// All the messages are serialized using json.
+pub trait Jsonify<'a>
+where
+ Self: Deserialize<'a> + Serialize + Sized,
+{
+ fn from_json(json_str: &'a str) -> Result<Self, SoftwareError> {
+ Ok(serde_json::from_str(json_str)?)
+ }
+
+ fn from_slice(bytes: &'a [u8]) -> Result<Self, SoftwareError> {
+ Ok(serde_json::from_slice(bytes)?)
+ }
+
+ fn to_json(&self) -> Result<String, SoftwareError> {
+ Ok(serde_json::to_string(self)?)
+ }
+
+ fn to_bytes(&self) -> Result<Vec<u8>, SoftwareError> {
+ Ok(serde_json::to_vec(self)?)
+ }
+}
+
+pub const fn software_filter_topic() -> &'static str {
+ "tedge/commands/req/software/#"
+}
+
+/// Message payload definition for SoftwareList request.
+#[derive(Debug, Deserialize, Serialize, PartialEq)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "camelCase")]
+pub struct SoftwareListRequest {
+ pub id: String,
+}
+
+impl<'a> Jsonify<'a> for SoftwareListRequest {}
+
+impl SoftwareListRequest {
+ pub fn new() -> SoftwareListRequest {
+ let id = nanoid!();
+ SoftwareListRequest { id }
+ }
+
+ pub fn new_with_id(id: &str) -> SoftwareListRequest {
+ SoftwareListRequest { id: id.to_string() }
+ }
+
+ pub fn topic_name() -> &'static str {
+ "tedge/commands/req/software/list"
+ }
+}
+
+/// Message payload definition for SoftwareUpdate request.
+#[derive(Debug, Deserialize, Serialize, PartialEq)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "camelCase")]
+pub struct SoftwareUpdateRequest {
+ pub id: String,
+ pub update_list: Vec<SoftwareRequestResponseSoftwareList>,
+}
+
+impl<'a> Jsonify<'a> for SoftwareUpdateRequest {}
+
+impl SoftwareUpdateRequest {
+ pub fn new() -> SoftwareUpdateRequest {
+ let id = nanoid!();
+ SoftwareUpdateRequest {
+ id,
+ update_list: vec![],
+ }
+ }
+
+ pub fn new_with_id(id: &str) -> SoftwareUpdateRequest {
+ SoftwareUpdateRequest {
+ id: id.to_string(),
+ update_list: vec![],
+ }
+ }
+
+ pub fn topic_name() -> &'static str {
+ "tedge/commands/req/software/update"
+ }
+
+ pub fn add_update(&mut self, mut update: SoftwareModuleUpdate) {
+ update.normalize();
+ let plugin_type = update
+ .module()
+ .module_type
+ .clone()
+ .unwrap_or_else(SoftwareModule::default_type);
+
+ if let Some(list) = self
+ .update_list
+ .iter_mut()
+ .find(|list| list.plugin_type == plugin_type)
+ {
+ list.modules.push(update.into());
+ } else {
+ self.update_list.push(SoftwareRequestResponseSoftwareList {
+ plugin_type,
+ modules: vec![update.into()],
+ });
+ }
+ }
+
+ pub fn add_updates(&mut self, plugin_type: &str, updates: Vec<SoftwareModuleUpdate>) {
+ self.update_list.push(SoftwareRequestResponseSoftwareList {
+ plugin_type: plugin_type.to_string(),
+ modules: updates
+ .into_iter()
+ .map(|update| update.into())
+ .collect::<Vec<SoftwareModuleItem>>(),
+ })
+ }
+
+ pub fn modules_types(&self) -> Vec<SoftwareType> {
+ let mut modules_types = vec![];
+
+ for updates_per_type in self.update_list.iter() {
+ modules_types.push(updates_per_type.plugin_type.clone())
+ }
+
+ modules_types
+ }
+
+ pub fn updates_for(&self, module_type: &str) -> Vec<SoftwareModuleUpdate> {
+ let mut updates = vec![];
+
+ if let Some(items) = self
+ .update_list
+ .iter()
+ .find(|&items| items.plugin_type == module_type)
+ {
+ for item in items.modules.iter() {
+ let module = SoftwareModule {
+ module_type: Some(module_type.to_string()),
+ name: item.name.clone(),
+ version: item.version.clone(),
+ url: item.url.clone(),
+ file_path: None,
+ };
+ match item.action {
+ None => {}
+ Some(SoftwareModuleAction::Install) => {
+ updates.push(SoftwareModuleUpdate::install(module));
+ }
+ Some(SoftwareModuleAction::Remove) => {
+ updates.push(SoftwareModuleUpdate::remove(module));
+ }
+ }
+ }
+ }
+
+ updates
+ }
+}
+
+/// Sub list of modules grouped by plugin type.
+#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub struct SoftwareRequestResponseSoftwareList {
+ #[serde(rename = "type")]
+ pub plugin_type: SoftwareType,
+ pub modules: Vec<SoftwareModuleItem>,
+}
+
+/// Possible statuses for result of Software operation.
+#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
+#[serde(rename_all = "camelCase")]
+pub enum SoftwareOperationStatus {
+ Successful,
+ Failed,
+ Executing,
+}
+
+/// Message payload definition for SoftwareList response.
+#[derive(Debug, Deserialize, Serialize, PartialEq)]
+pub struct SoftwareListResponse {
+ #[serde(flatten)]
+ response: SoftwareRequestResponse,
+}
+
+impl<'a> Jsonify<'a> for SoftwareListResponse {}
+
+impl SoftwareListResponse {
+ pub fn new(req: &SoftwareListRequest) -> SoftwareListResponse {
+ SoftwareListResponse {
+ response: SoftwareRequestResponse::new(&req.id, SoftwareOperationStatus::Executing),
+ }
+ }
+
+ pub fn topic_name() -> &'static str {
+ "tedge/commands/res/software/list"
+ }
+
+ pub fn add_modules(&mut self, plugin_type: &str, modules: Vec<SoftwareModule>) {
+ self.response.add_modules(
+ plugin_type.to_string(),
+ modules
+ .into_iter()
+ .map(|module| module.into())
+ .collect::<Vec<SoftwareModuleItem>>(),
+ );
+ }
+
+ pub fn set_error(&mut self, reason: &str) {
+ self.response.status = SoftwareOperationStatus::Failed;
+ self.response.reason = Some(reason.into());
+ }
+
+ pub fn id(&self) -> &str {
+ &self.response.id
+ }
+
+ pub fn status(&self) -> SoftwareOperationStatus {
+ self.response.status
+ }
+
+ pub fn error(&self) -> Option<String> {
+ self.response.reason.clone()
+ }
+
+ pub fn modules(&self) -> Vec<SoftwareModule> {
+ self.response.modules()
+ }
+}
+
+/// Message payload definition for SoftwareUpdate response.
+#[derive(Debug, Deserialize, Serialize, PartialEq)]
+pub struct SoftwareUpdateResponse {
+ #[serde(flatten)]
+ response: SoftwareRequestResponse,
+}
+
+impl<'a> Jsonify<'a> for SoftwareUpdateResponse {}
+
+impl SoftwareUpdateResponse {
+ pub fn new(req: &SoftwareUpdateRequest) -> SoftwareUpdateResponse {
+ SoftwareUpdateResponse {
+ response: SoftwareRequestResponse::new(&req.id, SoftwareOperationStatus::Executing),
+ }
+ }
+
+ pub fn topic_name() -> &'static str {
+ "tedge/commands/res/software/update"
+ }
+
+ pub fn add_modules(&mut self, plugin_type: &str, modules: Vec<SoftwareModule>) {
+ self.response.add_modules(
+ plugin_type.to_string(),
+ modules
+ .into_iter()
+ .map(|module| module.into())
+ .collect::<Vec<SoftwareModuleItem>>(),
+ );
+ }
+
+ pub fn add_errors(&mut self, plugin_type: &str, errors: Vec<SoftwareError>) {
+ self.response.add_errors(
+ plugin_type.to_string(),
+ errors
+ .into_iter()
+ .filter_map(|module| module.into())
+ .collect::<Vec<SoftwareModuleItem>>(),
+ );
+ }
+
+ pub fn set_error(&mut self, reason: &str) {
+ self.response.status = SoftwareOperationStatus::Failed;
+ self.response.reason = Some(reason.into());
+ }
+
+ pub fn id(&self) -> &str {
+ &self.response.id
+ }
+
+ pub fn status(&self) -> SoftwareOperationStatus {
+ self.response.status
+ }
+
+ pub fn error(&self) -> Option<String> {
+ self.response.reason.clone()
+ }
+
+ pub fn modules(&self) -> Vec<SoftwareModule> {
+ self.response.modules()
+ }
+}
+
+/// Variants represent Software Operations Supported actions.
+#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub enum SoftwareModuleAction {
+ Install,
+ Remove,
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub struct DownloadInfo {
+ pub url: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub auth: Option<Auth>,
+}
+
+impl From<&str> for DownloadInfo {
+ fn from(url: &str) -> Self {
+ Self::new(url)
+ }
+}
+
+impl DownloadInfo {
+ pub fn new(url: &str) -> Self {
+ Self {
+ url: url.into(),
+ auth: None,
+ }
+ }
+
+ pub fn with_auth(self, auth: Auth) -> Self {
+ Self {
+ auth: Some(auth),
+ ..self
+ }
+ }
+
+ pub fn url(&self) -> &str {
+ self.url.as_str()
+ }
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub enum Auth {
+ Bearer(String),
+}
+
+impl Auth {
+ pub fn new_bearer(token: &str) -> Self {
+ Self::Bearer(token.into())
+ }
+}
+
+/// Software module payload definition.
+#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub struct SoftwareModuleItem {
+ pub name: SoftwareName,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub version: Option<SoftwareVersion>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(flatten)]
+ pub url: Option<DownloadInfo>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub action: Option<SoftwareModuleAction>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub reason: Option<String>,
+}
+
+/// Software Operation Response payload format.
+#[derive(Debug, Deserialize, Serialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct SoftwareRequestResponse {
+ pub id: String,
+ pub status: SoftwareOperationStatus,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub reason: Option<String>,
+
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub current_software_list: Option<Vec<SoftwareRequestResponseSoftwareList>>,
+
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub failures: Vec<SoftwareRequestResponseSoftwareList>,
+}
+
+impl<'a> Jsonify<'a> for SoftwareRequestResponse {}
+
+impl SoftwareRequestResponse {
+ pub fn new(id: &str, status: SoftwareOperationStatus) -> Self {
+ SoftwareRequestResponse {
+ id: id.to_string(),
+ status,
+ current_software_list: None,
+ reason: None,
+ failures: vec![],
+ }
+ }
+
+ pub fn add_modules(&mut self, plugin_type: SoftwareType, modules: Vec<SoftwareModuleItem>) {
+ if self.failures.is_empty() {
+ self.status = SoftwareOperationStatus::Successful;
+ }
+
+ if self.current_software_list.is_none() {
+ self.current_software_list = Some(vec![]);
+ }
+
+ if let Some(list) = self.current_software_list.as_mut() {
+ list.push(SoftwareRequestResponseSoftwareList {
+ plugin_type,
+ modules,
+ })
+ }
+ }
+
+ pub fn add_errors(&mut self, plugin_type: SoftwareType, modules: Vec<SoftwareModuleItem>) {
+ self.status = SoftwareOperationStatus::Failed;
+
+ self.failures.push(SoftwareRequestResponseSoftwareList {
+ plugin_type,
+ modules,
+ })
+ }
+
+ pub fn modules(&self) -> Vec<SoftwareModule> {
+ let mut modules = vec![];
+
+ if let Some(list) = &self.current_software_list {
+ for module_per_plugin in list.iter() {
+ let module_type = &module_per_plugin.plugin_type;
+ for module in module_per_plugin.modules.iter() {
+ modules.push(SoftwareModule {
+ module_type: Some(module_type.clone()),
+ name: module.name.clone(),
+ version: module.version.clone(),
+ url: module.url.clone(),
+ file_path: None,
+ });
+ }
+ }
+ }
+
+ modules
+ }
+}
+
+impl From<SoftwareModule> for SoftwareModuleItem {
+ fn from(module: SoftwareModule) -> Self {
+ SoftwareModuleItem {
+ name: module.name,
+ version: module.version,
+ url: module.url,
+ action: None,
+ reason: None,
+ }
+ }
+}
+
+impl From<SoftwareModuleUpdate> for SoftwareModuleItem {
+ fn from(update: SoftwareModuleUpdate) -> Self {
+ match update {
+ SoftwareModuleUpdate::Install { module } => SoftwareModuleItem {
+ name: module.name,
+ version: module.version,
+ url: module.url,
+ action: Some(SoftwareModuleAction::Install),
+ reason: None,
+ },
+ SoftwareModuleUpdate::Remove { module } => SoftwareModuleItem {
+ name: module.name,
+ version: module.version,
+ url: module.url,
+ action: Some(SoftwareModuleAction::Remove),
+ reason: None,
+ },
+ }
+ }
+}
+
+impl From<SoftwareError> for Option<SoftwareModuleItem> {
+ fn from(error: SoftwareError) -> Self {
+ match error {
+ SoftwareError::Install { module, reason } => Some(SoftwareModuleItem {
+ name: module.name,
+ version: module.version,
+ url: module.url,
+ action: Some(SoftwareModuleAction::Install),
+ reason: Some(reason),
+ }),
+ SoftwareError::Remove { module, reason } => Some(SoftwareModuleItem {
+ name: module.name,
+ version: module.version,
+ url: module.url,
+ action: Some(SoftwareModuleAction::Remove),
+ reason: Some(reason),
+ }),
+ _ => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn serde_software_request_list() {
+ let request = SoftwareListRequest {
+ id: "1234".to_string(),
+ };
+ let expected_json = r#"{"id":"1234"}"#;
+
+ let actual_json = request.to_json().expect("Failed to serialize");
+
+ assert_eq!(actual_json, expected_json);
+
+ let de_request =
+ SoftwareListRequest::from_json(actual_json.as_str()).expect("failed to deserialize");
+ assert_eq!(request, de_request);
+ }
+
+ #[test]
+ fn serde_software_request_update() {
+ let debian_module1 = SoftwareModuleItem {
+ name: "debian1".into(),
+ version: Some("0.0.1".into()),
+ action: Some(SoftwareModuleAction::Install),
+ url: None,
+ reason: None,
+ };
+
+ let debian_module2 = SoftwareModuleItem {
+ name: "debian2".into(),
+ version: Some("0.0.2".into()),
+ action: Some(SoftwareModuleAction::Install),
+ url: None,
+ reason: None,
+ };
+
+ let debian_list = SoftwareRequestResponseSoftwareList {
+ plugin_type: "debian".into(),
+ modules: vec![debian_module1, debian_module2],
+ };
+
+ let docker_module1 = SoftwareModuleItem {
+ name: "docker1".into(),
+ version: Some("0.0.1".into()),
+ action: Some(SoftwareModuleAction::Remove),
+ url: Some("test.com".into()),
+ reason: None,
+ };
+
+ let docker_list = SoftwareRequestResponseSoftwareList {
+ plugin_type: "docker".into(),
+ modules: vec![docker_module1],
+ };
+
+ let request = SoftwareUpdateRequest {
+ id: "1234".to_string(),
+ update_list: vec![debian_list, docker_list],
+ };
+
+ let expected_json = r#"{"id":"1234","updateList":[{"type":"debian","modules":[{"name":"debian1","version":"0.0.1","action":"install"},{"name":"debian2","version":"0.0.2","action":"install"}]},{"type":"docker","modules":[{"name":"docker1","version":"0.0.1","url":"test.com","action":"remove"}]}]}"#;
+
+ let actual_json = request.to_json().expect("Fail to serialize the request");
+ assert_eq!(actual_json, expected_json);
+
+ let parsed_request =
+ SoftwareUpdateRequest::from_json(&actual_json).expect("Fail to parse the json request");
+ assert_eq!(parsed_request, request);
+ }
+
+ #[test]
+ fn serde_software_list_empty_successful() {
+ let request = SoftwareRequestResponse {
+ id: "1234".to_string(),
+ status: SoftwareOperationStatus::Successful,
+ reason: None,
+ current_software_list: Some(vec![]),
+ failures: vec![],
+ };
+
+ let expected_json = r#"{"id":"1234","status":"successful","currentSoftwareList":[]}"#;
+
+ let actual_json = request.to_json().expect("Fail to serialize the request");
+ assert_eq!(actual_json, expected_json);
+
+ let parsed_request = SoftwareRequestResponse::from_json(&actual_json)
+ .expect("Fail to parse the json request");
+ assert_eq!(parsed_request, request);
+ }
+
+ #[test]
+ fn serde_software_list_some_modules_successful() {
+ let module1 = SoftwareModuleItem {
+ name: "debian1".into(),
+ version: Some("0.0.1".into()),
+ action: None,
+ url: None,
+ reason: None,
+ };
+
+ let docker_module1 = SoftwareRequestResponseSoftwareList {
+ plugin_type: "debian".into(),
+ modules: vec![module1],
+ };
+
+ let request = SoftwareRequestResponse {
+ id: "1234".to_string(),
+ status: SoftwareOperationStatus::Successful,
+ reason: None,
+ current_software_list: Some(vec![docker_module1]),
+ failures: vec![],
+ };
+
+ let expected_json = r#"{"id":"1234","status":"successful","currentSoftwareList":[{"type":"debian","modules":[{"name":"debian1","version":"0.0.1"}]}]}"#;
+
+ let actual_json = request.to_json().expect("Fail to serialize the request");
+ assert_eq!(actual_json, expected_json);
+
+ let parsed_request = SoftwareRequestResponse::from_json(&actual_json)
+ .expect("Fail to parse the json request");
+ assert_eq!(parsed_request, request);
+ }
+}