opentelemetry_traceable/selector.rs
1//! Selecting `#[traceable]` functions by name/key, using exact match,
2//! or `*` pattern match.
3//!
4//! Name/key is either:
5//! * `module_path!() + "::" + fn_name`
6//! * the macro's `name` argument
7//!
8//! # Patterns
9//!
10//! `*` matches any sequence of characters. It acts as a wildcard.
11//!
12//! ```text
13//! my_app::domain::db::* matches my_app::domain::db::users::insert
14//! matches my_app::domain::db::query
15//! does NOT match my_app::domain::db
16//! *::db::* matches any db function, in any crate or module
17//! * matches everything
18//! ```
19//!
20//! # Typos are errors, empty patterns are warnings
21//!
22//! An *exact* selector that matches nothing is almost certainly a typo, so
23//! [`resolve`] fails ([`UnknownKeys`]) and callers apply nothing. A *pattern* that
24//! matches nothing is only reported ([`Selection::unmatched_patterns`]).
25
26use crate::registry;
27
28/// Whether `selector` is a pattern (contains `*`) rather than an exact key.
29pub fn is_pattern(selector: &str) -> bool {
30 selector.contains('*')
31}
32
33/// Whether `pattern` matches `key`.
34pub fn matches(pattern: &str, key: &str) -> bool {
35 let mut pattern_segments = pattern.split('*');
36 let first_pattern_segment = pattern_segments
37 .next()
38 .expect("`split` always yields at least one part");
39
40 let Some(mut key_after_prefix) = key.strip_prefix(first_pattern_segment) else {
41 // key does not start with `first_pattern_segment` --> no match
42 return false;
43 };
44 let Some(last_pattern_segment) = pattern_segments.next_back() else {
45 // `first_pattern_segment` was the whole pattern, so it must
46 // be equal to the whole key for a match.
47 return key_after_prefix.is_empty();
48 };
49 for mid_pattern_segment in pattern_segments {
50 // Find each segment and slide the key.
51 // If segments are all found in order we have a match.
52 match key_after_prefix.find(mid_pattern_segment) {
53 Some(at) => key_after_prefix = &key_after_prefix[at + mid_pattern_segment.len()..],
54 None => return false,
55 }
56 }
57
58 key_after_prefix.ends_with(last_pattern_segment)
59}
60
61/// Result of a selection (match of a list of selectors).
62#[derive(Debug, Clone, Default)]
63pub struct Selection {
64 /// Registry keys that matched.
65 pub keys: Vec<&'static str>,
66 /// Patterns that matched no key.
67 pub unmatched_patterns: Vec<String>,
68}
69
70/// Exact selectors that match no `#[traceable]` function in this binary.
71#[derive(Debug, Clone)]
72pub struct UnknownKeys {
73 /// The unrecognized selectors.
74 pub keys: Vec<String>,
75}
76
77impl std::fmt::Display for UnknownKeys {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "unknown #[traceable] key(s): {}", self.keys.join(", "))
80 }
81}
82
83impl std::error::Error for UnknownKeys {}
84
85/// Resolve `selectors`:
86///
87/// * exact registry keys
88/// * `*` patterns
89/// * mix of the above
90///
91/// against every `#[traceable]` function linked into this binary.
92///
93/// Unmatched *patterns* are returned in [`Selection::unmatched_patterns`].
94///
95/// # Errors
96///
97/// [`UnknownKeys`] if any *exact* selector matches no function.
98pub fn resolve<S: AsRef<str>>(selectors: &[S]) -> Result<Selection, UnknownKeys> {
99 let keys = registry::keys();
100 let mut matched: Vec<&'static str> = Vec::new();
101 let mut unknown: Vec<String> = Vec::new();
102 let mut unmatched_patterns: Vec<String> = Vec::new();
103
104 for selector in selectors {
105 let selector = selector.as_ref().trim();
106 if selector.is_empty() {
107 continue;
108 }
109 if is_pattern(selector) {
110 let before = matched.len();
111 matched.extend(keys.iter().copied().filter(|key| matches(selector, key)));
112 if matched.len() == before {
113 unmatched_patterns.push(selector.to_string());
114 }
115 } else {
116 // `keys` is sorted
117 match keys.binary_search_by(|candidate| (**candidate).cmp(selector)) {
118 Ok(at) => matched.push(keys[at]),
119 Err(_) => unknown.push(selector.to_string()),
120 }
121 }
122 }
123
124 if !unknown.is_empty() {
125 return Err(UnknownKeys { keys: unknown });
126 }
127
128 matched.sort_unstable();
129 matched.dedup();
130 Ok(Selection {
131 keys: matched,
132 unmatched_patterns,
133 })
134}