hyperstack_interpreter/
metrics_context.rs

1use crate::vm::{Register, RegisterValue};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5
6// ============================================================================
7// Type-safe field access traits and macros
8// ============================================================================
9
10/// Trait that describes how to access a field on a struct (NEW ENHANCED API)
11/// 
12/// This trait enables direct struct field access without the need for field_accessor! macros.
13/// Use the `impl_field_descriptors!` macro to automatically implement for all fields.
14/// 
15/// # Example
16/// ```ignore
17/// struct TradingMetrics {
18///     total_volume: u64,
19///     trade_count: u64,
20/// }
21/// 
22/// impl_field_descriptors!(TradingMetrics {
23///     total_volume: u64,
24///     trade_count: u64
25/// });
26/// 
27/// // Use struct fields directly
28/// ctx.get_field(TradingMetrics::total_volume())  // returns Option<u64>
29/// ctx.set_field(TradingMetrics::total_volume(), 1000)
30/// ctx.increment_field(TradingMetrics::trade_count(), 1)
31/// ```
32pub trait FieldDescriptor<T> {
33    /// The type of the field value
34    type Value: Serialize + for<'de> Deserialize<'de>;
35    
36    /// The path to this field (e.g., "total_volume")
37    fn path(&self) -> &'static str;
38}
39
40/// Trait for direct field references - legacy approach (still supported)
41/// 
42/// This trait enables compile-time field name extraction and type checking.
43/// Use the `field!` macro to create field references directly from struct fields.
44/// 
45/// # Example
46/// ```ignore
47/// struct TradingMetrics {
48///     total_volume: u64,
49///     trade_count: u64,
50/// }
51/// 
52/// // Use fields directly with the field! macro
53/// let volume = ctx.get(&field!(entity, total_volume));
54/// ctx.set(&field!(entity, total_volume), 1000);
55/// ctx.increment(&field!(entity, trade_count), 1);
56/// ```
57pub trait FieldRef<T> {
58    /// Get the field path (e.g., "total_volume")
59    fn path(&self) -> &'static str;
60    
61    /// Get the field value from a reference (for type inference)
62    fn get_ref<'a>(&self, _source: &'a T) -> Option<&'a T> {
63        None // Not used at runtime, only for type inference
64    }
65}
66
67/// Trait for type-safe field access without string literals (LEGACY API)
68/// 
69/// Implement this trait to create compile-time checked field accessors:
70/// ```ignore
71/// struct TotalVolume;
72/// impl FieldAccessor for TotalVolume {
73///     type Value = u64;
74///     fn path() -> &'static str { "total_volume" }
75/// }
76/// ```
77/// 
78/// Or use the `field_accessor!` macro for convenience.
79/// 
80/// **DEPRECATED**: Consider using the new `field!` macro instead for cleaner syntax.
81pub trait FieldAccessor {
82    /// The type of the field value
83    type Value: Serialize + for<'de> Deserialize<'de>;
84    
85    /// The path to this field (e.g., "total_volume" or "reserves.last_price")
86    fn path() -> &'static str;
87    
88    /// The nested path segments if needed (auto-computed from path)
89    fn segments() -> Vec<&'static str> {
90        Self::path().split('.').collect()
91    }
92}
93
94// Helper macro to join field names with dots (internal use)
95#[macro_export]
96#[doc(hidden)]
97macro_rules! __field_path {
98    ($first:ident) => {
99        stringify!($first)
100    };
101    ($first:ident, $($rest:ident),+) => {
102        concat!(stringify!($first), ".", $crate::__field_path!($($rest),+))
103    };
104}
105
106/// Macro to implement field descriptors for struct fields
107/// 
108/// This macro generates FieldDescriptor implementations and static methods for each field,
109/// allowing direct struct field access without the need for separate accessor types.
110/// 
111/// # Example
112/// ```ignore
113/// struct TradingMetrics {
114///     total_volume: u64,
115///     trade_count: u64,
116/// }
117/// 
118/// impl_field_descriptors!(TradingMetrics {
119///     total_volume: u64,
120///     trade_count: u64
121/// });
122/// 
123/// // Now you can use:
124/// ctx.get_field(TradingMetrics::total_volume())
125/// ctx.set_field(TradingMetrics::total_volume(), 1000)
126/// ```
127#[macro_export]
128macro_rules! impl_field_descriptors {
129    ($struct_name:ident { $( $field_name:ident : $field_type:ty ),* $(,)? }) => {
130        impl $struct_name {
131            $(
132                /// Returns a field descriptor for this field
133                pub fn $field_name() -> impl $crate::metrics_context::FieldDescriptor<$struct_name, Value = $field_type> {
134                    struct FieldDescriptorImpl;
135                    
136                    impl $crate::metrics_context::FieldDescriptor<$struct_name> for FieldDescriptorImpl {
137                        type Value = $field_type;
138                        
139                        fn path(&self) -> &'static str {
140                            stringify!($field_name)
141                        }
142                    }
143                    
144                    FieldDescriptorImpl
145                }
146            )*
147        }
148    };
149}
150
151/// Creates a field reference for direct struct field access (LEGACY API)
152/// 
153/// This macro captures the field name at compile time and creates a zero-cost
154/// field reference that can be used with MetricsContext methods.
155/// 
156/// # Examples
157/// 
158/// ```ignore
159/// struct TradingMetrics {
160///     total_volume: u64,
161///     trade_count: u64,
162/// }
163/// 
164/// let entity = TradingMetrics { total_volume: 0, trade_count: 0 };
165/// 
166/// // Create field references
167/// let volume_field = field!(entity, total_volume);
168/// let count_field = field!(entity, trade_count);
169/// 
170/// // Use with MetricsContext
171/// ctx.get_ref(&volume_field)           // Option<u64>
172/// ctx.set_ref(&count_field, 100)       // Set trade_count to 100
173/// ctx.increment_ref(&count_field, 1)   // Increment by 1
174/// 
175/// // Nested fields also work
176/// let price_field = field!(entity, reserves.last_price);
177/// ctx.set_ref(&price_field, 123.45);
178/// ```
179/// 
180/// # Advantages over field_accessor!
181/// - No need to define separate accessor structs
182/// - Field names are validated at compile time
183/// - Type inference works automatically from struct definition
184/// - Less boilerplate code
185#[macro_export]
186macro_rules! field {
187    // Simple field (no dots)
188    ($struct_expr:expr, $field:ident) => {{
189        // Create a zero-sized type that captures the field name
190        struct __FieldRef;
191        
192        impl<T> $crate::metrics_context::FieldRef<T> for __FieldRef {
193            fn path(&self) -> &'static str {
194                stringify!($field)
195            }
196        }
197        
198        // Return the field reference
199        __FieldRef
200    }};
201    
202    // Nested fields with dot notation
203    ($struct_expr:expr, $($field:ident).+) => {{
204        struct __FieldRef;
205        
206        impl<T> $crate::metrics_context::FieldRef<T> for __FieldRef {
207            fn path(&self) -> &'static str {
208                $crate::__field_path!($($field),+)
209            }
210        }
211        
212        __FieldRef
213    }};
214}
215
216/// Macro to define type-safe field accessors (LEGACY API)
217/// 
218/// # Examples
219/// 
220/// ```ignore
221/// // Simple field accessor
222/// field_accessor!(TotalVolume, u64, "total_volume");
223/// 
224/// // Nested field accessor
225/// field_accessor!(LastPrice, f64, "reserves.last_price");
226/// 
227/// // Usage with MetricsContext
228/// ctx.get_field(TotalVolume)  // returns Option<u64>
229/// ctx.set_field(TotalVolume, 1000)
230/// ```
231/// 
232/// **DEPRECATED**: Consider using the new `field!` macro instead:
233/// ```ignore
234/// ctx.get(&field!(entity, total_volume))  // Cleaner, no accessor struct needed
235/// ctx.set(&field!(entity, total_volume), 1000)
236/// ```
237#[macro_export]
238macro_rules! field_accessor {
239    ($name:ident, $type:ty, $path:expr) => {
240        pub struct $name;
241        
242        impl $crate::metrics_context::FieldAccessor for $name {
243            type Value = $type;
244            
245            fn path() -> &'static str {
246                $path
247            }
248        }
249    };
250}
251
252/// Re-export CompiledPath from vm module for public API
253pub use crate::vm::CompiledPath;
254
255/// MetricsContext provides an imperative API for complex aggregation logic
256/// in instruction hooks generated by declarative macros.
257/// 
258/// **Note:** You don't write instruction hooks directly. Instead, use declarative macros:
259/// - `#[aggregate]` for aggregations (Sum, Count, Min, Max, etc.)
260/// - `#[track_from]` for field tracking
261/// - `#[register_pda]` for PDA mappings
262/// 
263/// These macros generate instruction hooks internally that use MetricsContext.
264/// 
265/// It wraps VM registers to provide type-safe access to:
266/// - Instruction data (accounts, args)
267/// - Entity state (current field values)
268/// - Context metadata (slot, signature, timestamp)
269/// 
270/// # Enhanced Field Descriptor API (NEW - Recommended)
271/// ```ignore
272/// // Define your entity struct with declarative macros
273/// struct TradingMetrics {
274///     #[aggregate(
275///         from = [Buy, Sell],
276///         field = data::amount,
277///         strategy = Sum,
278///         lookup_by = accounts::mint
279///     )]
280///     total_volume: u64,
281///     
282///     #[aggregate(
283///         from = [Buy, Sell],
284///         strategy = Count,
285///         lookup_by = accounts::mint
286///     )]
287///     trade_count: u64,
288/// }
289/// 
290/// // The macro generates hooks that use MetricsContext internally
291/// // to implement the aggregation logic.
292/// ```
293/// 
294/// # Direct MetricsContext Usage (Internal/Advanced)
295/// 
296/// If you're implementing custom runtime logic or extending the macro system,
297/// you can use MetricsContext directly with field descriptors:
298/// 
299/// ```ignore
300/// // Generate field descriptors - replaces field_accessor! macro
301/// impl_field_descriptors!(TradingMetrics {
302///     total_volume: u64,
303///     trade_count: u64
304/// });
305/// 
306/// // In generated hook function (example - you don't write this)
307/// fn generated_update_metrics(ctx: &mut MetricsContext) {
308///     let volume = ctx.get_field(TradingMetrics::total_volume());
309///     ctx.set_field(TradingMetrics::total_volume(), 1000);
310///     ctx.increment_field(TradingMetrics::trade_count(), 1);
311/// }
312/// ```
313/// 
314/// # Field Accessor API (Legacy - Still Supported)
315/// ```ignore
316/// // Define field accessors once
317/// field_accessor!(TotalVolume, u64, "total_volume");
318/// field_accessor!(TradeCount, u64, "trade_count");
319/// 
320/// // Use with compile-time type checking
321/// ctx.get_field_legacy(TotalVolume)       // returns Option<u64>
322/// ctx.set_field_legacy(TotalVolume, 100)  // type-checked at compile time
323/// ctx.increment_field_legacy(TradeCount, 1)
324/// ```
325/// 
326/// # String-based API (Legacy - Use for dynamic field access only)
327/// ```ignore
328/// ctx.get::<u64>("total_volume")  // String-based, runtime errors possible
329/// ctx.set("total_volume", 100)
330/// ctx.increment("trade_count", 1)
331/// ```
332pub struct MetricsContext<'a> {
333    /// Register holding the current entity state
334    state_reg: Register,
335    /// All VM registers (mutable access for updates)
336    registers: &'a mut Vec<RegisterValue>,
337    /// Compiled field paths for efficient access
338    #[allow(dead_code)]
339    compiled_paths: &'a HashMap<String, CompiledPath>,
340    /// Blockchain slot number
341    slot: Option<u64>,
342    /// Transaction signature
343    signature: Option<String>,
344    /// Unix timestamp (milliseconds)
345    timestamp: i64,
346}
347
348impl<'a> MetricsContext<'a> {
349    /// Create a new MetricsContext wrapping VM state
350    pub fn new(
351        state_reg: Register,
352        registers: &'a mut Vec<RegisterValue>,
353        compiled_paths: &'a HashMap<String, CompiledPath>,
354        slot: Option<u64>,
355        signature: Option<String>,
356        timestamp: i64,
357    ) -> Self {
358        Self {
359            state_reg,
360            registers,
361            compiled_paths,
362            slot,
363            signature,
364            timestamp,
365        }
366    }
367
368    // ========================================================================
369    // Read instruction data
370    // ========================================================================
371
372    /// Get an account address from the instruction by name
373    /// Example: `ctx.account("user")` returns the user account address
374    pub fn account(&self, name: &str) -> Option<String> {
375        // Accounts are stored in registers, typically in a source register
376        // For now, we'll return None - full implementation requires access to source register
377        // This is a placeholder for the actual implementation
378        let _ = name;
379        None
380    }
381
382    /// Get a typed field from instruction data
383    /// Example: `ctx.data::<u64>("amount")` returns the amount field
384    pub fn data<T: for<'de> Deserialize<'de>>(&self, field: &str) -> Option<T> {
385        // Data fields are accessed via compiled paths
386        // For now, placeholder - full implementation requires source register access
387        let _ = field;
388        None
389    }
390
391    // ========================================================================
392    // Read current entity state
393    // ========================================================================
394
395    /// Get a typed value from the current entity state (string-based API)
396    /// Example: `ctx.get::<u64>("total_volume")` returns the current total_volume value
397    pub fn get<T: for<'de> Deserialize<'de>>(&self, field_path: &str) -> Option<T> {
398        let state = self.registers.get(self.state_reg)?;
399        
400        // Navigate the field path
401        let segments: Vec<&str> = field_path.split('.').collect();
402        let mut current = state;
403        
404        for segment in segments {
405            current = current.get(segment)?;
406        }
407        
408        // Deserialize the value
409        serde_json::from_value(current.clone()).ok()
410    }
411
412    /// Get a typed value using a field reference (NEW RECOMMENDED API)
413    /// Example: `ctx.get_ref(&field!(entity, total_volume))` returns Option<u64>
414    /// 
415    /// This provides compile-time field name validation and type inference.
416    pub fn get_ref<T, F>(&self, field_ref: &F) -> Option<T>
417    where
418        T: for<'de> Deserialize<'de>,
419        F: FieldRef<T>,
420    {
421        self.get(field_ref.path())
422    }
423
424    /// Type-safe field getter using struct field descriptors (NEW ENHANCED API)
425    /// Example: `ctx.get_field(TradingMetrics::total_volume())` returns `Option<u64>`
426    /// 
427    /// This provides compile-time type checking with direct struct field access.
428    pub fn get_field<T, F>(&self, field: F) -> Option<F::Value> 
429    where
430        F: FieldDescriptor<T>
431    {
432        self.get(field.path())
433    }
434
435    /// Type-safe field getter using legacy FieldAccessor trait
436    /// Example: `ctx.get_field_legacy(TotalVolume)` returns `Option<u64>`
437    /// 
438    /// This eliminates string literals and provides compile-time type checking.
439    pub fn get_field_legacy<F: FieldAccessor>(&self, _field: F) -> Option<F::Value> {
440        self.get(F::path())
441    }
442
443    // ========================================================================
444    // Update entity state
445    // ========================================================================
446
447    /// Set a field value in the entity state (string-based API)
448    /// Example: `ctx.set("last_trade_timestamp", ctx.timestamp())`
449    pub fn set<T: Serialize>(&mut self, field: &str, value: T) {
450        if let Ok(json_value) = serde_json::to_value(value) {
451            self.set_field_value(field, json_value);
452        }
453    }
454
455    /// Set a field value using a field reference (NEW RECOMMENDED API)
456    /// Example: `ctx.set_ref(&field!(entity, total_volume), 1000)`
457    /// 
458    /// This provides compile-time field name validation and type checking.
459    pub fn set_ref<T, F>(&mut self, field_ref: &F, value: T)
460    where
461        T: Serialize,
462        F: FieldRef<T>,
463    {
464        if let Ok(json_value) = serde_json::to_value(value) {
465            self.set_field_value(field_ref.path(), json_value);
466        }
467    }
468
469    /// Type-safe field setter using struct field descriptors (NEW ENHANCED API)
470    /// Example: `ctx.set_field(TradingMetrics::total_volume(), 1000)` sets total_volume to 1000
471    /// 
472    /// This provides compile-time type checking with direct struct field access.
473    pub fn set_field<T, F>(&mut self, field: F, value: F::Value)
474    where
475        F: FieldDescriptor<T>
476    {
477        self.set(field.path(), value)
478    }
479
480    /// Type-safe field setter using legacy FieldAccessor trait
481    /// Example: `ctx.set_field_legacy(TotalVolume, 1000)` sets total_volume to 1000
482    /// 
483    /// This eliminates string literals and provides compile-time type checking.
484    pub fn set_field_legacy<F: FieldAccessor>(&mut self, _field: F, value: F::Value) {
485        self.set(F::path(), value)
486    }
487
488    /// Increment a numeric field by a given amount (string-based API)
489    /// Example: `ctx.increment("whale_trade_count", 1)`
490    pub fn increment(&mut self, field: &str, amount: u64) {
491        if let Some(current) = self.get::<u64>(field) {
492            self.set(field, current + amount);
493        } else {
494            self.set(field, amount);
495        }
496    }
497
498    /// Increment a numeric field using a field reference (NEW RECOMMENDED API)
499    /// Example: `ctx.increment_ref(&field!(entity, trade_count), 1)`
500    /// 
501    /// This provides compile-time field name validation. Works with u64 fields.
502    pub fn increment_ref<F>(&mut self, field_ref: &F, amount: u64)
503    where
504        F: FieldRef<u64>,
505    {
506        let path = field_ref.path();
507        if let Some(current) = self.get::<u64>(path) {
508            self.set(path, current + amount);
509        } else {
510            self.set(path, amount);
511        }
512    }
513
514    /// Type-safe increment using struct field descriptors (NEW ENHANCED API)
515    /// Example: `ctx.increment_field(TradingMetrics::trade_count(), 1)`
516    /// 
517    /// Works with u64 fields and provides compile-time type checking.
518    pub fn increment_field<T, F>(&mut self, field: F, amount: u64)
519    where
520        F: FieldDescriptor<T, Value = u64>
521    {
522        self.increment(field.path(), amount)
523    }
524
525    /// Type-safe increment using legacy FieldAccessor trait
526    /// Example: `ctx.increment_field_legacy(TradeCount, 1)`
527    /// 
528    /// Works with any numeric type that can convert to/from u64.
529    pub fn increment_field_legacy<F: FieldAccessor>(&mut self, _field: F, amount: u64)
530    where
531        F::Value: Into<u64> + From<u64>
532    {
533        self.increment(F::path(), amount)
534    }
535
536    /// Add a value to a numeric accumulator (alias for increment - string-based API)
537    /// Example: `ctx.sum("total_fees", fee_amount)`
538    pub fn sum(&mut self, field: &str, value: u64) {
539        self.increment(field, value);
540    }
541
542    /// Add a value to a numeric accumulator using a field reference (NEW RECOMMENDED API)
543    /// Example: `ctx.sum_ref(&field!(entity, total_fees), fee_amount)`
544    /// 
545    /// This is an alias for `increment_ref()` that may be clearer for accumulation use cases.
546    pub fn sum_ref<F>(&mut self, field_ref: &F, value: u64)
547    where
548        F: FieldRef<u64>,
549    {
550        self.increment_ref(field_ref, value)
551    }
552
553    /// Type-safe sum using struct field descriptors (NEW ENHANCED API)
554    /// Example: `ctx.sum_field(TradingMetrics::total_fees(), fee_amount)`
555    /// 
556    /// Works with u64 fields and provides compile-time type checking.
557    pub fn sum_field<T, F>(&mut self, field: F, value: u64)
558    where
559        F: FieldDescriptor<T, Value = u64>
560    {
561        self.sum(field.path(), value)
562    }
563
564    /// Type-safe sum using legacy FieldAccessor trait
565    /// Example: `ctx.sum_field_legacy(TotalFees, fee_amount)`
566    /// 
567    /// Works with any numeric type that can convert to/from u64.
568    pub fn sum_field_legacy<F: FieldAccessor>(&mut self, _field: F, value: u64)
569    where
570        F::Value: Into<u64> + From<u64>
571    {
572        self.sum(F::path(), value)
573    }
574
575    /// Add a value to a unique set and update the count field
576    /// Example: `ctx.add_unique("unique_traders", user_address)`
577    pub fn add_unique(&mut self, field: &str, value: String) {
578        // Get the internal set field name (conventionally field + "_set")
579        let set_field = format!("{}_set", field);
580        
581        // Get existing set or create new one
582        let mut set: HashSet<String> = self.get::<HashSet<String>>(&set_field).unwrap_or_default();
583        
584        // Add the value
585        set.insert(value);
586        
587        // Update the set and count
588        let count = set.len() as u64;
589        self.set(&set_field, set);
590        self.set(field, count);
591    }
592
593    // ========================================================================
594    // Access context metadata
595    // ========================================================================
596
597    /// Get the current timestamp in milliseconds
598    pub fn timestamp(&self) -> i64 {
599        self.timestamp
600    }
601
602    /// Get the blockchain slot number
603    pub fn slot(&self) -> u64 {
604        self.slot.unwrap_or(0)
605    }
606
607    /// Get the transaction signature
608    pub fn signature(&self) -> &str {
609        self.signature.as_deref().unwrap_or("")
610    }
611
612    // ========================================================================
613    // Internal helpers
614    // ========================================================================
615
616    fn set_field_value(&mut self, field_path: &str, value: Value) {
617        if let Some(state) = self.registers.get_mut(self.state_reg) {
618            if !state.is_object() {
619                *state = Value::Object(serde_json::Map::new());
620            }
621            
622            let segments: Vec<&str> = field_path.split('.').collect();
623            let mut current = state;
624            
625            // Navigate to the parent object
626            for segment in &segments[..segments.len() - 1] {
627                if current.get(segment).is_none() {
628                    current[segment] = Value::Object(serde_json::Map::new());
629                }
630                current = current.get_mut(segment).unwrap();
631            }
632            
633            // Set the final field
634            if let Some(last_segment) = segments.last() {
635                current[*last_segment] = value;
636            }
637        }
638    }
639}
640
641// Re-export HashSet for use in add_unique
642use std::collections::HashSet;
643
644#[cfg(test)]
645mod tests {
646    use super::*;
647    use serde_json::json;
648
649    #[test]
650    fn test_get_field() {
651        let mut registers = vec![
652            json!({
653                "total_volume": 1000,
654                "metrics": {
655                    "count": 5
656                }
657            })
658        ];
659        
660        let compiled_paths = HashMap::new();
661        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
662        
663        assert_eq!(ctx.get::<u64>("total_volume"), Some(1000));
664        assert_eq!(ctx.get::<u64>("metrics.count"), Some(5));
665    }
666
667    #[test]
668    fn test_set_field() {
669        let mut registers = vec![json!({})];
670        let compiled_paths = HashMap::new();
671        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
672        
673        ctx.set("total_volume", 2000u64);
674        assert_eq!(ctx.get::<u64>("total_volume"), Some(2000));
675    }
676
677    #[test]
678    fn test_increment() {
679        let mut registers = vec![json!({"count": 10})];
680        let compiled_paths = HashMap::new();
681        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
682        
683        ctx.increment("count", 5);
684        assert_eq!(ctx.get::<u64>("count"), Some(15));
685        
686        // Test incrementing non-existent field
687        ctx.increment("new_count", 3);
688        assert_eq!(ctx.get::<u64>("new_count"), Some(3));
689    }
690
691    #[test]
692    fn test_context_metadata() {
693        let mut registers = vec![json!({})];
694        let compiled_paths = HashMap::new();
695        let ctx = MetricsContext::new(
696            0,
697            &mut registers,
698            &compiled_paths,
699            Some(12345),
700            Some("abc123".to_string()),
701            1000000,
702        );
703        
704        assert_eq!(ctx.slot(), 12345);
705        assert_eq!(ctx.signature(), "abc123");
706        assert_eq!(ctx.timestamp(), 1000000);
707    }
708
709    #[test]
710    fn test_enhanced_field_descriptor_api() {
711        // Define a struct representing our entity
712        struct TradingMetrics {
713            total_volume: u64,
714            trade_count: u64,
715        }
716        
717        // Generate field descriptors for the struct
718        impl_field_descriptors!(TradingMetrics {
719            total_volume: u64,
720            trade_count: u64
721        });
722        
723        let mut registers = vec![json!({
724            "total_volume": 1000,
725            "trade_count": 5
726        })];
727        
728        let compiled_paths = HashMap::new();
729        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
730        
731        // Test enhanced API - direct struct field access
732        assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(1000));
733        assert_eq!(ctx.get_field(TradingMetrics::trade_count()), Some(5));
734        
735        // Test type-safe set
736        ctx.set_field(TradingMetrics::total_volume(), 2000);
737        assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(2000));
738        
739        // Test type-safe increment
740        ctx.increment_field(TradingMetrics::trade_count(), 3);
741        assert_eq!(ctx.get_field(TradingMetrics::trade_count()), Some(8));
742        
743        // Test type-safe sum
744        ctx.sum_field(TradingMetrics::total_volume(), 500);
745        assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(2500));
746    }
747
748    #[test]
749    fn test_legacy_field_accessor_api() {
750        // Define field accessors using the legacy macro
751        field_accessor!(TotalVolume, u64, "total_volume");
752        field_accessor!(TradeCount, u64, "trade_count");
753        field_accessor!(LastPrice, u64, "reserves.last_price");
754        
755        let mut registers = vec![json!({
756            "total_volume": 1000,
757            "trade_count": 5,
758            "reserves": {
759                "last_price": 250
760            }
761        })];
762        
763        let compiled_paths = HashMap::new();
764        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
765        
766        // Test legacy type-safe get
767        assert_eq!(ctx.get_field_legacy(TotalVolume), Some(1000));
768        assert_eq!(ctx.get_field_legacy(TradeCount), Some(5));
769        assert_eq!(ctx.get_field_legacy(LastPrice), Some(250));
770        
771        // Test legacy type-safe set
772        ctx.set_field_legacy(TotalVolume, 2000);
773        assert_eq!(ctx.get_field_legacy(TotalVolume), Some(2000));
774        
775        // Test legacy type-safe increment
776        ctx.increment_field_legacy(TradeCount, 3);
777        assert_eq!(ctx.get_field_legacy(TradeCount), Some(8));
778        
779        // Test legacy type-safe sum
780        ctx.sum_field_legacy(TotalVolume, 500);
781        assert_eq!(ctx.get_field_legacy(TotalVolume), Some(2500));
782        
783        // Test nested path
784        ctx.set_field_legacy(LastPrice, 300);
785        assert_eq!(ctx.get_field_legacy(LastPrice), Some(300));
786    }
787
788    #[test]
789    fn test_enhanced_api_with_different_types() {
790        // Test with different field types
791        struct PriceMetrics {
792            average_price: f64,
793            volume: u64,
794        }
795        
796        impl_field_descriptors!(PriceMetrics {
797            average_price: f64,
798            volume: u64
799        });
800        
801        let mut registers = vec![json!({})];
802        let compiled_paths = HashMap::new();
803        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
804        
805        ctx.set_field(PriceMetrics::average_price(), 123.45);
806        assert_eq!(ctx.get_field(PriceMetrics::average_price()), Some(123.45));
807        
808        ctx.set_field(PriceMetrics::volume(), 1000);
809        assert_eq!(ctx.get_field(PriceMetrics::volume()), Some(1000));
810    }
811
812    #[test]
813    fn test_legacy_api_with_different_types() {
814        // Test legacy API with f64 type
815        field_accessor!(AveragePrice, f64, "average_price");
816        
817        let mut registers = vec![json!({})];
818        let compiled_paths = HashMap::new();
819        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
820        
821        ctx.set_field_legacy(AveragePrice, 123.45);
822        assert_eq!(ctx.get_field_legacy(AveragePrice), Some(123.45));
823    }
824
825    #[test]
826    fn test_field_ref_api() {
827        // Define a struct to represent our entity
828        struct TradingMetrics {
829            total_volume: u64,
830            trade_count: u64,
831            last_price: f64,
832        }
833        
834        // Create an instance (the actual field values don't matter, we just need the struct for field! macro)
835        let entity = TradingMetrics {
836            total_volume: 0,
837            trade_count: 0,
838            last_price: 0.0,
839        };
840        
841        let mut registers = vec![json!({
842            "total_volume": 1000,
843            "trade_count": 5,
844            "last_price": 250.5
845        })];
846        
847        let compiled_paths = HashMap::new();
848        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
849        
850        // ========================================================================
851        // Test new field reference API - cleaner than field_accessor!
852        // ========================================================================
853        
854        // Test get_ref
855        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, total_volume)), Some(1000));
856        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, trade_count)), Some(5));
857        assert_eq!(ctx.get_ref::<f64, _>(&field!(entity, last_price)), Some(250.5));
858        
859        // Test set_ref
860        ctx.set_ref(&field!(entity, total_volume), 2000u64);
861        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, total_volume)), Some(2000));
862        
863        ctx.set_ref(&field!(entity, last_price), 300.75);
864        assert_eq!(ctx.get_ref::<f64, _>(&field!(entity, last_price)), Some(300.75));
865        
866        // Test increment_ref
867        ctx.increment_ref(&field!(entity, trade_count), 3);
868        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, trade_count)), Some(8));
869        
870        // Test sum_ref
871        ctx.sum_ref(&field!(entity, total_volume), 500);
872        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, total_volume)), Some(2500));
873    }
874
875    #[test]
876    fn test_field_ref_with_nested_struct() {
877        // Test with nested fields
878        struct Metrics {
879            reserves: Reserves,
880        }
881        
882        struct Reserves {
883            last_price: f64,
884        }
885        
886        let entity = Metrics {
887            reserves: Reserves { last_price: 0.0 },
888        };
889        
890        let mut registers = vec![json!({
891            "reserves": {
892                "last_price": 100.5
893            }
894        })];
895        
896        let compiled_paths = HashMap::new();
897        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
898        
899        // Test nested field access with dot notation
900        assert_eq!(ctx.get_ref::<f64, _>(&field!(entity, reserves.last_price)), Some(100.5));
901        
902        ctx.set_ref(&field!(entity, reserves.last_price), 200.75);
903        assert_eq!(ctx.get_ref::<f64, _>(&field!(entity, reserves.last_price)), Some(200.75));
904    }
905
906    #[test]
907    fn test_enhanced_api_field_initialization() {
908        // Test that increment/sum works when field doesn't exist yet
909        struct WhaleMetrics {
910            whale_trade_count: u64,
911            total_whale_volume: u64,
912        }
913        
914        impl_field_descriptors!(WhaleMetrics {
915            whale_trade_count: u64,
916            total_whale_volume: u64
917        });
918        
919        let mut registers = vec![json!({})];
920        let compiled_paths = HashMap::new();
921        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
922        
923        // Increment on non-existent field should initialize to the amount
924        ctx.increment_field(WhaleMetrics::whale_trade_count(), 1);
925        assert_eq!(ctx.get_field(WhaleMetrics::whale_trade_count()), Some(1));
926        
927        // Subsequent increment should add to existing value
928        ctx.increment_field(WhaleMetrics::whale_trade_count(), 2);
929        assert_eq!(ctx.get_field(WhaleMetrics::whale_trade_count()), Some(3));
930        
931        // Test sum field initialization
932        ctx.sum_field(WhaleMetrics::total_whale_volume(), 5000);
933        assert_eq!(ctx.get_field(WhaleMetrics::total_whale_volume()), Some(5000));
934        
935        ctx.sum_field(WhaleMetrics::total_whale_volume(), 3000);
936        assert_eq!(ctx.get_field(WhaleMetrics::total_whale_volume()), Some(8000));
937    }
938
939    #[test]
940    fn test_legacy_field_ref_initialization() {
941        // Test legacy field ref API that increment_ref works when field doesn't exist yet
942        struct Metrics {
943            whale_trade_count: u64,
944        }
945        
946        let entity = Metrics { whale_trade_count: 0 };
947        
948        let mut registers = vec![json!({})];
949        let compiled_paths = HashMap::new();
950        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
951        
952        // Increment on non-existent field should initialize to the amount
953        ctx.increment_ref(&field!(entity, whale_trade_count), 1);
954        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, whale_trade_count)), Some(1));
955        
956        // Subsequent increment should add to existing value
957        ctx.increment_ref(&field!(entity, whale_trade_count), 2);
958        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, whale_trade_count)), Some(3));
959    }
960
961    #[test]
962    fn test_backward_compatibility() {
963        // Verify that old string-based API still works
964        let mut registers = vec![json!({
965            "volume": 100
966        })];
967        
968        let compiled_paths = HashMap::new();
969        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
970        
971        // Old string API should still work
972        assert_eq!(ctx.get::<u64>("volume"), Some(100));
973        ctx.set("volume", 200u64);
974        assert_eq!(ctx.get::<u64>("volume"), Some(200));
975        ctx.increment("volume", 50);
976        assert_eq!(ctx.get::<u64>("volume"), Some(250));
977    }
978}