1use std::{borrow::Cow, hash::Hash};
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum LabelValue<'a> {
8 Str(Cow<'a, str>),
10 Int(i64),
12 Uint(u64),
14 Bool(bool),
16}
17
18impl LabelValue<'_> {
19 pub fn as_str(&self) -> Cow<'_, str> {
21 match self {
22 LabelValue::Str(s) => Cow::Borrowed(s.as_ref()),
23 LabelValue::Int(v) => Cow::Owned(v.to_string()),
24 LabelValue::Uint(v) => Cow::Owned(v.to_string()),
25 LabelValue::Bool(v) => Cow::Borrowed(if *v { "true" } else { "false" }),
26 }
27 }
28}
29
30impl<'a> From<&'a str> for LabelValue<'a> {
31 fn from(s: &'a str) -> Self {
32 LabelValue::Str(Cow::Borrowed(s))
33 }
34}
35
36impl From<String> for LabelValue<'static> {
37 fn from(s: String) -> Self {
38 LabelValue::Str(Cow::Owned(s))
39 }
40}
41
42macro_rules! impl_from_int {
43 ($($t:ty => $variant:ident),*) => {
44 $(
45 impl From<$t> for LabelValue<'static> {
46 fn from(v: $t) -> Self {
47 LabelValue::$variant(v as _)
48 }
49 }
50 )*
51 };
52}
53
54impl_from_int!(
55 i64 => Int, i32 => Int, i16 => Int, i8 => Int,
56 u64 => Uint, u32 => Uint, u16 => Uint, u8 => Uint
57);
58
59impl From<bool> for LabelValue<'static> {
60 fn from(v: bool) -> Self {
61 LabelValue::Bool(v)
62 }
63}
64
65pub type LabelPair<'a> = (&'static str, LabelValue<'a>);
67
68pub trait EncodeLabelValue {
74 fn encode_label_value(&self) -> LabelValue<'_>;
76}
77
78impl EncodeLabelValue for str {
79 fn encode_label_value(&self) -> LabelValue<'_> {
80 LabelValue::Str(Cow::Borrowed(self))
81 }
82}
83
84impl EncodeLabelValue for String {
85 fn encode_label_value(&self) -> LabelValue<'_> {
86 LabelValue::Str(Cow::Borrowed(self.as_str()))
87 }
88}
89
90impl<T: EncodeLabelValue + ?Sized> EncodeLabelValue for &T {
91 fn encode_label_value(&self) -> LabelValue<'_> {
92 T::encode_label_value(self)
93 }
94}
95
96impl EncodeLabelValue for bool {
97 fn encode_label_value(&self) -> LabelValue<'_> {
98 LabelValue::Bool(*self)
99 }
100}
101
102macro_rules! impl_encode_label_value_int {
103 ($($t:ty => $variant:ident),*) => {
104 $(
105 impl EncodeLabelValue for $t {
106 fn encode_label_value(&self) -> LabelValue<'_> {
107 LabelValue::$variant(*self as _)
108 }
109 }
110 )*
111 };
112}
113
114impl_encode_label_value_int!(
115 i64 => Int, i32 => Int, i16 => Int, i8 => Int,
116 u64 => Uint, u32 => Uint, u16 => Uint, u8 => Uint
117);
118
119pub trait EncodeLabelSet: Hash + Eq + Clone + Send + Sync + 'static {
124 fn encode_label_pairs(&self) -> Vec<LabelPair<'_>>;
126}
127
128#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
130pub struct NoLabels;
131
132impl EncodeLabelSet for NoLabels {
133 fn encode_label_pairs(&self) -> Vec<LabelPair<'_>> {
134 Vec::new()
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::EncodeLabelSet;
142
143 #[test]
144 fn test_label_value_as_str() {
145 assert_eq!(LabelValue::from("hello").as_str(), "hello");
146 assert_eq!(LabelValue::from(42i64).as_str(), "42");
147 assert_eq!(LabelValue::from(42u64).as_str(), "42");
148 assert_eq!(LabelValue::from(true).as_str(), "true");
149 assert_eq!(LabelValue::from(false).as_str(), "false");
150 }
151
152 #[test]
153 fn test_no_labels() {
154 assert!(NoLabels.encode_label_pairs().is_empty());
155 }
156
157 #[test]
158 fn test_encode_label_value() {
159 let s = String::from("hello");
161 assert!(matches!(
162 s.encode_label_value(),
163 LabelValue::Str(Cow::Borrowed("hello"))
164 ));
165 assert!(matches!(42u64.encode_label_value(), LabelValue::Uint(42)));
166 assert!(matches!((-7i32).encode_label_value(), LabelValue::Int(-7)));
167 assert!(matches!(true.encode_label_value(), LabelValue::Bool(true)));
168 }
169
170 #[test]
171 fn test_derive_encode_label_set() {
172 #[derive(Clone, Hash, PartialEq, Eq, crate::EncodeLabelSet)]
174 struct MyLabels {
175 method: String,
176 kind: &'static str,
177 #[label(name = "status_code")]
178 status: u16,
179 count: i64,
180 #[label(skip)]
181 _internal: u64,
182 }
183
184 let labels = MyLabels {
185 method: "GET".into(),
186 kind: "x",
187 status: 200,
188 count: -3,
189 _internal: 999,
190 };
191 let pairs = labels.encode_label_pairs();
192 assert_eq!(pairs.len(), 4);
193 assert_eq!(pairs[0], ("method", LabelValue::from("GET")));
194 assert_eq!(pairs[1].0, "kind");
195 assert_eq!(pairs[1].1.as_str(), "x");
196 assert_eq!(pairs[2].0, "status_code");
197 assert_eq!(pairs[2].1.as_str(), "200");
198 assert_eq!(pairs[3].1.as_str(), "-3");
199 assert!(matches!(&pairs[0].1, LabelValue::Str(Cow::Borrowed(_))));
201 }
202
203 #[test]
204 fn test_derive_encode_label_value_enum() {
205 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, crate::EncodeLabelValue)]
207 enum Kind {
208 Ipv4,
209 Ipv6,
210 #[label(name = "loopback")]
211 Local,
212 }
213
214 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, crate::EncodeLabelValue)]
216 #[label(rename_all = "kebab-case")]
217 enum Mode {
218 FastPath,
219 SlowPath,
220 }
221
222 assert_eq!(Kind::Ipv4.encode_label_value().as_str(), "ipv4");
223 assert_eq!(Kind::Ipv6.encode_label_value().as_str(), "ipv6");
224 assert_eq!(Kind::Local.encode_label_value().as_str(), "loopback");
225 assert_eq!(Mode::FastPath.encode_label_value().as_str(), "fast-path");
226 assert_eq!(Mode::SlowPath.encode_label_value().as_str(), "slow-path");
227 }
228
229 #[test]
230 fn test_derive_rename_all() {
231 #[derive(Clone, Hash, PartialEq, Eq, crate::EncodeLabelSet)]
233 #[label(rename_all = "kebab-case")]
234 struct Kebab {
235 method_name: String,
236 #[label(name = "literal")]
237 status_code: u16,
238 }
239 #[derive(Clone, Hash, PartialEq, Eq, crate::EncodeLabelSet)]
240 #[label(rename_all = "PascalCase")]
241 struct Pascal {
242 api_method: String,
243 }
244
245 let kebab = Kebab {
246 method_name: "GET".into(),
247 status_code: 200,
248 };
249 let k = kebab.encode_label_pairs();
250 assert_eq!(k[0].0, "method-name");
251 assert_eq!(k[1].0, "literal");
252
253 let pascal = Pascal {
254 api_method: "POST".into(),
255 };
256 let p = pascal.encode_label_pairs();
257 assert_eq!(p[0].0, "ApiMethod");
258 }
259}