rnotifylib/message/
component.rs

1use std::fmt::{Display, Formatter};
2use serde::{Serialize, Deserialize, Deserializer, Serializer};
3
4/// Indicates what (program/functionality) a [`Message`] is referring to.
5/// This helps route messages to the relevant location, as well
6/// as control their severity.
7///
8/// # Examples #
9/// ```rust
10/// use rnotifylib::message::component::Component;
11///
12/// let db_backup_component = Component::from("database/backup");
13/// let db_uptime_component = Component::from("database/uptime");
14///
15/// let db_component = Component::from("database");
16///
17/// // Both of these are children of the db component - Hence destinations that subscribe
18/// // to the database component will receive both backup and uptime messages.
19/// assert!(db_backup_component.is_child_of(&db_component), "backup component should be child of db component");
20/// assert!(db_uptime_component.is_child_of(&db_component), "uptime component should be child of db component");
21///
22/// // Additionally, the database component is a "child" of itself,
23/// // Therefore messages with the "database" component will be sent to places that listen for the database component
24/// assert!(db_component.is_child_of(&db_component));
25/// ```
26///
27/// [`Message`]: crate::message::Message
28#[derive(Debug, Clone, PartialEq)]
29pub struct Component {
30    parts: Vec<String>,
31}
32
33impl Component {
34    /// Gets whether this is a child of the given parent.
35    /// ```rust
36    /// use rnotifylib::message::component::Component;
37    ///
38    /// // Two child components
39    /// let db_backup_component = Component::from("database/backup");
40    /// let db_uptime_component = Component::from("database/uptime");
41    ///
42    /// // Parent database component
43    /// let db_component = Component::from("database");
44    ///
45    /// // Child components of the same thing are children.
46    /// assert!(db_backup_component.is_child_of(&db_component), "backup component should be child of db component");
47    /// assert!(db_uptime_component.is_child_of(&db_component), "uptime component should be child of db component");
48    ///
49    /// // And the parent is a child of itself.
50    /// assert!(db_component.is_child_of(&db_component), "Should be a child of itself");
51    ///
52    /// // But the parent is not a child of the child.
53    /// assert!(!db_component.is_child_of(&db_backup_component), "database component should not be a child of the backup sub-component");
54    ///
55    /// let website_component = Component::from("website");
56    /// let website_backend_component = Component::from("website/component");
57    ///
58    /// // Unrelated components are not children of each other
59    /// assert!(!db_component.is_child_of(&website_backend_component), "db component shouldn't be a child of website backend component");
60    /// assert!(!db_component.is_child_of(&website_component), "db component shouldn't be a child of the website component");
61    ///
62    /// assert!(!db_backup_component.is_child_of(&website_component), "db backup component shouldn't be a child of the website component");
63    /// assert!(!db_backup_component.is_child_of(&website_backend_component), "db backup shouldn't be a child of the website backup component");
64    /// ```
65    pub fn is_child_of(&self, parent: &Component) -> bool {
66        if parent.parts.len() > self.parts.len()  {
67            return false;
68        }
69        let l = parent.parts.len();
70        self.parts[..l] == parent.parts[..l]
71    }
72}
73
74impl From<&str> for Component {
75    fn from(s: &str) -> Self {
76        Self {
77            parts: s.split("/").map(|s| s.to_owned()).filter(|s| !s.is_empty()).collect()
78        }
79    }
80}
81
82impl<'de> Deserialize<'de> for Component {
83    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
84        let s = String::deserialize(deserializer)?;
85        Ok(s.as_str().into())
86    }
87}
88
89impl Serialize for Component {
90    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
91        serializer.serialize_str(&self.parts.join("/"))
92    }
93}
94
95impl Display for Component {
96    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
97        write!(f, "{}", self.parts.join("/"))
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_child_of() {
107        let child: Component = "root/sub/block".into();
108        let parent1: Component = "root".into();
109        let parent2: Component = "root/sub".into();
110        let parent3: Component = "root/sub/".into();
111
112        should_be_child(&child, &parent1, "basic");
113        should_be_child(&child, &parent2, "depth 2");
114        should_be_child(&child, &parent3, "parent trailing /");
115
116        let parent4: Component = "scraperpi/services".into();
117        let child2: Component = "scraperpi".into();
118        assert!(!child2.is_child_of(&parent4));
119
120        let parent4: Component = "heating".into();
121        let child3: Component = "heating/test".into();
122        assert!(child3.is_child_of(&parent4));
123    }
124
125    fn should_be_child(child: &Component, parent: &Component, message: &str) {
126        assert!(child.is_child_of(parent), "{} should be child of: {} - {}", &child, &parent, message);
127    }
128
129    #[test]
130    fn completely_different() {
131        let child: Component = "aaa".into();
132        let parent: Component = "heating".into();
133        assert!(!child.is_child_of(&parent));
134    }
135}