1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::fmt::{Display, Formatter};
use serde::{Serialize, Deserialize, Deserializer, Serializer};

/// Indicates what (program/functionality) a [`Message`] is referring to.
/// This helps route messages to the relevant location, as well
/// as control their severity.
///
/// # Examples #
/// ```rust
/// use rnotifylib::message::component::Component;
///
/// let db_backup_component = Component::from("database/backup");
/// let db_uptime_component = Component::from("database/uptime");
///
/// let db_component = Component::from("database");
///
/// // Both of these are children of the db component - Hence destinations that subscribe
/// // to the database component will receive both backup and uptime messages.
/// assert!(db_backup_component.is_child_of(&db_component), "backup component should be child of db component");
/// assert!(db_uptime_component.is_child_of(&db_component), "uptime component should be child of db component");
///
/// // Additionally, the database component is a "child" of itself,
/// // Therefore messages with the "database" component will be sent to places that listen for the database component
/// assert!(db_component.is_child_of(&db_component));
/// ```
///
/// [`Message`]: crate::message::Message
#[derive(Debug, Clone, PartialEq)]
pub struct Component {
    parts: Vec<String>,
}

impl Component {
    /// Gets whether this is a child of the given parent.
    /// ```rust
    /// use rnotifylib::message::component::Component;
    ///
    /// // Two child components
    /// let db_backup_component = Component::from("database/backup");
    /// let db_uptime_component = Component::from("database/uptime");
    ///
    /// // Parent database component
    /// let db_component = Component::from("database");
    ///
    /// // Child components of the same thing are children.
    /// assert!(db_backup_component.is_child_of(&db_component), "backup component should be child of db component");
    /// assert!(db_uptime_component.is_child_of(&db_component), "uptime component should be child of db component");
    ///
    /// // And the parent is a child of itself.
    /// assert!(db_component.is_child_of(&db_component), "Should be a child of itself");
    ///
    /// // But the parent is not a child of the child.
    /// assert!(!db_component.is_child_of(&db_backup_component), "database component should not be a child of the backup sub-component");
    ///
    /// let website_component = Component::from("website");
    /// let website_backend_component = Component::from("website/component");
    ///
    /// // Unrelated components are not children of each other
    /// assert!(!db_component.is_child_of(&website_backend_component), "db component shouldn't be a child of website backend component");
    /// assert!(!db_component.is_child_of(&website_component), "db component shouldn't be a child of the website component");
    ///
    /// assert!(!db_backup_component.is_child_of(&website_component), "db backup component shouldn't be a child of the website component");
    /// assert!(!db_backup_component.is_child_of(&website_backend_component), "db backup shouldn't be a child of the website backup component");
    /// ```
    pub fn is_child_of(&self, parent: &Component) -> bool {
        if parent.parts.len() > self.parts.len()  {
            return false;
        }
        let l = parent.parts.len();
        self.parts[..l] == parent.parts[..l]
    }
}

impl From<&str> for Component {
    fn from(s: &str) -> Self {
        Self {
            parts: s.split("/").map(|s| s.to_owned()).filter(|s| !s.is_empty()).collect()
        }
    }
}

impl<'de> Deserialize<'de> for Component {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
        let s = String::deserialize(deserializer)?;
        Ok(s.as_str().into())
    }
}

impl Serialize for Component {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
        serializer.serialize_str(&self.parts.join("/"))
    }
}

impl Display for Component {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.parts.join("/"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_child_of() {
        let child: Component = "root/sub/block".into();
        let parent1: Component = "root".into();
        let parent2: Component = "root/sub".into();
        let parent3: Component = "root/sub/".into();

        should_be_child(&child, &parent1, "basic");
        should_be_child(&child, &parent2, "depth 2");
        should_be_child(&child, &parent3, "parent trailing /");

        let parent4: Component = "scraperpi/services".into();
        let child2: Component = "scraperpi".into();
        assert!(!child2.is_child_of(&parent4));

        let parent4: Component = "heating".into();
        let child3: Component = "heating/test".into();
        assert!(child3.is_child_of(&parent4));
    }

    fn should_be_child(child: &Component, parent: &Component, message: &str) {
        assert!(child.is_child_of(parent), "{} should be child of: {} - {}", &child, &parent, message);
    }

    #[test]
    fn completely_different() {
        let child: Component = "aaa".into();
        let parent: Component = "heating".into();
        assert!(!child.is_child_of(&parent));
    }
}