srad_client/
types.rs

1use std::string::FromUtf8Error;
2
3use prost::DecodeError;
4use srad_types::{
5    payload::Payload,
6    topic::{state_host_topic, NodeMessage as NodeMessageType, NodeTopic, QoS},
7};
8use thiserror::Error;
9
10/// Error types for message processing operations.
11///
12/// This enum represents the various error conditions that can occur
13/// when decoding sparkplug protobuf payloads, validating topics, or handling payloads.
14#[derive(Error, Debug, PartialEq)]
15pub enum MessageError {
16    #[error("There was an error decoding the payload: {0}")]
17    DecodePayloadError(DecodeError),
18    #[error("The topic was invalid")]
19    InvalidSparkplugTopic,
20    #[error("Topic parts utf8 decode error: {0}")]
21    TopicUtf8Error(FromUtf8Error),
22    #[error("Unable to decode state message as json: {0}")]
23    StatePayloadJsonDecodeError(String),
24}
25
26impl From<FromUtf8Error> for MessageError {
27    fn from(e: FromUtf8Error) -> Self {
28        MessageError::TopicUtf8Error(e)
29    }
30}
31
32/// An enum representing the different type of message.
33#[derive(Debug, PartialEq)]
34pub enum MessageKind {
35    Birth,
36    Death,
37    Cmd,
38    Data,
39    Other(String),
40}
41
42/// A Message struct containing payload and the type of topic it was received on
43#[derive(Debug, PartialEq)]
44pub struct Message {
45    pub payload: Payload,
46    pub kind: MessageKind,
47}
48
49/// An enum representing the different type message published on a STATE topic.
50#[derive(Debug, Clone, PartialEq)]
51pub enum StatePayload {
52    Online { timestamp: u64 },
53    Offline { timestamp: u64 },
54}
55
56impl StatePayload {
57    /// Get the [QoS] and retain settings that the State message should be published with
58    pub fn get_publish_quality_retain(&self) -> (QoS, bool) {
59        match self {
60            StatePayload::Online { timestamp: _ } => (QoS::AtLeastOnce, true),
61            StatePayload::Offline { timestamp: _ } => (QoS::AtLeastOnce, true),
62        }
63    }
64}
65
66impl From<StatePayload> for Vec<u8> {
67    fn from(value: StatePayload) -> Self {
68        match value {
69            StatePayload::Online { timestamp } => {
70                format!("{{\"online\" : true, \"timestamp\" : {timestamp}}}").into()
71            }
72            StatePayload::Offline { timestamp } => {
73                format!("{{\"online\" : false, \"timestamp\" : {timestamp}}}").into()
74            }
75        }
76    }
77}
78
79/// Represents a message from a Node.
80#[derive(Debug, PartialEq)]
81pub struct NodeMessage {
82    /// The group the node belongs to.
83    pub group_id: String,
84    /// The nodes unique identifier.
85    pub node_id: String,
86    /// The message.
87    pub message: Message,
88}
89
90/// Represents a message from a Device.
91#[derive(Debug, PartialEq)]
92pub struct DeviceMessage {
93    /// The group the node belongs to.
94    pub group_id: String,
95    /// The nodes unique identifier.
96    pub node_id: String,
97    /// The devices unique identifier.
98    pub device_id: String,
99    /// The message.
100    pub message: Message,
101}
102
103/// An enum that represents the different types of events an [EventLoop](crate::EventLoop) implementation can produce.
104#[derive(Debug, PartialEq)]
105pub enum Event {
106    Offline,
107    Online,
108    Node(NodeMessage),
109    Device(DeviceMessage),
110    State {
111        host_id: String,
112        payload: StatePayload,
113    },
114    InvalidPublish {
115        reason: MessageError,
116        topic: Vec<u8>,
117        payload: Vec<u8>,
118    },
119}
120
121/// struct representing the last will of a Node or Application
122#[derive(Debug, Clone, PartialEq)]
123pub struct LastWill {
124    pub topic: String,
125    pub retain: bool,
126    pub qos: QoS,
127    pub payload: Vec<u8>,
128}
129
130impl LastWill {
131    pub fn new_node(group: &str, node_id: &str, payload: Payload) -> Self {
132        let topic = NodeTopic::new(group, NodeMessageType::NDeath, node_id);
133        let (qos, retain) = topic.get_publish_quality_retain();
134        Self {
135            retain,
136            qos,
137            payload: payload.into(),
138            topic: topic.topic,
139        }
140    }
141
142    pub fn new_app(host_id: &str, timestamp: u64) -> Self {
143        Self {
144            topic: state_host_topic(host_id),
145            retain: true,
146            qos: QoS::AtLeastOnce,
147            payload: StatePayload::Offline { timestamp }.into(),
148        }
149    }
150}