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}