1#[inline]
2pub fn short_type_name<T: ?Sized>() -> String {
3 let full = std::any::type_name::<T>();
4 let mut out = String::with_capacity(full.len());
5 let mut segment_start = 0;
6 for (i, c) in full.char_indices() {
7 if matches!(c, '<' | '>' | ',' | ' ') {
8 out.push_str(strip_path(&full[segment_start..i]));
9 out.push(c);
10 segment_start = i + c.len_utf8();
11 }
12 }
13
14 if segment_start < full.len() {
15 out.push_str(strip_path(&full[segment_start..]));
16 }
17
18 out
19}
20
21fn strip_path(segment: &str) -> &str {
22 match segment.rfind("::") {
23 Some(idx) => &segment[idx + 2..],
24 None => segment,
25 }
26}
27
28pub trait ToSnakeCase<O> {
29 fn to_snake_case(&self) -> O;
30}
31
32impl ToSnakeCase<String> for &'_ str {
33 fn to_snake_case(&self) -> String {
34 if self
35 .chars()
36 .all(|c| c.is_uppercase() || c.is_ascii_digit() || c == '_')
37 {
38 return self.to_string();
39 }
40
41 let mut snake_case = String::new();
42
43 for (i, c) in self.chars().enumerate() {
44 if c.is_uppercase() {
45 if i != 0 {
46 snake_case.push('_');
47 }
48 for lower_c in c.to_lowercase() {
49 snake_case.push(lower_c);
50 }
51 } else {
52 snake_case.push(c);
53 }
54 }
55 snake_case
56 }
57}
58
59impl ToSnakeCase<String> for String {
60 fn to_snake_case(&self) -> String {
61 if self
62 .chars()
63 .all(|c| c.is_uppercase() || c.is_ascii_digit() || c == '_')
64 {
65 return self.to_string();
66 }
67
68 let mut snake_case = String::new();
69
70 for (i, c) in self.chars().enumerate() {
71 if c.is_uppercase() {
72 if i != 0 {
73 snake_case.push('_');
74 }
75 for lower_c in c.to_lowercase() {
76 snake_case.push(lower_c);
77 }
78 } else {
79 snake_case.push(c);
80 }
81 }
82 snake_case
83 }
84}
85
86pub fn generate_metric_key<T: ?Sized>(category: &str) -> String {
87 let struct_type_name = std::any::type_name::<T>();
88 let head = struct_type_name
89 .split('<')
90 .next()
91 .unwrap_or(struct_type_name);
92 let base = head.rsplit("::").next().unwrap_or(head).trim();
93
94 let mut parts = words(base);
95 parts.retain(|w| w != category);
96
97 let mut result = vec![category.to_string()];
98 result.extend(parts);
99 result.join(".")
100}
101
102fn words(s: &str) -> Vec<String> {
103 let chars: Vec<char> = s.chars().collect();
104 let mut words = Vec::new();
105 let mut current = String::new();
106
107 for (i, &c) in chars.iter().enumerate() {
108 if c == '_' || c == '-' || c.is_whitespace() {
109 if !current.is_empty() {
110 words.push(std::mem::take(&mut current));
111 }
112 continue;
113 }
114 if c.is_uppercase() {
115 let prev_lower_or_digit =
116 i > 0 && (chars[i - 1].is_lowercase() || chars[i - 1].is_ascii_digit());
117 let prev_upper = i > 0 && chars[i - 1].is_uppercase();
118 let next_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
119 let boundary = prev_lower_or_digit || (prev_upper && next_lower);
120 if boundary && !current.is_empty() {
121 words.push(std::mem::take(&mut current));
122 }
123 }
124 current.push(c.to_ascii_lowercase());
125 }
126 if !current.is_empty() {
127 words.push(current);
128 }
129 words
130}