Skip to main content

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}