saluki_core/
support.rs

1//! Cross-cutting support types shared across subsystems.
2
3use std::fmt;
4
5use smallvec::SmallVec;
6use stringtheory::MetaString;
7
8use crate::runtime::get_sanitized_name;
9
10/// A fully qualified, canonical identifier for a uniquely addressable subsystem within the overall system.
11///
12/// `SubsystemIdentifier` is meant to represent a unique identifier for any "subsystem" in a Saluki-based data plane
13/// such that it is already sanitized, normalized, and ready to be used in the various registries and areas where unique
14/// names are required: health registry, resource account, supervision trees, and more.
15///
16/// Segments are sanitized as they are added, and any segment that sanitizes to an empty string is dropped. A
17/// `SubsystemIdentifier` therefore never contains an empty segment, and its rendered form never has a leading,
18/// trailing, or doubled separator.
19#[derive(Clone, Debug, Eq, Hash, PartialEq)]
20pub struct SubsystemIdentifier {
21    segments: SmallVec<[MetaString; 6]>,
22}
23
24impl SubsystemIdentifier {
25    /// Creates a `SubsystemIdentifier` from a number of segments.
26    ///
27    /// Each segment is sanitized/normalized independently, and any segment that sanitizes to an empty string is
28    /// dropped.
29    pub fn from_segments<I, S>(segments: I) -> Self
30    where
31        I: IntoIterator<Item = S>,
32        S: AsRef<str>,
33    {
34        Self {
35            segments: segments
36                .into_iter()
37                .map(|s| get_sanitized_name(s.as_ref()))
38                .filter(|s| !s.is_empty())
39                .collect(),
40        }
41    }
42
43    /// Creates a `SubsystemIdentifier` from a single dotted string, treating each period as a segment separator.
44    ///
45    /// The input is split on `.`, then each segment is sanitized/normalized independently (matching
46    /// [`from_segments`][Self::from_segments]) and empty segments are dropped. This is the correct constructor when the
47    /// caller already holds an assembled, dotted name (for example `topology.primary.sources.in`); passing such a
48    /// string to [`from_segments`][Self::from_segments] as a lone segment would instead collapse the periods into
49    /// underscores.
50    pub fn from_dotted(dotted: &str) -> Self {
51        Self::from_segments(dotted.split('.'))
52    }
53
54    /// Consumes the identifier and returns a new one with the given segment appended.
55    ///
56    /// The segment is sanitized/normalized first; if it sanitizes to empty, the identifier is returned unchanged.
57    pub fn child<S: AsRef<str>>(mut self, segment: S) -> Self {
58        let segment = get_sanitized_name(segment.as_ref());
59        if !segment.is_empty() {
60            self.segments.push(segment);
61        }
62        self
63    }
64
65    /// Returns `true` if `other` is a strict descendant of this identifier.
66    ///
67    /// A descendant has this identifier as a complete leading run of segments and at least one additional segment. For
68    /// example, `env_provider.workload` is an ancestor of `env_provider.workload.remote_agent` but not of
69    /// `env_provider.workloads` (segments are compared whole, not as string prefixes), nor of `env_provider.workload`
70    /// itself (the match must be strict).
71    pub fn is_ancestor_of(&self, other: &SubsystemIdentifier) -> bool {
72        other.segments.len() > self.segments.len() && other.segments[..self.segments.len()] == self.segments[..]
73    }
74}
75
76impl fmt::Display for SubsystemIdentifier {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        for (idx, segment) in self.segments.iter().enumerate() {
79            if idx > 0 {
80                write!(f, ".")?;
81            }
82            write!(f, "{}", segment)?;
83        }
84        Ok(())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::SubsystemIdentifier;
91
92    #[test]
93    fn general_segments_are_sanitized() {
94        let identifier = SubsystemIdentifier::from_segments(["env_provider", "workload", "remote-agent"]);
95        assert_eq!(identifier.to_string(), "env_provider.workload.remote_agent");
96    }
97
98    #[test]
99    fn from_dotted_splits_and_sanitizes() {
100        // Periods separate segments, and each segment is sanitized independently.
101        let identifier = SubsystemIdentifier::from_dotted("topology.primary.sources.dsd-in");
102        assert_eq!(identifier.to_string(), "topology.primary.sources.dsd_in");
103
104        // A lone segment via `from_segments` collapses periods instead of splitting on them.
105        let collapsed = SubsystemIdentifier::from_segments(["topology.primary"]);
106        assert_eq!(collapsed.to_string(), "topology_primary");
107    }
108
109    #[test]
110    fn empty_segments_are_dropped() {
111        // Leading, trailing, and doubled separators produce empty segments, which are dropped so the rendered form
112        // never has stray separators. This keeps the identifier consistent with the process-tree name sanitizer.
113        assert_eq!(SubsystemIdentifier::from_dotted(".a..b.").to_string(), "a.b");
114        assert_eq!(SubsystemIdentifier::from_segments(["a", "", "b"]).to_string(), "a.b");
115
116        // A segment consisting only of trimmable characters sanitizes to empty and is likewise dropped, including via
117        // `child`.
118        assert_eq!(SubsystemIdentifier::from_segments(["a", "--", "b"]).to_string(), "a.b");
119        assert_eq!(SubsystemIdentifier::from_segments(["a"]).child("--").to_string(), "a");
120    }
121
122    #[test]
123    fn is_ancestor_of() {
124        let identifier = SubsystemIdentifier::from_segments(["env_provider", "workload"]);
125        let ancestor_of = |s: &str| identifier.is_ancestor_of(&SubsystemIdentifier::from_dotted(s));
126
127        // Strict descendants match, at any depth.
128        assert!(ancestor_of("env_provider.workload.remote_agent"));
129        assert!(ancestor_of("env_provider.workload.remote_agent.aggregator"));
130
131        // The identifier is not an ancestor of itself.
132        assert!(!ancestor_of("env_provider.workload"));
133
134        // Segments are compared whole, so a longer segment sharing a prefix does not match.
135        assert!(!ancestor_of("env_provider.workloads.remote_agent"));
136
137        // Unrelated names, and names for which the identifier is a suffix rather than a prefix, do not match.
138        assert!(!ancestor_of("topology.primary.sources.in"));
139        assert!(!ancestor_of("workload.remote_agent"));
140    }
141}