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}