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
use std::fmt::{Display, Formatter};
use serde::{Serialize, Deserialize, Deserializer, Serializer};

#[derive(Debug, Clone, PartialEq)]
pub struct Component {
    parts: Vec<String>,
}

impl 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));
    }
}