use crate::{DeviceMessageType, NodeMessageType, TopicNamespace};
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq)]
pub enum TopicName {
NodeMessage {
namespace: TopicNamespace,
group_id: String,
node_message_type: NodeMessageType,
edge_node_id: String,
},
DeviceMessage {
namespace: TopicNamespace,
group_id: String,
device_message_type: DeviceMessageType,
edge_node_id: String,
device_id: String,
},
StateMessage {
scada_host_id: String,
},
}
impl TopicName {
pub const fn new_node_message(
namespace: TopicNamespace,
group_id: String,
node_message_type: NodeMessageType,
edge_node_id: String,
) -> Self {
TopicName::NodeMessage {
namespace,
group_id,
node_message_type,
edge_node_id,
}
}
pub const fn new_device_message(
namespace: TopicNamespace,
group_id: String,
device_message_type: DeviceMessageType,
edge_node_id: String,
device_id: String,
) -> Self {
TopicName::DeviceMessage {
namespace,
group_id,
device_message_type,
edge_node_id,
device_id,
}
}
pub const fn new_state_message(scada_host_id: String) -> Self {
TopicName::StateMessage { scada_host_id }
}
}
impl FromStr for TopicName {
type Err = Box<dyn Error + Sync + Send + 'static>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('/').collect();
if parts.len() == 2 {
let mut iter = parts.iter();
if Some(&"STATE") == iter.next() {
if let Some(scada_host_id) = iter.next() {
return Ok(TopicName::StateMessage {
scada_host_id: scada_host_id.to_string(),
});
}
}
} else if parts.len() == 4 {
let mut iter = parts.iter();
let namespace;
let group_id;
let node_message_type;
let edge_node_id;
if let Some(s) = iter.next() {
namespace = TopicNamespace::from_str(s)?;
if let Some(s) = iter.next() {
group_id = s.to_string();
if let Some(s) = iter.next() {
node_message_type = NodeMessageType::from_str(s)?;
if let Some(s) = iter.next() {
edge_node_id = s.to_string();
return Ok(TopicName::NodeMessage {
namespace,
group_id,
node_message_type,
edge_node_id,
});
}
}
}
}
} else if parts.len() == 5 {
let mut iter = parts.iter();
let namespace;
let group_id;
let device_message_type;
let edge_node_id;
if let Some(s) = iter.next() {
namespace = TopicNamespace::from_str(s)?;
if let Some(s) = iter.next() {
group_id = s.to_string();
if let Some(s) = iter.next() {
device_message_type = DeviceMessageType::from_str(s)?;
if let Some(s) = iter.next() {
edge_node_id = s.to_string();
if let Some(s) = iter.next() {
return Ok(TopicName::DeviceMessage {
namespace,
group_id,
device_message_type,
edge_node_id,
device_id: s.to_string(),
});
}
}
}
}
}
}
Err(Box::from(TopicNameParseError))
}
}
impl ToString for TopicName {
fn to_string(&self) -> String {
match self {
TopicName::NodeMessage {
namespace,
group_id,
node_message_type,
edge_node_id,
} => format!(
"{}/{}/{}/{}",
namespace.to_string(),
group_id,
node_message_type.to_string(),
edge_node_id
),
TopicName::DeviceMessage {
namespace,
group_id,
device_message_type,
edge_node_id,
device_id,
} => format!(
"{}/{}/{}/{}/{}",
namespace.to_string(),
group_id,
device_message_type.to_string(),
edge_node_id,
device_id
),
TopicName::StateMessage { scada_host_id } => format!("STATE/{}", scada_host_id),
}
}
}
#[derive(Debug, PartialEq)]
pub struct TopicNameParseError;
impl Display for TopicNameParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self, f)
}
}
impl Error for TopicNameParseError {}
#[cfg(test)]
mod tests {
use crate::{DeviceMessageType, NodeMessageType, TopicName, TopicNamespace};
use std::str::FromStr;
#[test]
fn parse_state() {
TopicName::from_str("STATE/scada_id").unwrap();
}
#[test]
fn test_errors() {
assert_eq!(
TopicName::from_str("").unwrap_err().to_string(),
"TopicNameParseError"
);
assert_eq!(
TopicName::from_str("STATE/to_many/slashes")
.unwrap_err()
.to_string(),
"TopicNameParseError"
);
assert_eq!(
TopicName::from_str("STATE/to_many/slashes/x/x/x")
.unwrap_err()
.to_string(),
"TopicNameParseError"
);
assert_eq!(
TopicName::from_str("wrong_namespace/to_many/slashes/x/x")
.unwrap_err()
.to_string(),
"Error parsing topic's namespace"
);
assert_eq!(
TopicName::from_str("wrong_namespace/to_many/slashes/x")
.unwrap_err()
.to_string(),
"Error parsing topic's namespace"
);
assert_eq!(
TopicName::from_str("wrong_namespace/to_many/slashes/x")
.unwrap_err()
.to_string(),
"Error parsing topic's namespace"
);
assert_eq!(
TopicName::from_str("spBv1.0/my_group/WRONG/nodeId")
.unwrap_err()
.to_string(),
"Unknown NodeMessageType"
);
assert_eq!(
TopicName::from_str("spBv1.0/my_group/WRONG/nodeId/deviceId")
.unwrap_err()
.to_string(),
"Unknown DeviceMessageType"
);
}
#[test]
fn test_parse_node_cmd() {
if let TopicName::NodeMessage {
namespace,
group_id,
node_message_type,
edge_node_id,
} = TopicName::from_str("spBv1.0/my_group/NBIRTH/nodeId").unwrap()
{
assert_eq!(namespace, TopicNamespace::SPBV1_0);
assert_eq!(group_id, "my_group");
assert_eq!(node_message_type, NodeMessageType::NBIRTH);
assert_eq!(edge_node_id, "nodeId");
} else {
panic!();
}
}
#[test]
fn test_parse_device_cmd() {
if let TopicName::DeviceMessage {
namespace,
group_id,
device_message_type,
edge_node_id,
device_id,
} = TopicName::from_str("spBv1.0/my_group/DBIRTH/nodeId/deviceId").unwrap()
{
assert_eq!(namespace, TopicNamespace::SPBV1_0);
assert_eq!(group_id, "my_group");
assert_eq!(device_message_type, DeviceMessageType::DBIRTH);
assert_eq!(edge_node_id, "nodeId");
assert_eq!(device_id, "deviceId");
} else {
panic!();
}
}
}