use crate::vm::{Register, RegisterValue};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub trait FieldDescriptor<T> {
type Value: Serialize + for<'de> Deserialize<'de>;
fn path(&self) -> &'static str;
}
pub trait FieldRef<T> {
fn path(&self) -> &'static str;
fn get_ref<'a>(&self, _source: &'a T) -> Option<&'a T> {
None }
}
pub trait FieldAccessor {
type Value: Serialize + for<'de> Deserialize<'de>;
fn path() -> &'static str;
fn segments() -> Vec<&'static str> {
Self::path().split('.').collect()
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! __field_path {
($first:ident) => {
stringify!($first)
};
($first:ident, $($rest:ident),+) => {
concat!(stringify!($first), ".", $crate::__field_path!($($rest),+))
};
}
#[macro_export]
macro_rules! impl_field_descriptors {
($struct_name:ident { $( $field_name:ident : $field_type:ty ),* $(,)? }) => {
impl $struct_name {
$(
pub fn $field_name() -> impl $crate::metrics_context::FieldDescriptor<$struct_name, Value = $field_type> {
struct FieldDescriptorImpl;
impl $crate::metrics_context::FieldDescriptor<$struct_name> for FieldDescriptorImpl {
type Value = $field_type;
fn path(&self) -> &'static str {
stringify!($field_name)
}
}
FieldDescriptorImpl
}
)*
}
};
}
#[macro_export]
macro_rules! field {
($struct_expr:expr, $field:ident) => {{
struct __FieldRef;
impl<T> $crate::metrics_context::FieldRef<T> for __FieldRef {
fn path(&self) -> &'static str {
stringify!($field)
}
}
__FieldRef
}};
($struct_expr:expr, $($field:ident).+) => {{
struct __FieldRef;
impl<T> $crate::metrics_context::FieldRef<T> for __FieldRef {
fn path(&self) -> &'static str {
$crate::__field_path!($($field),+)
}
}
__FieldRef
}};
}
#[macro_export]
macro_rules! field_accessor {
($name:ident, $type:ty, $path:expr) => {
pub struct $name;
impl $crate::metrics_context::FieldAccessor for $name {
type Value = $type;
fn path() -> &'static str {
$path
}
}
};
}
pub use crate::vm::CompiledPath;
pub struct MetricsContext<'a> {
state_reg: Register,
registers: &'a mut Vec<RegisterValue>,
#[allow(dead_code)]
compiled_paths: &'a HashMap<String, CompiledPath>,
slot: Option<u64>,
signature: Option<String>,
timestamp: i64,
}
impl<'a> MetricsContext<'a> {
pub fn new(
state_reg: Register,
registers: &'a mut Vec<RegisterValue>,
compiled_paths: &'a HashMap<String, CompiledPath>,
slot: Option<u64>,
signature: Option<String>,
timestamp: i64,
) -> Self {
Self {
state_reg,
registers,
compiled_paths,
slot,
signature,
timestamp,
}
}
pub fn account(&self, name: &str) -> Option<String> {
let _ = name;
None
}
pub fn data<T: for<'de> Deserialize<'de>>(&self, field: &str) -> Option<T> {
let _ = field;
None
}
pub fn get<T: for<'de> Deserialize<'de>>(&self, field_path: &str) -> Option<T> {
let state = self.registers.get(self.state_reg)?;
let segments: Vec<&str> = field_path.split('.').collect();
let mut current = state;
for segment in segments {
current = current.get(segment)?;
}
serde_json::from_value(current.clone()).ok()
}
pub fn get_ref<T, F>(&self, field_ref: &F) -> Option<T>
where
T: for<'de> Deserialize<'de>,
F: FieldRef<T>,
{
self.get(field_ref.path())
}
pub fn get_field<T, F>(&self, field: F) -> Option<F::Value>
where
F: FieldDescriptor<T>,
{
self.get(field.path())
}
pub fn get_field_legacy<F: FieldAccessor>(&self, _field: F) -> Option<F::Value> {
self.get(F::path())
}
pub fn set<T: Serialize>(&mut self, field: &str, value: T) {
if let Ok(json_value) = serde_json::to_value(value) {
self.set_field_value(field, json_value);
}
}
pub fn set_ref<T, F>(&mut self, field_ref: &F, value: T)
where
T: Serialize,
F: FieldRef<T>,
{
if let Ok(json_value) = serde_json::to_value(value) {
self.set_field_value(field_ref.path(), json_value);
}
}
pub fn set_field<T, F>(&mut self, field: F, value: F::Value)
where
F: FieldDescriptor<T>,
{
self.set(field.path(), value)
}
pub fn set_field_legacy<F: FieldAccessor>(&mut self, _field: F, value: F::Value) {
self.set(F::path(), value)
}
pub fn increment(&mut self, field: &str, amount: u64) {
if let Some(current) = self.get::<u64>(field) {
self.set(field, current + amount);
} else {
self.set(field, amount);
}
}
pub fn increment_ref<F>(&mut self, field_ref: &F, amount: u64)
where
F: FieldRef<u64>,
{
let path = field_ref.path();
if let Some(current) = self.get::<u64>(path) {
self.set(path, current + amount);
} else {
self.set(path, amount);
}
}
pub fn increment_field<T, F>(&mut self, field: F, amount: u64)
where
F: FieldDescriptor<T, Value = u64>,
{
self.increment(field.path(), amount)
}
pub fn increment_field_legacy<F: FieldAccessor>(&mut self, _field: F, amount: u64)
where
F::Value: Into<u64> + From<u64>,
{
self.increment(F::path(), amount)
}
pub fn sum(&mut self, field: &str, value: u64) {
self.increment(field, value);
}
pub fn sum_ref<F>(&mut self, field_ref: &F, value: u64)
where
F: FieldRef<u64>,
{
self.increment_ref(field_ref, value)
}
pub fn sum_field<T, F>(&mut self, field: F, value: u64)
where
F: FieldDescriptor<T, Value = u64>,
{
self.sum(field.path(), value)
}
pub fn sum_field_legacy<F: FieldAccessor>(&mut self, _field: F, value: u64)
where
F::Value: Into<u64> + From<u64>,
{
self.sum(F::path(), value)
}
pub fn add_unique(&mut self, field: &str, value: String) {
let set_field = format!("{}_set", field);
let mut set: HashSet<String> = self.get::<HashSet<String>>(&set_field).unwrap_or_default();
set.insert(value);
let count = set.len() as u64;
self.set(&set_field, set);
self.set(field, count);
}
pub fn timestamp(&self) -> i64 {
self.timestamp
}
pub fn slot(&self) -> u64 {
self.slot.unwrap_or(0)
}
pub fn signature(&self) -> &str {
self.signature.as_deref().unwrap_or("")
}
fn set_field_value(&mut self, field_path: &str, value: Value) {
if let Some(state) = self.registers.get_mut(self.state_reg) {
if !state.is_object() {
*state = Value::Object(serde_json::Map::new());
}
let segments: Vec<&str> = field_path.split('.').collect();
let mut current = state;
for segment in &segments[..segments.len() - 1] {
if current.get(segment).is_none() {
current[segment] = Value::Object(serde_json::Map::new());
}
current = current.get_mut(segment).unwrap();
}
if let Some(last_segment) = segments.last() {
current[*last_segment] = value;
}
}
}
}
use std::collections::HashSet;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_get_field() {
let mut registers = vec![json!({
"total_volume": 1000,
"metrics": {
"count": 5
}
})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
assert_eq!(ctx.get::<u64>("total_volume"), Some(1000));
assert_eq!(ctx.get::<u64>("metrics.count"), Some(5));
}
#[test]
fn test_set_field() {
let mut registers = vec![json!({})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
ctx.set("total_volume", 2000u64);
assert_eq!(ctx.get::<u64>("total_volume"), Some(2000));
}
#[test]
fn test_increment() {
let mut registers = vec![json!({"count": 10})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
ctx.increment("count", 5);
assert_eq!(ctx.get::<u64>("count"), Some(15));
ctx.increment("new_count", 3);
assert_eq!(ctx.get::<u64>("new_count"), Some(3));
}
#[test]
fn test_context_metadata() {
let mut registers = vec![json!({})];
let compiled_paths = HashMap::new();
let ctx = MetricsContext::new(
0,
&mut registers,
&compiled_paths,
Some(12345),
Some("abc123".to_string()),
1000000,
);
assert_eq!(ctx.slot(), 12345);
assert_eq!(ctx.signature(), "abc123");
assert_eq!(ctx.timestamp(), 1000000);
}
#[test]
fn test_enhanced_field_descriptor_api() {
struct TradingMetrics {
total_volume: u64,
trade_count: u64,
}
impl_field_descriptors!(TradingMetrics {
total_volume: u64,
trade_count: u64
});
let mut registers = vec![json!({
"total_volume": 1000,
"trade_count": 5
})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(1000));
assert_eq!(ctx.get_field(TradingMetrics::trade_count()), Some(5));
ctx.set_field(TradingMetrics::total_volume(), 2000);
assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(2000));
ctx.increment_field(TradingMetrics::trade_count(), 3);
assert_eq!(ctx.get_field(TradingMetrics::trade_count()), Some(8));
ctx.sum_field(TradingMetrics::total_volume(), 500);
assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(2500));
}
#[test]
fn test_legacy_field_accessor_api() {
field_accessor!(TotalVolume, u64, "total_volume");
field_accessor!(TradeCount, u64, "trade_count");
field_accessor!(LastPrice, u64, "reserves.last_price");
let mut registers = vec![json!({
"total_volume": 1000,
"trade_count": 5,
"reserves": {
"last_price": 250
}
})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
assert_eq!(ctx.get_field_legacy(TotalVolume), Some(1000));
assert_eq!(ctx.get_field_legacy(TradeCount), Some(5));
assert_eq!(ctx.get_field_legacy(LastPrice), Some(250));
ctx.set_field_legacy(TotalVolume, 2000);
assert_eq!(ctx.get_field_legacy(TotalVolume), Some(2000));
ctx.increment_field_legacy(TradeCount, 3);
assert_eq!(ctx.get_field_legacy(TradeCount), Some(8));
ctx.sum_field_legacy(TotalVolume, 500);
assert_eq!(ctx.get_field_legacy(TotalVolume), Some(2500));
ctx.set_field_legacy(LastPrice, 300);
assert_eq!(ctx.get_field_legacy(LastPrice), Some(300));
}
#[test]
fn test_enhanced_api_with_different_types() {
struct PriceMetrics {
average_price: f64,
volume: u64,
}
impl_field_descriptors!(PriceMetrics {
average_price: f64,
volume: u64
});
let mut registers = vec![json!({})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
ctx.set_field(PriceMetrics::average_price(), 123.45);
assert_eq!(ctx.get_field(PriceMetrics::average_price()), Some(123.45));
ctx.set_field(PriceMetrics::volume(), 1000);
assert_eq!(ctx.get_field(PriceMetrics::volume()), Some(1000));
}
#[test]
fn test_legacy_api_with_different_types() {
field_accessor!(AveragePrice, f64, "average_price");
let mut registers = vec![json!({})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
ctx.set_field_legacy(AveragePrice, 123.45);
assert_eq!(ctx.get_field_legacy(AveragePrice), Some(123.45));
}
#[test]
fn test_field_ref_api() {
struct TradingMetrics {
total_volume: u64,
trade_count: u64,
last_price: f64,
}
let entity = TradingMetrics {
total_volume: 0,
trade_count: 0,
last_price: 0.0,
};
let mut registers = vec![json!({
"total_volume": 1000,
"trade_count": 5,
"last_price": 250.5
})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
assert_eq!(
ctx.get_ref::<u64, _>(&field!(entity, total_volume)),
Some(1000)
);
assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, trade_count)), Some(5));
assert_eq!(
ctx.get_ref::<f64, _>(&field!(entity, last_price)),
Some(250.5)
);
ctx.set_ref(&field!(entity, total_volume), 2000u64);
assert_eq!(
ctx.get_ref::<u64, _>(&field!(entity, total_volume)),
Some(2000)
);
ctx.set_ref(&field!(entity, last_price), 300.75);
assert_eq!(
ctx.get_ref::<f64, _>(&field!(entity, last_price)),
Some(300.75)
);
ctx.increment_ref(&field!(entity, trade_count), 3);
assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, trade_count)), Some(8));
ctx.sum_ref(&field!(entity, total_volume), 500);
assert_eq!(
ctx.get_ref::<u64, _>(&field!(entity, total_volume)),
Some(2500)
);
}
#[test]
fn test_field_ref_with_nested_struct() {
struct Metrics {
reserves: Reserves,
}
struct Reserves {
last_price: f64,
}
let entity = Metrics {
reserves: Reserves { last_price: 0.0 },
};
let mut registers = vec![json!({
"reserves": {
"last_price": 100.5
}
})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
assert_eq!(
ctx.get_ref::<f64, _>(&field!(entity, reserves.last_price)),
Some(100.5)
);
ctx.set_ref(&field!(entity, reserves.last_price), 200.75);
assert_eq!(
ctx.get_ref::<f64, _>(&field!(entity, reserves.last_price)),
Some(200.75)
);
}
#[test]
fn test_enhanced_api_field_initialization() {
struct WhaleMetrics {
whale_trade_count: u64,
total_whale_volume: u64,
}
impl_field_descriptors!(WhaleMetrics {
whale_trade_count: u64,
total_whale_volume: u64
});
let mut registers = vec![json!({})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
ctx.increment_field(WhaleMetrics::whale_trade_count(), 1);
assert_eq!(ctx.get_field(WhaleMetrics::whale_trade_count()), Some(1));
ctx.increment_field(WhaleMetrics::whale_trade_count(), 2);
assert_eq!(ctx.get_field(WhaleMetrics::whale_trade_count()), Some(3));
ctx.sum_field(WhaleMetrics::total_whale_volume(), 5000);
assert_eq!(
ctx.get_field(WhaleMetrics::total_whale_volume()),
Some(5000)
);
ctx.sum_field(WhaleMetrics::total_whale_volume(), 3000);
assert_eq!(
ctx.get_field(WhaleMetrics::total_whale_volume()),
Some(8000)
);
}
#[test]
fn test_legacy_field_ref_initialization() {
struct Metrics {
whale_trade_count: u64,
}
let entity = Metrics {
whale_trade_count: 0,
};
let mut registers = vec![json!({})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
ctx.increment_ref(&field!(entity, whale_trade_count), 1);
assert_eq!(
ctx.get_ref::<u64, _>(&field!(entity, whale_trade_count)),
Some(1)
);
ctx.increment_ref(&field!(entity, whale_trade_count), 2);
assert_eq!(
ctx.get_ref::<u64, _>(&field!(entity, whale_trade_count)),
Some(3)
);
}
#[test]
fn test_backward_compatibility() {
let mut registers = vec![json!({
"volume": 100
})];
let compiled_paths = HashMap::new();
let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
assert_eq!(ctx.get::<u64>("volume"), Some(100));
ctx.set("volume", 200u64);
assert_eq!(ctx.get::<u64>("volume"), Some(200));
ctx.increment("volume", 50);
assert_eq!(ctx.get::<u64>("volume"), Some(250));
}
}