1use std::borrow::{Borrow, Cow};
2use std::sync::Arc;
3use std::{fmt, hash};
4
5use std::hash::{Hash, Hasher};
6
7#[non_exhaustive]
13#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct Key(OtelString);
15
16impl Key {
17 pub fn new(value: impl Into<Key>) -> Self {
30 value.into()
31 }
32
33 pub const fn from_static_str(value: &'static str) -> Self {
35 Key(OtelString::Static(value))
36 }
37
38 pub fn as_str(&self) -> &str {
40 self.0.as_str()
41 }
42}
43
44impl From<&'static str> for Key {
45 fn from(key_str: &'static str) -> Self {
47 Key(OtelString::Static(key_str))
48 }
49}
50
51impl From<String> for Key {
52 fn from(string: String) -> Self {
54 Key(OtelString::Owned(string.into_boxed_str()))
55 }
56}
57
58impl From<Arc<str>> for Key {
59 fn from(string: Arc<str>) -> Self {
61 Key(OtelString::RefCounted(string))
62 }
63}
64
65impl From<Cow<'static, str>> for Key {
66 fn from(string: Cow<'static, str>) -> Self {
68 match string {
69 Cow::Borrowed(s) => Key(OtelString::Static(s)),
70 Cow::Owned(s) => Key(OtelString::Owned(s.into_boxed_str())),
71 }
72 }
73}
74
75impl fmt::Debug for Key {
76 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
77 self.0.fmt(fmt)
78 }
79}
80
81impl From<Key> for String {
82 fn from(key: Key) -> Self {
83 match key.0 {
84 OtelString::Owned(s) => s.to_string(),
85 OtelString::Static(s) => s.to_string(),
86 OtelString::RefCounted(s) => s.to_string(),
87 }
88 }
89}
90
91impl fmt::Display for Key {
92 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match &self.0 {
94 OtelString::Owned(s) => s.fmt(fmt),
95 OtelString::Static(s) => s.fmt(fmt),
96 OtelString::RefCounted(s) => s.fmt(fmt),
97 }
98 }
99}
100
101impl Borrow<str> for Key {
102 fn borrow(&self) -> &str {
103 self.0.as_str()
104 }
105}
106
107impl AsRef<str> for Key {
108 fn as_ref(&self) -> &str {
109 self.0.as_str()
110 }
111}
112
113#[derive(Clone, Debug, Eq)]
114enum OtelString {
115 Owned(Box<str>),
116 Static(&'static str),
117 RefCounted(Arc<str>),
118}
119
120impl OtelString {
121 fn as_str(&self) -> &str {
122 match self {
123 OtelString::Owned(s) => s.as_ref(),
124 OtelString::Static(s) => s,
125 OtelString::RefCounted(s) => s.as_ref(),
126 }
127 }
128}
129
130impl PartialOrd for OtelString {
131 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
132 Some(self.cmp(other))
133 }
134}
135
136impl Ord for OtelString {
137 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
138 self.as_str().cmp(other.as_str())
139 }
140}
141
142impl PartialEq for OtelString {
143 fn eq(&self, other: &Self) -> bool {
144 self.as_str().eq(other.as_str())
145 }
146}
147
148impl hash::Hash for OtelString {
149 fn hash<H: hash::Hasher>(&self, state: &mut H) {
150 self.as_str().hash(state)
151 }
152}
153
154#[non_exhaustive]
156#[derive(Clone, Debug, PartialEq)]
157pub enum Array {
158 Bool(Vec<bool>),
160 I64(Vec<i64>),
162 F64(Vec<f64>),
164 String(Vec<StringValue>),
166}
167
168impl fmt::Display for Array {
169 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match self {
171 Array::Bool(values) => display_array_str(values, fmt),
172 Array::I64(values) => display_array_str(values, fmt),
173 Array::F64(values) => display_array_str(values, fmt),
174 Array::String(values) => {
175 write!(fmt, "[")?;
176 for (i, t) in values.iter().enumerate() {
177 if i > 0 {
178 write!(fmt, ",")?;
179 }
180 write!(fmt, "\"{}\"", t)?;
181 }
182 write!(fmt, "]")
183 }
184 }
185 }
186}
187
188fn display_array_str<T: fmt::Display>(slice: &[T], fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(fmt, "[")?;
190 for (i, t) in slice.iter().enumerate() {
191 if i > 0 {
192 write!(fmt, ",")?;
193 }
194 write!(fmt, "{}", t)?;
195 }
196 write!(fmt, "]")
197}
198
199macro_rules! into_array {
200 ($(($t:ty, $val:expr),)+) => {
201 $(
202 impl From<$t> for Array {
203 fn from(t: $t) -> Self {
204 $val(t)
205 }
206 }
207 )+
208 }
209}
210
211into_array!(
212 (Vec<bool>, Array::Bool),
213 (Vec<i64>, Array::I64),
214 (Vec<f64>, Array::F64),
215 (Vec<StringValue>, Array::String),
216);
217
218#[non_exhaustive]
220#[derive(Clone, Debug, PartialEq)]
221pub enum Value {
222 Bool(bool),
224 I64(i64),
226 F64(f64),
228 String(StringValue),
230 Array(Array),
232}
233
234#[non_exhaustive]
236#[derive(Clone, PartialEq, Eq, Hash)]
237pub struct StringValue(OtelString);
238
239impl fmt::Debug for StringValue {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 self.0.fmt(f)
242 }
243}
244
245impl fmt::Display for StringValue {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 match &self.0 {
248 OtelString::Owned(s) => s.fmt(f),
249 OtelString::Static(s) => s.fmt(f),
250 OtelString::RefCounted(s) => s.fmt(f),
251 }
252 }
253}
254
255impl AsRef<str> for StringValue {
256 fn as_ref(&self) -> &str {
257 self.0.as_str()
258 }
259}
260
261impl StringValue {
262 pub fn as_str(&self) -> &str {
264 self.0.as_str()
265 }
266}
267
268impl From<StringValue> for String {
269 fn from(s: StringValue) -> Self {
270 match s.0 {
271 OtelString::Owned(s) => s.to_string(),
272 OtelString::Static(s) => s.to_string(),
273 OtelString::RefCounted(s) => s.to_string(),
274 }
275 }
276}
277
278impl From<&'static str> for StringValue {
279 fn from(s: &'static str) -> Self {
280 StringValue(OtelString::Static(s))
281 }
282}
283
284impl From<String> for StringValue {
285 fn from(s: String) -> Self {
286 StringValue(OtelString::Owned(s.into_boxed_str()))
287 }
288}
289
290impl From<Arc<str>> for StringValue {
291 fn from(s: Arc<str>) -> Self {
292 StringValue(OtelString::RefCounted(s))
293 }
294}
295
296impl From<Cow<'static, str>> for StringValue {
297 fn from(s: Cow<'static, str>) -> Self {
298 match s {
299 Cow::Owned(s) => StringValue(OtelString::Owned(s.into_boxed_str())),
300 Cow::Borrowed(s) => StringValue(OtelString::Static(s)),
301 }
302 }
303}
304
305impl From<Value> for StringValue {
306 fn from(s: Value) -> Self {
307 match s {
308 Value::Bool(v) => format!("{}", v).into(),
309 Value::I64(v) => format!("{}", v).into(),
310 Value::F64(v) => format!("{}", v).into(),
311 Value::String(v) => v,
312 Value::Array(v) => format!("{}", v).into(),
313 }
314 }
315}
316
317impl Value {
318 pub fn as_str(&self) -> Cow<'_, str> {
322 match self {
323 Value::Bool(v) => format!("{}", v).into(),
324 Value::I64(v) => format!("{}", v).into(),
325 Value::F64(v) => format!("{}", v).into(),
326 Value::String(v) => Cow::Borrowed(v.as_str()),
327 Value::Array(v) => format!("{}", v).into(),
328 }
329 }
330}
331
332macro_rules! from_values {
333 (
334 $(
335 ($t:ty, $val:expr);
336 )+
337 ) => {
338 $(
339 impl From<$t> for Value {
340 fn from(t: $t) -> Self {
341 $val(t)
342 }
343 }
344 )+
345 }
346}
347
348from_values!(
349 (bool, Value::Bool);
350 (i64, Value::I64);
351 (f64, Value::F64);
352 (StringValue, Value::String);
353);
354
355impl From<&'static str> for Value {
356 fn from(s: &'static str) -> Self {
357 Value::String(s.into())
358 }
359}
360
361impl From<String> for Value {
362 fn from(s: String) -> Self {
363 Value::String(s.into())
364 }
365}
366
367impl From<Arc<str>> for Value {
368 fn from(s: Arc<str>) -> Self {
369 Value::String(s.into())
370 }
371}
372
373impl From<Cow<'static, str>> for Value {
374 fn from(s: Cow<'static, str>) -> Self {
375 Value::String(s.into())
376 }
377}
378
379impl fmt::Display for Value {
380 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
381 match self {
382 Value::Bool(v) => v.fmt(fmt),
383 Value::I64(v) => v.fmt(fmt),
384 Value::F64(v) => v.fmt(fmt),
385 Value::String(v) => fmt.write_str(v.as_str()),
386 Value::Array(v) => v.fmt(fmt),
387 }
388 }
389}
390
391#[derive(Clone, Debug, PartialEq)]
393#[non_exhaustive]
394pub struct KeyValue {
395 pub key: Key,
397
398 pub value: Value,
400}
401
402impl KeyValue {
403 pub fn new<K, V>(key: K, value: V) -> Self
405 where
406 K: Into<Key>,
407 V: Into<Value>,
408 {
409 KeyValue {
410 key: key.into(),
411 value: value.into(),
412 }
413 }
414}
415
416struct F64Hashable(f64);
417
418impl PartialEq for F64Hashable {
419 fn eq(&self, other: &Self) -> bool {
420 self.0.to_bits() == other.0.to_bits()
421 }
422}
423
424impl Eq for F64Hashable {}
425
426impl Hash for F64Hashable {
427 fn hash<H: Hasher>(&self, state: &mut H) {
428 self.0.to_bits().hash(state);
429 }
430}
431
432impl Hash for KeyValue {
433 fn hash<H: Hasher>(&self, state: &mut H) {
434 self.key.hash(state);
435 match &self.value {
436 Value::F64(f) => F64Hashable(*f).hash(state),
437 Value::Array(a) => match a {
438 Array::Bool(b) => b.hash(state),
439 Array::I64(i) => i.hash(state),
440 Array::F64(f) => f.iter().for_each(|f| F64Hashable(*f).hash(state)),
441 Array::String(s) => s.hash(state),
442 },
443 Value::Bool(b) => b.hash(state),
444 Value::I64(i) => i.hash(state),
445 Value::String(s) => s.hash(state),
446 };
447 }
448}
449
450impl Eq for KeyValue {}
451
452#[derive(Debug, Default, Clone)]
461#[non_exhaustive]
462pub struct InstrumentationScope {
463 name: Cow<'static, str>,
467
468 version: Option<Cow<'static, str>>,
470
471 schema_url: Option<Cow<'static, str>>,
475
476 attributes: Vec<KeyValue>,
478}
479
480impl PartialEq for InstrumentationScope {
481 fn eq(&self, other: &Self) -> bool {
482 self.name == other.name
483 && self.version == other.version
484 && self.schema_url == other.schema_url
485 && self.attributes.len() == other.attributes.len()
486 && {
487 let mut self_attrs = Vec::from_iter(&self.attributes);
488 let mut other_attrs = Vec::from_iter(&other.attributes);
489 self_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
490 other_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
491 self_attrs == other_attrs
492 }
493 }
494}
495
496impl Eq for InstrumentationScope {}
497
498impl hash::Hash for InstrumentationScope {
499 fn hash<H: hash::Hasher>(&self, state: &mut H) {
500 self.name.hash(state);
501 self.version.hash(state);
502 self.schema_url.hash(state);
503 let mut sorted_attrs = Vec::from_iter(&self.attributes);
504 sorted_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
505 for attribute in sorted_attrs {
506 attribute.hash(state);
507 }
508 }
509}
510
511impl InstrumentationScope {
512 pub fn builder<T: Into<Cow<'static, str>>>(name: T) -> InstrumentationScopeBuilder {
514 InstrumentationScopeBuilder {
515 name: name.into(),
516 version: None,
517 schema_url: None,
518 attributes: None,
519 }
520 }
521
522 #[inline]
524 pub fn name(&self) -> &str {
525 &self.name
526 }
527
528 #[inline]
530 pub fn version(&self) -> Option<&str> {
531 self.version.as_deref()
532 }
533
534 #[inline]
538 pub fn schema_url(&self) -> Option<&str> {
539 self.schema_url.as_deref()
540 }
541
542 #[inline]
544 pub fn attributes(&self) -> impl Iterator<Item = &KeyValue> {
545 self.attributes.iter()
546 }
547}
548
549#[derive(Debug)]
561pub struct InstrumentationScopeBuilder {
562 name: Cow<'static, str>,
563 version: Option<Cow<'static, str>>,
564 schema_url: Option<Cow<'static, str>>,
565 attributes: Option<Vec<KeyValue>>,
566}
567
568impl InstrumentationScopeBuilder {
569 pub fn with_version(mut self, version: impl Into<Cow<'static, str>>) -> Self {
579 self.version = Some(version.into());
580 self
581 }
582
583 pub fn with_schema_url(mut self, schema_url: impl Into<Cow<'static, str>>) -> Self {
593 self.schema_url = Some(schema_url.into());
594 self
595 }
596
597 pub fn with_attributes<I>(mut self, attributes: I) -> Self
609 where
610 I: IntoIterator<Item = KeyValue>,
611 {
612 self.attributes = Some(attributes.into_iter().collect());
613 self
614 }
615
616 pub fn build(self) -> InstrumentationScope {
618 InstrumentationScope {
619 name: self.name,
620 version: self.version,
621 schema_url: self.schema_url,
622 attributes: self.attributes.unwrap_or_default(),
623 }
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use std::hash::{Hash, Hasher};
630
631 use crate::{InstrumentationScope, KeyValue};
632
633 use rand::random;
634 use std::collections::hash_map::DefaultHasher;
635 use std::f64;
636
637 #[test]
638 fn kv_float_equality() {
639 let kv1 = KeyValue::new("key", 1.0);
640 let kv2 = KeyValue::new("key", 1.0);
641 assert_eq!(kv1, kv2);
642
643 let kv1 = KeyValue::new("key", 1.0);
644 let kv2 = KeyValue::new("key", 1.01);
645 assert_ne!(kv1, kv2);
646
647 let kv1 = KeyValue::new("key", f64::NAN);
648 let kv2 = KeyValue::new("key", f64::NAN);
649 assert_ne!(kv1, kv2, "NAN is not equal to itself");
650
651 for float_val in [
652 f64::INFINITY,
653 f64::NEG_INFINITY,
654 f64::MAX,
655 f64::MIN,
656 f64::MIN_POSITIVE,
657 ]
658 .iter()
659 {
660 let kv1 = KeyValue::new("key", *float_val);
661 let kv2 = KeyValue::new("key", *float_val);
662 assert_eq!(kv1, kv2);
663 }
664
665 for _ in 0..100 {
666 let random_value = random::<f64>();
667 let kv1 = KeyValue::new("key", random_value);
668 let kv2 = KeyValue::new("key", random_value);
669 assert_eq!(kv1, kv2);
670 }
671 }
672
673 #[test]
674 fn kv_float_hash() {
675 for float_val in [
676 f64::NAN,
677 f64::INFINITY,
678 f64::NEG_INFINITY,
679 f64::MAX,
680 f64::MIN,
681 f64::MIN_POSITIVE,
682 ]
683 .iter()
684 {
685 let kv1 = KeyValue::new("key", *float_val);
686 let kv2 = KeyValue::new("key", *float_val);
687 assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
688 }
689
690 for _ in 0..100 {
691 let random_value = random::<f64>();
692 let kv1 = KeyValue::new("key", random_value);
693 let kv2 = KeyValue::new("key", random_value);
694 assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
695 }
696 }
697
698 fn hash_helper<T: Hash>(item: &T) -> u64 {
699 let mut hasher = DefaultHasher::new();
700 item.hash(&mut hasher);
701 hasher.finish()
702 }
703
704 #[test]
705 fn instrumentation_scope_equality() {
706 let scope1 = InstrumentationScope::builder("my-crate")
707 .with_version("v0.1.0")
708 .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
709 .with_attributes([KeyValue::new("k", "v")])
710 .build();
711 let scope2 = InstrumentationScope::builder("my-crate")
712 .with_version("v0.1.0")
713 .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
714 .with_attributes([KeyValue::new("k", "v")])
715 .build();
716 assert_eq!(scope1, scope2);
717 }
718
719 #[test]
720 fn instrumentation_scope_equality_attributes_diff_order() {
721 let scope1 = InstrumentationScope::builder("my-crate")
722 .with_version("v0.1.0")
723 .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
724 .with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
725 .build();
726 let scope2 = InstrumentationScope::builder("my-crate")
727 .with_version("v0.1.0")
728 .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
729 .with_attributes([KeyValue::new("k2", "v2"), KeyValue::new("k1", "v1")])
730 .build();
731 assert_eq!(scope1, scope2);
732
733 let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
735 scope1.hash(&mut hasher1);
736 let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
737 scope2.hash(&mut hasher2);
738 assert_eq!(hasher1.finish(), hasher2.finish());
739 }
740
741 #[test]
742 fn instrumentation_scope_equality_different_attributes() {
743 let scope1 = InstrumentationScope::builder("my-crate")
744 .with_version("v0.1.0")
745 .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
746 .with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
747 .build();
748 let scope2 = InstrumentationScope::builder("my-crate")
749 .with_version("v0.1.0")
750 .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
751 .with_attributes([KeyValue::new("k2", "v3"), KeyValue::new("k4", "v5")])
752 .build();
753 assert_ne!(scope1, scope2);
754
755 let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
757 scope1.hash(&mut hasher1);
758 let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
759 scope2.hash(&mut hasher2);
760 assert_ne!(hasher1.finish(), hasher2.finish());
761 }
762}