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![json!({
652            "total_volume": 1000,
653            "metrics": {
654                "count": 5
655            }
656        })];
657
658        let compiled_paths = HashMap::new();
659        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
660
661        assert_eq!(ctx.get::<u64>("total_volume"), Some(1000));
662        assert_eq!(ctx.get::<u64>("metrics.count"), Some(5));
663    }
664
665    #[test]
666    fn test_set_field() {
667        let mut registers = vec![json!({})];
668        let compiled_paths = HashMap::new();
669        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
670
671        ctx.set("total_volume", 2000u64);
672        assert_eq!(ctx.get::<u64>("total_volume"), Some(2000));
673    }
674
675    #[test]
676    fn test_increment() {
677        let mut registers = vec![json!({"count": 10})];
678        let compiled_paths = HashMap::new();
679        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
680
681        ctx.increment("count", 5);
682        assert_eq!(ctx.get::<u64>("count"), Some(15));
683
684        // Test incrementing non-existent field
685        ctx.increment("new_count", 3);
686        assert_eq!(ctx.get::<u64>("new_count"), Some(3));
687    }
688
689    #[test]
690    fn test_context_metadata() {
691        let mut registers = vec![json!({})];
692        let compiled_paths = HashMap::new();
693        let ctx = MetricsContext::new(
694            0,
695            &mut registers,
696            &compiled_paths,
697            Some(12345),
698            Some("abc123".to_string()),
699            1000000,
700        );
701
702        assert_eq!(ctx.slot(), 12345);
703        assert_eq!(ctx.signature(), "abc123");
704        assert_eq!(ctx.timestamp(), 1000000);
705    }
706
707    #[test]
708    fn test_enhanced_field_descriptor_api() {
709        // Define a struct representing our entity
710        struct TradingMetrics {
711            total_volume: u64,
712            trade_count: u64,
713        }
714
715        // Generate field descriptors for the struct
716        impl_field_descriptors!(TradingMetrics {
717            total_volume: u64,
718            trade_count: u64
719        });
720
721        let mut registers = vec![json!({
722            "total_volume": 1000,
723            "trade_count": 5
724        })];
725
726        let compiled_paths = HashMap::new();
727        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
728
729        // Test enhanced API - direct struct field access
730        assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(1000));
731        assert_eq!(ctx.get_field(TradingMetrics::trade_count()), Some(5));
732
733        // Test type-safe set
734        ctx.set_field(TradingMetrics::total_volume(), 2000);
735        assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(2000));
736
737        // Test type-safe increment
738        ctx.increment_field(TradingMetrics::trade_count(), 3);
739        assert_eq!(ctx.get_field(TradingMetrics::trade_count()), Some(8));
740
741        // Test type-safe sum
742        ctx.sum_field(TradingMetrics::total_volume(), 500);
743        assert_eq!(ctx.get_field(TradingMetrics::total_volume()), Some(2500));
744    }
745
746    #[test]
747    fn test_legacy_field_accessor_api() {
748        // Define field accessors using the legacy macro
749        field_accessor!(TotalVolume, u64, "total_volume");
750        field_accessor!(TradeCount, u64, "trade_count");
751        field_accessor!(LastPrice, u64, "reserves.last_price");
752
753        let mut registers = vec![json!({
754            "total_volume": 1000,
755            "trade_count": 5,
756            "reserves": {
757                "last_price": 250
758            }
759        })];
760
761        let compiled_paths = HashMap::new();
762        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
763
764        // Test legacy type-safe get
765        assert_eq!(ctx.get_field_legacy(TotalVolume), Some(1000));
766        assert_eq!(ctx.get_field_legacy(TradeCount), Some(5));
767        assert_eq!(ctx.get_field_legacy(LastPrice), Some(250));
768
769        // Test legacy type-safe set
770        ctx.set_field_legacy(TotalVolume, 2000);
771        assert_eq!(ctx.get_field_legacy(TotalVolume), Some(2000));
772
773        // Test legacy type-safe increment
774        ctx.increment_field_legacy(TradeCount, 3);
775        assert_eq!(ctx.get_field_legacy(TradeCount), Some(8));
776
777        // Test legacy type-safe sum
778        ctx.sum_field_legacy(TotalVolume, 500);
779        assert_eq!(ctx.get_field_legacy(TotalVolume), Some(2500));
780
781        // Test nested path
782        ctx.set_field_legacy(LastPrice, 300);
783        assert_eq!(ctx.get_field_legacy(LastPrice), Some(300));
784    }
785
786    #[test]
787    fn test_enhanced_api_with_different_types() {
788        // Test with different field types
789        struct PriceMetrics {
790            average_price: f64,
791            volume: u64,
792        }
793
794        impl_field_descriptors!(PriceMetrics {
795            average_price: f64,
796            volume: u64
797        });
798
799        let mut registers = vec![json!({})];
800        let compiled_paths = HashMap::new();
801        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
802
803        ctx.set_field(PriceMetrics::average_price(), 123.45);
804        assert_eq!(ctx.get_field(PriceMetrics::average_price()), Some(123.45));
805
806        ctx.set_field(PriceMetrics::volume(), 1000);
807        assert_eq!(ctx.get_field(PriceMetrics::volume()), Some(1000));
808    }
809
810    #[test]
811    fn test_legacy_api_with_different_types() {
812        // Test legacy API with f64 type
813        field_accessor!(AveragePrice, f64, "average_price");
814
815        let mut registers = vec![json!({})];
816        let compiled_paths = HashMap::new();
817        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
818
819        ctx.set_field_legacy(AveragePrice, 123.45);
820        assert_eq!(ctx.get_field_legacy(AveragePrice), Some(123.45));
821    }
822
823    #[test]
824    fn test_field_ref_api() {
825        // Define a struct to represent our entity
826        struct TradingMetrics {
827            total_volume: u64,
828            trade_count: u64,
829            last_price: f64,
830        }
831
832        // Create an instance (the actual field values don't matter, we just need the struct for field! macro)
833        let entity = TradingMetrics {
834            total_volume: 0,
835            trade_count: 0,
836            last_price: 0.0,
837        };
838
839        let mut registers = vec![json!({
840            "total_volume": 1000,
841            "trade_count": 5,
842            "last_price": 250.5
843        })];
844
845        let compiled_paths = HashMap::new();
846        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
847
848        // ========================================================================
849        // Test new field reference API - cleaner than field_accessor!
850        // ========================================================================
851
852        // Test get_ref
853        assert_eq!(
854            ctx.get_ref::<u64, _>(&field!(entity, total_volume)),
855            Some(1000)
856        );
857        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, trade_count)), Some(5));
858        assert_eq!(
859            ctx.get_ref::<f64, _>(&field!(entity, last_price)),
860            Some(250.5)
861        );
862
863        // Test set_ref
864        ctx.set_ref(&field!(entity, total_volume), 2000u64);
865        assert_eq!(
866            ctx.get_ref::<u64, _>(&field!(entity, total_volume)),
867            Some(2000)
868        );
869
870        ctx.set_ref(&field!(entity, last_price), 300.75);
871        assert_eq!(
872            ctx.get_ref::<f64, _>(&field!(entity, last_price)),
873            Some(300.75)
874        );
875
876        // Test increment_ref
877        ctx.increment_ref(&field!(entity, trade_count), 3);
878        assert_eq!(ctx.get_ref::<u64, _>(&field!(entity, trade_count)), Some(8));
879
880        // Test sum_ref
881        ctx.sum_ref(&field!(entity, total_volume), 500);
882        assert_eq!(
883            ctx.get_ref::<u64, _>(&field!(entity, total_volume)),
884            Some(2500)
885        );
886    }
887
888    #[test]
889    fn test_field_ref_with_nested_struct() {
890        // Test with nested fields
891        struct Metrics {
892            reserves: Reserves,
893        }
894
895        struct Reserves {
896            last_price: f64,
897        }
898
899        let entity = Metrics {
900            reserves: Reserves { last_price: 0.0 },
901        };
902
903        let mut registers = vec![json!({
904            "reserves": {
905                "last_price": 100.5
906            }
907        })];
908
909        let compiled_paths = HashMap::new();
910        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
911
912        // Test nested field access with dot notation
913        assert_eq!(
914            ctx.get_ref::<f64, _>(&field!(entity, reserves.last_price)),
915            Some(100.5)
916        );
917
918        ctx.set_ref(&field!(entity, reserves.last_price), 200.75);
919        assert_eq!(
920            ctx.get_ref::<f64, _>(&field!(entity, reserves.last_price)),
921            Some(200.75)
922        );
923    }
924
925    #[test]
926    fn test_enhanced_api_field_initialization() {
927        // Test that increment/sum works when field doesn't exist yet
928        struct WhaleMetrics {
929            whale_trade_count: u64,
930            total_whale_volume: u64,
931        }
932
933        impl_field_descriptors!(WhaleMetrics {
934            whale_trade_count: u64,
935            total_whale_volume: u64
936        });
937
938        let mut registers = vec![json!({})];
939        let compiled_paths = HashMap::new();
940        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
941
942        // Increment on non-existent field should initialize to the amount
943        ctx.increment_field(WhaleMetrics::whale_trade_count(), 1);
944        assert_eq!(ctx.get_field(WhaleMetrics::whale_trade_count()), Some(1));
945
946        // Subsequent increment should add to existing value
947        ctx.increment_field(WhaleMetrics::whale_trade_count(), 2);
948        assert_eq!(ctx.get_field(WhaleMetrics::whale_trade_count()), Some(3));
949
950        // Test sum field initialization
951        ctx.sum_field(WhaleMetrics::total_whale_volume(), 5000);
952        assert_eq!(
953            ctx.get_field(WhaleMetrics::total_whale_volume()),
954            Some(5000)
955        );
956
957        ctx.sum_field(WhaleMetrics::total_whale_volume(), 3000);
958        assert_eq!(
959            ctx.get_field(WhaleMetrics::total_whale_volume()),
960            Some(8000)
961        );
962    }
963
964    #[test]
965    fn test_legacy_field_ref_initialization() {
966        // Test legacy field ref API that increment_ref works when field doesn't exist yet
967        struct Metrics {
968            whale_trade_count: u64,
969        }
970
971        let entity = Metrics {
972            whale_trade_count: 0,
973        };
974
975        let mut registers = vec![json!({})];
976        let compiled_paths = HashMap::new();
977        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
978
979        // Increment on non-existent field should initialize to the amount
980        ctx.increment_ref(&field!(entity, whale_trade_count), 1);
981        assert_eq!(
982            ctx.get_ref::<u64, _>(&field!(entity, whale_trade_count)),
983            Some(1)
984        );
985
986        // Subsequent increment should add to existing value
987        ctx.increment_ref(&field!(entity, whale_trade_count), 2);
988        assert_eq!(
989            ctx.get_ref::<u64, _>(&field!(entity, whale_trade_count)),
990            Some(3)
991        );
992    }
993
994    #[test]
995    fn test_backward_compatibility() {
996        // Verify that old string-based API still works
997        let mut registers = vec![json!({
998            "volume": 100
999        })];
1000
1001        let compiled_paths = HashMap::new();
1002        let mut ctx = MetricsContext::new(0, &mut registers, &compiled_paths, None, None, 0);
1003
1004        // Old string API should still work
1005        assert_eq!(ctx.get::<u64>("volume"), Some(100));
1006        ctx.set("volume", 200u64);
1007        assert_eq!(ctx.get::<u64>("volume"), Some(200));
1008        ctx.increment("volume", 50);
1009        assert_eq!(ctx.get::<u64>("volume"), Some(250));
1010    }
1011}