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