use alloc::format;
use alloc::string::{String, ToString};
#[cfg(feature = "observability")]
use alloc::vec::Vec;
use core::any::TypeId;
use serde::{Deserialize, Serialize};
use crate::graph::RecordOrigin;
use crate::record_id::{RecordId, RecordKey};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordMetadata {
pub record_id: u32,
pub record_key: String,
pub name: String,
pub type_id: String,
pub origin: RecordOrigin,
pub buffer_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub buffer_capacity: Option<usize>,
pub producer_count: usize,
pub consumer_count: usize,
pub writable: bool,
pub outbound_connector_count: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub entity: Option<String>,
#[cfg(feature = "observability")]
#[serde(skip_serializing_if = "Option::is_none")]
pub produced_count: Option<u64>,
#[cfg(feature = "observability")]
#[serde(skip_serializing_if = "Option::is_none")]
pub consumed_count: Option<u64>,
#[cfg(feature = "observability")]
#[serde(skip_serializing_if = "Option::is_none")]
pub dropped_count: Option<u64>,
#[cfg(feature = "observability")]
#[serde(skip_serializing_if = "Option::is_none")]
pub occupancy: Option<(usize, usize)>,
#[cfg(feature = "observability")]
#[serde(skip_serializing_if = "Option::is_none")]
pub stage_profiling: Option<Vec<crate::profiling::StageProfilingInfo>>,
#[cfg(feature = "observability")]
#[serde(skip_serializing_if = "Option::is_none")]
pub signal_stats: Option<Vec<crate::profiling::SignalStatsInfo>>,
}
impl RecordMetadata {
#[allow(clippy::too_many_arguments)]
pub fn new<K: RecordKey>(
record_id: RecordId,
record_key: K,
type_id: TypeId,
name: String,
origin: RecordOrigin,
buffer_type: String,
buffer_capacity: Option<usize>,
producer_count: usize,
consumer_count: usize,
writable: bool,
outbound_connector_count: usize,
) -> Self {
let entity = Some(super::topic_leaf(record_key.as_str()).to_string());
Self {
record_id: record_id.raw(),
record_key: record_key.as_str().to_string(),
name,
type_id: format!("{:?}", type_id),
origin,
buffer_type,
buffer_capacity,
producer_count,
consumer_count,
writable,
outbound_connector_count,
schema_type: None,
entity,
#[cfg(feature = "observability")]
produced_count: None,
#[cfg(feature = "observability")]
consumed_count: None,
#[cfg(feature = "observability")]
dropped_count: None,
#[cfg(feature = "observability")]
occupancy: None,
#[cfg(feature = "observability")]
stage_profiling: None,
#[cfg(feature = "observability")]
signal_stats: None,
}
}
#[cfg(feature = "observability")]
pub fn with_buffer_metrics(mut self, snapshot: crate::buffer::BufferMetricsSnapshot) -> Self {
self.produced_count = Some(snapshot.produced_count);
self.consumed_count = Some(snapshot.consumed_count);
self.dropped_count = Some(snapshot.dropped_count);
if snapshot.occupancy.1 > 0 {
self.occupancy = Some(snapshot.occupancy);
}
self
}
#[cfg(feature = "observability")]
pub fn with_stage_profiling(
mut self,
stages: Vec<crate::profiling::StageProfilingInfo>,
) -> Self {
if !stages.is_empty() {
self.stage_profiling = Some(stages);
}
self
}
#[cfg(feature = "observability")]
pub fn with_signal_stats(mut self, signals: Vec<crate::profiling::SignalStatsInfo>) -> Self {
if !signals.is_empty() {
self.signal_stats = Some(signals);
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::record_id::StringKey;
#[test]
fn test_record_metadata_creation() {
let type_id = TypeId::of::<i32>();
let metadata = RecordMetadata::new(
RecordId::new(0),
StringKey::new("test.record"),
type_id,
"i32".to_string(),
RecordOrigin::Source,
"spmc_ring".to_string(),
Some(100),
1,
2,
false,
0,
);
assert_eq!(metadata.record_id, 0);
assert_eq!(metadata.record_key, "test.record");
assert_eq!(metadata.name, "i32");
assert!(matches!(metadata.origin, RecordOrigin::Source));
assert_eq!(metadata.buffer_type, "spmc_ring");
assert_eq!(metadata.buffer_capacity, Some(100));
assert_eq!(metadata.producer_count, 1);
assert_eq!(metadata.consumer_count, 2);
assert_eq!(metadata.outbound_connector_count, 0);
assert!(!metadata.writable);
}
#[test]
fn test_record_metadata_serialization() {
let type_id = TypeId::of::<String>();
let metadata = RecordMetadata::new(
RecordId::new(1),
StringKey::new("app.config"),
type_id,
"String".to_string(),
RecordOrigin::Passive,
"single_latest".to_string(),
None,
1,
1,
true,
2,
);
let json = serde_json::to_string(&metadata).unwrap();
assert!(json.contains("\"record_id\":1"));
assert!(json.contains("\"record_key\":\"app.config\""));
assert!(json.contains("\"name\":\"String\""));
assert!(json.contains("\"origin\":\"passive\""));
assert!(json.contains("\"buffer_type\":\"single_latest\""));
assert!(json.contains("\"writable\":true"));
assert!(json.contains("\"outbound_connector_count\":2"));
}
}