use crate::api;
use crate::api::labels;
use crate::api::KeyValue;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::collections::{btree_map, btree_map::Entry, BTreeMap};
use std::time::Duration;
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Resource {
attrs: BTreeMap<api::Key, api::Value>,
}
impl Resource {
pub fn new<T: IntoIterator<Item = api::KeyValue>>(kvs: T) -> Self {
let mut resource = Resource::default();
for kv in kvs.into_iter() {
resource.insert(kv);
}
resource
}
pub fn from_detectors(timeout: Duration, detectors: Vec<Box<dyn ResourceDetector>>) -> Self {
let mut resource = Resource::default();
for detector in detectors {
let detected_res = detector.detect(timeout);
for (key, value) in detected_res.into_iter() {
resource.insert(KeyValue::new(key, value));
}
}
resource
}
pub fn merge(&self, other: &Self) -> Self {
if self.attrs.is_empty() {
return other.clone();
}
if other.attrs.is_empty() {
return self.clone();
}
let mut resource = Resource::default();
for (k, v) in self.attrs.iter() {
resource.insert(api::KeyValue {
key: k.clone(),
value: v.clone(),
});
}
for (k, v) in other.attrs.iter() {
resource.insert(api::KeyValue {
key: k.clone(),
value: v.clone(),
});
}
resource
}
pub fn len(&self) -> usize {
self.attrs.len()
}
pub fn is_empty(&self) -> bool {
self.attrs.is_empty()
}
pub fn iter(&self) -> Iter {
self.into_iter()
}
pub fn encoded(&self, encoder: &dyn labels::Encoder) -> String {
encoder.encode(&mut self.into_iter())
}
fn insert(&mut self, item: api::KeyValue) {
match self.attrs.entry(item.key) {
Entry::Occupied(mut existing_item) => {
if let api::Value::String(s) = existing_item.get() {
if s.is_empty() {
existing_item.insert(item.value);
}
}
}
Entry::Vacant(v) => {
v.insert(item.value);
}
}
}
}
#[derive(Debug)]
pub struct IntoIter(btree_map::IntoIter<api::Key, api::Value>);
impl Iterator for IntoIter {
type Item = (api::Key, api::Value);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl IntoIterator for Resource {
type Item = (api::Key, api::Value);
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter(self.attrs.into_iter())
}
}
#[derive(Debug)]
pub struct Iter<'a>(btree_map::Iter<'a, api::Key, api::Value>);
impl<'a> Iterator for Iter<'a> {
type Item = (&'a api::Key, &'a api::Value);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<'a> IntoIterator for &'a Resource {
type Item = (&'a api::Key, &'a api::Value);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
Iter(self.attrs.iter())
}
}
pub trait ResourceDetector {
fn detect(&self, timeout: Duration) -> Resource;
}
#[cfg(test)]
mod tests {
use super::Resource;
use crate::api;
use crate::sdk::EnvResourceDetector;
use std::collections::BTreeMap;
use std::{env, time};
#[test]
fn new_resource() {
let args_with_dupe_keys = vec![
api::KeyValue::new("a", ""),
api::KeyValue::new("a", "final"),
];
let mut expected_attrs = BTreeMap::new();
expected_attrs.insert(api::Key::new("a"), api::Value::from("final"));
assert_eq!(
Resource::new(args_with_dupe_keys),
Resource {
attrs: expected_attrs
}
);
}
#[test]
fn merge_resource() {
let resource_a = Resource::new(vec![
api::KeyValue::new("a", ""),
api::KeyValue::new("b", "b-value"),
]);
let resource_b = Resource::new(vec![
api::KeyValue::new("a", "final"),
api::KeyValue::new("c", "c-value"),
]);
let mut expected_attrs = BTreeMap::new();
expected_attrs.insert(api::Key::new("a"), api::Value::from("final"));
expected_attrs.insert(api::Key::new("b"), api::Value::from("b-value"));
expected_attrs.insert(api::Key::new("c"), api::Value::from("c-value"));
assert_eq!(
resource_a.merge(&resource_b),
Resource {
attrs: expected_attrs
}
);
}
#[test]
fn detect_resource() {
env::set_var("OTEL_RESOURCE_ATTRIBUTES", "key=value, k = v , a= x, a=z");
env::set_var("irrelevant".to_uppercase(), "20200810");
let detector = EnvResourceDetector::new();
let resource =
Resource::from_detectors(time::Duration::from_secs(5), vec![Box::new(detector)]);
assert_eq!(
resource,
Resource::new(vec![
api::KeyValue::new(
api::Key::new("key".to_string()),
api::Value::String("value".to_string())
),
api::KeyValue::new(
api::Key::new("k".to_string()),
api::Value::String("v".to_string())
),
api::KeyValue::new(
api::Key::new("a".to_string()),
api::Value::String("x".to_string())
),
api::KeyValue::new(
api::Key::new("a".to_string()),
api::Value::String("z".to_string())
)
])
)
}
}