Skip to main content

over_there/core/msg/
mod.rs

1pub mod content;
2
3use chrono::prelude::{DateTime, Utc};
4use content::{Content, Reply, Request};
5use derive_more::{Display, Error};
6use rand::random;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Display, Error)]
10pub enum MsgError {
11    AssembleMsg(serde_cbor::Error),
12    DisassembleMsg(serde_cbor::Error),
13}
14
15#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
16pub struct Header {
17    /// ID associated with a request or reply
18    pub id: u32,
19
20    /// The time at which the message was created
21    pub creation_date: DateTime<Utc>,
22}
23
24impl Default for Header {
25    fn default() -> Self {
26        Self {
27            id: random(),
28            creation_date: Utc::now(),
29        }
30    }
31}
32
33#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
34pub struct Msg {
35    /// Information associated with this message
36    pub header: Header,
37
38    /// Information associated with the parent of this message
39    /// to provide origin context
40    pub parent_header: Option<Header>,
41
42    /// Content within the message
43    pub content: Content,
44}
45
46impl Msg {
47    pub fn new(content: Content, parent_header: Option<Header>) -> Self {
48        Self {
49            header: Header::default(),
50            parent_header,
51            content,
52        }
53    }
54
55    pub fn to_vec(&self) -> Result<Vec<u8>, MsgError> {
56        // NOTE: Cannot use to_vec_packed here as it fails to deserialize
57        serde_cbor::ser::to_vec(&self).map_err(MsgError::AssembleMsg)
58    }
59
60    pub fn from_slice(slice: &[u8]) -> Result<Self, MsgError> {
61        serde_cbor::from_slice(slice).map_err(MsgError::DisassembleMsg)
62    }
63
64    /// Sets the parent header of this msg with that of the provided header
65    pub fn with_parent_header(&mut self, header: Header) -> &mut Self {
66        self.parent_header = Some(header);
67        self
68    }
69
70    /// Sets the parent header of this msg with that of the provided parent
71    pub fn with_parent(&mut self, parent: &Self) -> &mut Self {
72        self.with_parent_header(parent.header.clone())
73    }
74}
75
76/// Produce a new message from the content with no parent
77impl From<Content> for Msg {
78    fn from(content: Content) -> Self {
79        Self {
80            header: Header::default(),
81            parent_header: None,
82            content,
83        }
84    }
85}
86
87impl From<Request> for Msg {
88    fn from(request: Request) -> Self {
89        Self::from(Content::from(request))
90    }
91}
92
93impl From<Reply> for Msg {
94    fn from(reply: Reply) -> Self {
95        Self::from(Content::from(reply))
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn default_header_should_have_creation_date_set_to_now() {
105        let header = Header::default();
106
107        // Verify creation date was set to around now
108        assert!(
109            Utc::now()
110                .signed_duration_since(header.creation_date)
111                .num_milliseconds()
112                >= 0,
113            "Unexpected creation date: {:?}",
114            header.creation_date
115        );
116    }
117
118    #[test]
119    fn from_content_should_create_msg_with_content() {
120        let msg = Msg::from(Request::Heartbeat);
121
122        assert_eq!(msg.parent_header, None);
123
124        match msg.content {
125            Content::Request(Request::Heartbeat) => (),
126            x => panic!("Unexpected content: {:?}", x),
127        }
128    }
129
130    #[test]
131    fn with_parent_header_should_set_header() {
132        let mut msg = Msg::from(Reply::Heartbeat);
133        let header = Header::default();
134
135        msg.with_parent_header(header.clone());
136
137        assert_eq!(msg.parent_header, Some(header));
138    }
139
140    #[test]
141    fn with_parent_should_set_header_to_that_of_parent() {
142        let mut msg = Msg::from(Reply::Heartbeat);
143        let parent = Msg::from(Request::Heartbeat);
144
145        msg.with_parent(&parent);
146
147        assert_eq!(msg.parent_header, Some(parent.header));
148    }
149}