pub(super) mod segment_document_builder;
pub(super) mod value_builder;
use std::{borrow::Cow, collections::HashMap};
use opentelemetry::{Array, StringValue, Value};
use crate::xray_exporter::types::{AnnotationValue, AnySlice, AnyValue, StrList};
type AttributeKey = &'static str;
type BuilderHandler<'v, Builder> = fn(&mut Builder, &'v Value) -> bool;
pub(super) trait SpanAttributeProcessor<'v, const N: usize> {
const HANDLERS: [(AttributeKey, BuilderHandler<'v, Self>); N];
}
pub(super) type ProcessorId = usize;
pub(super) type HandlerId = usize;
#[derive(Debug, Default)]
pub(super) struct DispatchTable(HashMap<&'static str, Vec<(ProcessorId, HandlerId)>>);
impl DispatchTable {
pub fn dispatch(&self, key: &str) -> &[(ProcessorId, HandlerId)] {
match self.0.get(key) {
Some(v) => v.as_slice(),
None => &[],
}
}
pub fn register<'v, const N: usize, P: SpanAttributeProcessor<'v, N>>(
&mut self,
processor_id: ProcessorId,
) {
for (handler_id, key) in P::HANDLERS.iter().map(|a| a.0).enumerate() {
let element = (processor_id, handler_id);
self.0
.entry(key)
.and_modify(|v| v.push(element))
.or_insert_with(|| vec![element]);
}
}
}
pub(super) fn get_cow(v: &Value) -> Cow<'_, str> {
match v {
Value::String(string_value) => Cow::Borrowed(string_value.as_str()),
v => Cow::Owned(v.to_string()),
}
}
pub(super) fn get_str(v: &Value) -> Option<&str> {
match v {
Value::String(string_value) => Some(string_value.as_str()),
_ => None,
}
}
pub(super) fn get_integer(v: &Value) -> Option<i64> {
match v {
Value::I64(i) => Some(*i),
Value::String(i) => i.as_str().trim().parse().ok(),
_ => None,
}
}
pub(super) fn get_bool(v: &Value) -> Option<bool> {
match v {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub(super) fn get_string_vec(v: &Value) -> Option<&dyn StrList> {
match v {
Value::Array(Array::String(string_values)) => Some(string_values),
_ => None,
}
}
pub(super) fn get_annotation(v: &Value) -> Option<AnnotationValue<'_>> {
match v {
Value::Bool(b) => Some(AnnotationValue::Boolean(*b)),
Value::I64(i) => Some(AnnotationValue::Int(*i)),
Value::F64(f) => Some(AnnotationValue::Float(*f)),
Value::String(s) => Some(AnnotationValue::String(s.as_str())),
_ => None,
}
}
pub(super) fn get_any_value(v: &Value) -> Option<AnyValue<'_>> {
Some(match v {
Value::Bool(b) => AnyValue::Bool(*b),
Value::I64(i) => AnyValue::Int(*i),
Value::F64(f) => AnyValue::Float(*f),
Value::String(s) => AnyValue::String(s.as_str()),
Value::Array(a) => AnyValue::Slice(match a {
Array::Bool(items) => AnySlice::from(items.as_slice()),
Array::I64(items) => AnySlice::from(items.as_slice()),
Array::F64(items) => AnySlice::from(items.as_slice()),
Array::String(string_values) => AnySlice::String(string_values),
_ => return None,
}),
_ => return None,
})
}
impl StrList for Vec<StringValue> {
fn len(&self) -> usize {
self.len()
}
fn get(&self, i: usize) -> Option<&str> {
self.as_slice().get(i).map(|s| s.as_str())
}
}
mod semconv {
pub use opentelemetry_semantic_conventions::attribute::*;
pub const AWS_OPERATION: &str = "aws.operation";
pub const AWS_SERVICE: &str = "aws.service";
pub const DB_SERVICE: &str = "db.service";
pub const AWS_REGION: &str = "aws.region";
pub const AWS_QUEUE_URL: &str = "aws.queue.url";
pub const AWS_TABLE_NAME: &str = "aws.table.name";
pub const TELEMETRY_AUTO_VERSION: &str = "telemetry.auto.version";
pub const HTTP_STATUS_TEXT: &str = "http.status_text";
pub const AWS_HTTP_ERROR_MESSAGE: &str = "aws.http.error_message";
pub const AWS_HTTP_ERROR_EVENT: &str = "aws.http.error.event";
pub const AWS_HTTP_TRACED: &str = "aws.http.traced";
pub const CODE_MODULE_NAME: &str = "code.module.name";
}
#[cfg(test)]
mod tests {
use core::f64::consts::PI;
use super::*;
use opentelemetry::{Array, Value};
struct MockProcessor;
impl<'v> SpanAttributeProcessor<'v, 3> for MockProcessor {
const HANDLERS: [(&'static str, fn(&mut Self, &'v Value) -> bool); 3] = [
("attribute.a", |_, _| true),
("attribute.c", |_, _| true),
("attribute.b", |_, _| true),
];
}
struct AnotherMockProcessor;
impl<'v> SpanAttributeProcessor<'v, 2> for AnotherMockProcessor {
const HANDLERS: [(&'static str, fn(&mut Self, &'v Value) -> bool); 2] = [
("attribute.a", |_, _| true), ("attribute.d", |_, _| true),
];
}
#[test]
fn dispatch_table_register_valid() {
let mut table = DispatchTable::default();
table.register::<3, MockProcessor>(0);
assert_eq!(table.0.len(), 3);
table.register::<2, AnotherMockProcessor>(1);
assert_eq!(table.0.len(), 4);
}
#[test]
fn dispatch_table_register_empty() {
struct EmptyProcessor;
impl<'v> SpanAttributeProcessor<'v, 0> for EmptyProcessor {
const HANDLERS: [(&'static str, fn(&mut Self, &'v Value) -> bool); 0] = [];
}
let mut table = DispatchTable::default();
table.register::<0, EmptyProcessor>(0);
assert_eq!(table.0.len(), 0);
}
#[test]
fn dispatch_table_dispatch_valid() {
let mut table = DispatchTable::default();
table.register::<3, MockProcessor>(0);
table.register::<2, AnotherMockProcessor>(1);
let handlers = table.dispatch("attribute.b");
assert_eq!(handlers.len(), 1);
assert_eq!(handlers[0], (0, 2));
let handlers = table.dispatch("attribute.a");
assert_eq!(handlers.len(), 2);
assert_eq!(handlers[0], (0, 0));
assert_eq!(handlers[1], (1, 0));
let handlers = table.dispatch("attribute.d");
assert_eq!(handlers.len(), 1);
assert_eq!(handlers[0], (1, 1));
}
#[test]
fn dispatch_table_dispatch_invalid() {
let mut table = DispatchTable::default();
table.register::<3, MockProcessor>(0);
let handlers = table.dispatch("nonexistent.key");
assert_eq!(handlers.len(), 0);
let handlers = table.dispatch("attribute");
assert_eq!(handlers.len(), 0);
let handlers = table.dispatch("Attribute.a");
assert_eq!(handlers.len(), 0);
let empty_table = DispatchTable::default();
let handlers = empty_table.dispatch("any.key");
assert_eq!(handlers.len(), 0);
}
#[test]
fn get_cow_valid() {
let special = Value::String("hello\nworld\t!".into());
let cow = get_cow(&special);
assert!(matches!(cow, Cow::Borrowed(_)));
assert_eq!(cow, "hello\nworld\t!");
}
#[test]
fn get_cow_other() {
let bool_val = Value::Bool(true);
let cow = get_cow(&bool_val);
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(cow, "true");
let int_val = Value::I64(42);
let cow = get_cow(&int_val);
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(cow, "42");
let float_val = Value::F64(PI);
let cow = get_cow(&float_val);
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(cow, "3.141592653589793");
let array_val = Value::Array(Array::Bool(vec![true, false]));
let cow = get_cow(&array_val);
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(cow, "[true,false]");
}
#[test]
fn get_str_valid() {
let string_val = Value::String("test".into());
assert_eq!(get_str(&string_val), Some("test"));
}
#[test]
fn get_str_invalid() {
let bool_val = Value::Bool(true);
assert_eq!(get_str(&bool_val), None);
let int_val = Value::I64(42);
assert_eq!(get_str(&int_val), None);
let float_val = Value::F64(PI);
assert_eq!(get_str(&float_val), None);
let array_val = Value::Array(Array::String(vec!["test".into()]));
assert_eq!(get_str(&array_val), None);
}
#[test]
fn get_integer_valid() {
let int_val = Value::I64(42);
assert_eq!(get_integer(&int_val), Some(42));
let negative = Value::I64(-100);
assert_eq!(get_integer(&negative), Some(-100));
let zero = Value::I64(0);
assert_eq!(get_integer(&zero), Some(0));
let string_int = Value::String("123".into());
assert_eq!(get_integer(&string_int), Some(123));
let negative_str = Value::String("-456".into());
assert_eq!(get_integer(&negative_str), Some(-456));
let whitespace = Value::String(" 789 ".into());
assert_eq!(get_integer(&whitespace), Some(789));
}
#[test]
fn get_integer_invalid() {
let bool_val = Value::Bool(true);
assert_eq!(get_integer(&bool_val), None);
let float_val = Value::F64(PI);
assert_eq!(get_integer(&float_val), None);
let invalid_str = Value::String("not a number".into());
assert_eq!(get_integer(&invalid_str), None);
let empty = Value::String("".into());
assert_eq!(get_integer(&empty), None);
let float_str = Value::String("3.14".into());
assert_eq!(get_integer(&float_str), None);
let array_val = Value::Array(Array::I64(vec![1, 2, 3]));
assert_eq!(get_integer(&array_val), None);
}
#[test]
fn get_bool_valid() {
let true_val = Value::Bool(true);
assert_eq!(get_bool(&true_val), Some(true));
let false_val = Value::Bool(false);
assert_eq!(get_bool(&false_val), Some(false));
}
#[test]
fn get_bool_invalid() {
let int_val = Value::I64(1);
assert_eq!(get_bool(&int_val), None);
let string_val = Value::String("true".into());
assert_eq!(get_bool(&string_val), None);
let float_val = Value::F64(1.0);
assert_eq!(get_bool(&float_val), None);
let array_val = Value::Array(Array::Bool(vec![true]));
assert_eq!(get_bool(&array_val), None);
}
#[test]
fn get_string_vec_valid() {
let string_array = Value::Array(Array::String(vec![
"first".into(),
"second".into(),
"third".into(),
]));
let result = get_string_vec(&string_array);
assert!(result.is_some());
let str_list = result.unwrap();
assert_eq!(str_list.len(), 3);
assert_eq!(str_list.get(0), Some("first"));
assert_eq!(str_list.get(1), Some("second"));
assert_eq!(str_list.get(2), Some("third"));
let empty_array = Value::Array(Array::String(vec![]));
let result = get_string_vec(&empty_array);
assert!(result.is_some());
assert_eq!(result.unwrap().len(), 0);
let single = Value::Array(Array::String(vec!["only".into()]));
let result = get_string_vec(&single);
assert!(result.is_some());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn get_string_vec_invalid() {
let bool_array = Value::Array(Array::Bool(vec![true, false]));
assert!(get_string_vec(&bool_array).is_none());
let int_array = Value::Array(Array::I64(vec![1, 2, 3]));
assert!(get_string_vec(&int_array).is_none());
let float_array = Value::Array(Array::F64(vec![1.0, 2.0]));
assert!(get_string_vec(&float_array).is_none());
let string_val = Value::String("not an array".into());
assert!(get_string_vec(&string_val).is_none());
let int_val = Value::I64(42);
assert!(get_string_vec(&int_val).is_none());
}
#[test]
fn get_annotation_valid() {
let bool_val = Value::Bool(true);
let result = get_annotation(&bool_val);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnnotationValue::Boolean(true)));
let int_val = Value::I64(42);
let result = get_annotation(&int_val);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnnotationValue::Int(42)));
let float_val = Value::F64(PI);
let result = get_annotation(&float_val);
assert!(result.is_some());
if let Some(AnnotationValue::Float(f)) = result {
assert!((f - PI).abs() < 0.001);
} else {
panic!("Expected Float variant");
}
let string_val = Value::String("test".into());
let result = get_annotation(&string_val);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnnotationValue::String("test")));
let empty = Value::String("".into());
let result = get_annotation(&empty);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnnotationValue::String("")));
}
#[test]
fn get_annotation_invalid() {
let bool_array = Value::Array(Array::Bool(vec![true]));
assert!(get_annotation(&bool_array).is_none());
let int_array = Value::Array(Array::I64(vec![1, 2]));
assert!(get_annotation(&int_array).is_none());
let float_array = Value::Array(Array::F64(vec![1.0]));
assert!(get_annotation(&float_array).is_none());
let string_array = Value::Array(Array::String(vec!["test".into()]));
assert!(get_annotation(&string_array).is_none());
}
#[test]
fn get_any_value_comprehensive() {
let bool_val = Value::Bool(true);
let result = get_any_value(&bool_val);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnyValue::Bool(true)));
let false_val = Value::Bool(false);
assert!(matches!(
get_any_value(&false_val).unwrap(),
AnyValue::Bool(false)
));
let int_val = Value::I64(42);
let result = get_any_value(&int_val);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnyValue::Int(42)));
let negative_int = Value::I64(-100);
assert!(matches!(
get_any_value(&negative_int).unwrap(),
AnyValue::Int(-100)
));
let float_val = Value::F64(PI);
let result = get_any_value(&float_val);
assert!(result.is_some());
if let Some(AnyValue::Float(f)) = result {
assert!((f - PI).abs() < 0.001);
} else {
panic!("Expected Float variant");
}
let string_val = Value::String("test".into());
let result = get_any_value(&string_val);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnyValue::String("test")));
let empty_string = Value::String("".into());
assert!(matches!(
get_any_value(&empty_string).unwrap(),
AnyValue::String("")
));
let bool_array = Value::Array(Array::Bool(vec![true, false, true]));
let result = get_any_value(&bool_array);
assert!(result.is_some());
assert!(matches!(
result.unwrap(),
AnyValue::Slice(AnySlice::Bool(_))
));
let int_array = Value::Array(Array::I64(vec![1, 2, 3]));
let result = get_any_value(&int_array);
assert!(result.is_some());
assert!(matches!(result.unwrap(), AnyValue::Slice(AnySlice::Int(_))));
let float_array = Value::Array(Array::F64(vec![1.0, 2.0, 3.0]));
let result = get_any_value(&float_array);
assert!(result.is_some());
assert!(matches!(
result.unwrap(),
AnyValue::Slice(AnySlice::Float(_))
));
let string_array = Value::Array(Array::String(vec!["first".into(), "second".into()]));
let result = get_any_value(&string_array);
assert!(result.is_some());
assert!(matches!(
result.unwrap(),
AnyValue::Slice(AnySlice::String(_))
));
let empty_bool_array = Value::Array(Array::Bool(vec![]));
assert!(matches!(
get_any_value(&empty_bool_array).unwrap(),
AnyValue::Slice(AnySlice::Bool(_))
));
let empty_string_array = Value::Array(Array::String(vec![]));
assert!(matches!(
get_any_value(&empty_string_array).unwrap(),
AnyValue::Slice(AnySlice::String(_))
));
}
}