kumo_prometheus/
labels.rs

1pub trait MetricLabel {
2    fn label_names() -> &'static [&'static str];
3    fn emit_text_value(&self, target: &mut String, value: &str);
4    fn emit_json_value(&self, target: &mut String, value: &str);
5}
6
7/// `macro_rules!` implementation of `count_tts`.
8/// Source: <https://github.com/camsteffen/count-tts>
9#[macro_export]
10macro_rules! count_tts {
11    () => (0);
12    ($one:tt) => (1);
13    ($($a:tt $b:tt)+) => ($crate::count_tts!($($a)+) << 1);
14    ($odd:tt $($a:tt $b:tt)+) => ($crate::count_tts!($($a)+) << 1 | 1);
15}
16
17/// Used to declare a label key struct suitable for use in registering
18/// counters in the counter registry.
19///
20/// Usage looks like:
21///
22/// ```ignore
23/// label_key! {
24///    pub struct LabelKey {
25///       pub label1: String,
26///    }
27/// }
28/// ```
29///
30/// Always include the trailing comma after each struct field!
31///
32/// The macro will also generate `BorrowedLabelKey` and `LabelKeyTrait`
33/// types.  The `LabelKeyTrait` is implemented for both `LabelKey` and
34/// `BorrowedLabelKey` and provides a `key()` method that will return a
35/// `BorrowedLabelKey` for either.
36///
37/// The `BorrowedLabelKey` offers a `to_owned()` method to return a
38/// `LabelKey`, and a `label_pairs()` method to return a fixed size
39/// array representation consisting of the key and value pairs:
40///
41/// ```ignore
42/// assert_eq!(BorrowedLabelKey { label1: "hello"}.label_pairs(),
43///   [("label1", "hello")]
44/// )
45/// ```
46#[macro_export]
47macro_rules! label_key {
48    (pub struct $name:ident {
49        $(
50            pub $fieldname:ident: String,
51        )*
52    }
53    ) => {
54        $crate::paste::paste!{
55            #[derive(Clone, Hash, Eq, PartialEq)]
56            pub struct $name {
57                $(
58                    pub $fieldname: String,
59                )*
60            }
61
62            pub trait [<$name Trait>] {
63                fn key(&self) -> [<Borrowed $name>]<'_>;
64            }
65
66            impl [<$name Trait>] for $name {
67                fn key(&self) -> [<Borrowed $name>]<'_> {
68                    [<Borrowed $name>] {
69                        $(
70                            $fieldname: self.$fieldname.as_str(),
71                        )*
72                    }
73                }
74            }
75
76            // <https://github.com/sunshowers-code/borrow-complex-key-example/blob/main/src/lib.rs>
77            // has a detailed explanation of this stuff.
78            #[derive(Clone, Copy, Hash, Eq, PartialEq)]
79            pub struct [<Borrowed $name>]<'a> {
80                $(
81                    pub $fieldname: &'a str,
82                )*
83            }
84
85            impl<'a> [<Borrowed $name>] <'a> {
86                #[allow(unused)]
87                pub fn to_owned(&self) -> $name {
88                    $name {
89                        $(
90                            $fieldname: self.$fieldname.to_string(),
91                        )*
92                    }
93                }
94
95                #[allow(unused)]
96                pub fn label_pairs(&self) -> [(&str,&str); $crate::count_tts!($($fieldname)*)] {
97                    [
98                        $(
99                            (stringify!($fieldname), self.$fieldname),
100                        )*
101                    ]
102                }
103            }
104
105            impl<'a> From<&'a [<Borrowed $name>]<'a>> for $name {
106                fn from(value: &'a [<Borrowed $name>]<'_>) -> $name {
107                    value.to_owned()
108                }
109            }
110
111            impl<'a> From<&'a dyn [<$name Trait>]> for $name {
112                fn from(value: &'a (dyn [<$name Trait>] + 'a)) -> $name {
113                    value.key().to_owned()
114                }
115            }
116
117            impl<'a> [<$name Trait>] for [<Borrowed $name>] <'a> {
118                fn key(&self) -> [<Borrowed $name>]<'_> {
119                    *self
120                }
121            }
122
123            impl<'a> ::std::borrow::Borrow<dyn [<$name Trait>] + 'a> for $name {
124                fn borrow(&self) -> &(dyn [<$name Trait>] + 'a) {
125                    self
126                }
127            }
128
129            impl<'a> PartialEq for (dyn [<$name Trait>] + 'a) {
130                fn eq(&self, other: &Self) -> bool {
131                    self.key().eq(&other.key())
132                }
133            }
134
135            impl<'a> Eq for (dyn [<$name Trait>] + 'a) {}
136            impl<'a> ::std::hash::Hash for (dyn [<$name Trait>] + 'a) {
137                fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
138                    self.key().hash(state)
139                }
140            }
141
142            impl $crate::labels::MetricLabel for $name {
143                fn label_names() -> &'static [&'static str] {
144                    const LABEL_NAMES: &'static [&'static str] = &[
145                        $(
146                            stringify!($fieldname),
147                        )*
148                    ];
149
150                    LABEL_NAMES
151                }
152
153                fn emit_text_value(&self, target: &mut String, value: &str) {
154                    let key = self.key();
155                    let pairs = key.label_pairs();
156                    target.push('{');
157                    for (i, (key, value)) in pairs.iter().enumerate() {
158                        if i > 0 {
159                            target.push_str(", ");
160                        }
161                        target.push_str(key);
162                        target.push_str("=\"");
163                        target.push_str(value);
164                        target.push_str("\"");
165                    }
166                    target.push_str("} ");
167                    target.push_str(value);
168                }
169
170                fn emit_json_value(&self, target: &mut String, value: &str) {
171                    let key = self.key();
172                    let pairs = key.label_pairs();
173
174                    if pairs.len() == 1 {
175                        target.push('"');
176                        target.push_str(pairs[0].1);
177                        target.push_str("\":");
178                        target.push_str(value);
179                    } else {
180                        target.push_str("{");
181                        for (key, value) in pairs.iter() {
182                            target.push_str("\"");
183                            target.push_str(key);
184                            target.push_str("\":\"");
185                            target.push_str(value);
186                            target.push_str("\",");
187                        }
188
189                        target.push_str("\"@\":");
190                        target.push_str(value);
191                        target.push_str("}");
192                    }
193                }
194            }
195        }
196    };
197}
198
199#[cfg(test)]
200mod test {
201    #[test]
202    fn test_label_macro() {
203        label_key! {
204            pub struct MyLabel {
205                pub myname: String,
206            }
207        }
208
209        assert_eq!(
210            BorrowedMyLabel { myname: "hello" }.label_pairs(),
211            [("myname", "hello")]
212        );
213    }
214
215    #[test]
216    fn test_labels_macro() {
217        label_key! {
218            pub struct MyLabels {
219                pub myname: String,
220                pub second_name: String,
221            }
222        }
223
224        assert_eq!(
225            BorrowedMyLabels {
226                myname: "hello",
227                second_name: "there"
228            }
229            .label_pairs(),
230            [("myname", "hello"), ("second_name", "there")]
231        );
232    }
233}