hyperstack_interpreter/
ast.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::marker::PhantomData;
4use std::collections::BTreeMap;
5
6// ============================================================================
7// IDL Snapshot Types - Embedded IDL for AST-only compilation
8// ============================================================================
9
10/// Snapshot of an Anchor IDL for embedding in the AST
11/// Contains all information needed to generate parsers and SDK types
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct IdlSnapshot {
14    /// Program name (e.g., "pump")
15    pub name: String,
16    /// Program version
17    pub version: String,
18    /// Account type definitions
19    pub accounts: Vec<IdlAccountSnapshot>,
20    /// Instruction definitions
21    pub instructions: Vec<IdlInstructionSnapshot>,
22    /// Type definitions (structs, enums)
23    #[serde(default)]
24    pub types: Vec<IdlTypeDefSnapshot>,
25    /// Event definitions
26    #[serde(default)]
27    pub events: Vec<IdlEventSnapshot>,
28    /// Error definitions
29    #[serde(default)]
30    pub errors: Vec<IdlErrorSnapshot>,
31    /// Discriminant size in bytes (1 for Steel, 8 for Anchor)
32    /// Defaults to 8 (Anchor) for backwards compatibility
33    #[serde(default = "default_discriminant_size")]
34    pub discriminant_size: usize,
35}
36
37fn default_discriminant_size() -> usize {
38    8
39}
40
41/// Account definition from IDL
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct IdlAccountSnapshot {
44    /// Account name (e.g., "BondingCurve")
45    pub name: String,
46    /// 8-byte discriminator
47    pub discriminator: Vec<u8>,
48    /// Documentation
49    #[serde(default)]
50    pub docs: Vec<String>,
51}
52
53/// Instruction definition from IDL
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct IdlInstructionSnapshot {
56    /// Instruction name (e.g., "buy", "sell", "create")
57    pub name: String,
58    /// 8-byte discriminator
59    pub discriminator: Vec<u8>,
60    /// Documentation
61    #[serde(default)]
62    pub docs: Vec<String>,
63    /// Account arguments
64    pub accounts: Vec<IdlInstructionAccountSnapshot>,
65    /// Data arguments
66    pub args: Vec<IdlFieldSnapshot>,
67}
68
69/// Account argument in an instruction
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct IdlInstructionAccountSnapshot {
72    /// Account name (e.g., "mint", "user")
73    pub name: String,
74    /// Whether this account is writable
75    #[serde(default)]
76    pub writable: bool,
77    /// Whether this account is a signer
78    #[serde(default)]
79    pub signer: bool,
80    /// Optional - if the account is optional
81    #[serde(default)]
82    pub optional: bool,
83    /// Fixed address constraint (if any)
84    #[serde(default)]
85    pub address: Option<String>,
86    /// Documentation
87    #[serde(default)]
88    pub docs: Vec<String>,
89}
90
91/// Field definition (used in instructions, accounts, types)
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct IdlFieldSnapshot {
94    /// Field name
95    pub name: String,
96    /// Field type
97    #[serde(rename = "type")]
98    pub type_: IdlTypeSnapshot,
99}
100
101/// Type representation from IDL
102#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(untagged)]
104pub enum IdlTypeSnapshot {
105    /// Simple types: "u64", "bool", "string", "pubkey", etc.
106    Simple(String),
107    /// Array type: { "array": ["u8", 32] }
108    Array(IdlArrayTypeSnapshot),
109    /// Option type: { "option": "u64" }
110    Option(IdlOptionTypeSnapshot),
111    /// Vec type: { "vec": "u8" }
112    Vec(IdlVecTypeSnapshot),
113    /// Defined/custom type: { "defined": { "name": "MyStruct" } }
114    Defined(IdlDefinedTypeSnapshot),
115}
116
117/// Array type representation
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct IdlArrayTypeSnapshot {
120    /// [element_type, size]
121    pub array: Vec<IdlArrayElementSnapshot>,
122}
123
124/// Array element (can be type or size)
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(untagged)]
127pub enum IdlArrayElementSnapshot {
128    /// Nested type
129    Type(IdlTypeSnapshot),
130    /// Type name as string
131    TypeName(String),
132    /// Array size
133    Size(u32),
134}
135
136/// Option type representation
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct IdlOptionTypeSnapshot {
139    pub option: Box<IdlTypeSnapshot>,
140}
141
142/// Vec type representation
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct IdlVecTypeSnapshot {
145    pub vec: Box<IdlTypeSnapshot>,
146}
147
148/// Defined/custom type reference
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct IdlDefinedTypeSnapshot {
151    pub defined: IdlDefinedInnerSnapshot,
152}
153
154/// Inner defined type (can be named or simple string)
155#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(untagged)]
157pub enum IdlDefinedInnerSnapshot {
158    /// Named: { "name": "MyStruct" }
159    Named { name: String },
160    /// Simple string reference
161    Simple(String),
162}
163
164/// Type definition (struct or enum)
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct IdlTypeDefSnapshot {
167    /// Type name
168    pub name: String,
169    /// Documentation
170    #[serde(default)]
171    pub docs: Vec<String>,
172    /// Type definition (struct or enum)
173    #[serde(rename = "type")]
174    pub type_def: IdlTypeDefKindSnapshot,
175}
176
177/// Type definition kind (struct or enum)
178#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(untagged)]
180pub enum IdlTypeDefKindSnapshot {
181    /// Struct: { "kind": "struct", "fields": [...] }
182    Struct { kind: String, fields: Vec<IdlFieldSnapshot> },
183    /// Enum: { "kind": "enum", "variants": [...] }
184    Enum { kind: String, variants: Vec<IdlEnumVariantSnapshot> },
185}
186
187/// Enum variant
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct IdlEnumVariantSnapshot {
190    pub name: String,
191}
192
193/// Event definition
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct IdlEventSnapshot {
196    /// Event name
197    pub name: String,
198    /// 8-byte discriminator
199    pub discriminator: Vec<u8>,
200    /// Documentation
201    #[serde(default)]
202    pub docs: Vec<String>,
203}
204
205/// Error definition
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct IdlErrorSnapshot {
208    /// Error code
209    pub code: u32,
210    /// Error name
211    pub name: String,
212    /// Error message
213    pub msg: String,
214}
215
216impl IdlTypeSnapshot {
217    /// Convert IDL type to Rust type string
218    pub fn to_rust_type_string(&self) -> String {
219        match self {
220            IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
221            IdlTypeSnapshot::Array(arr) => {
222                if arr.array.len() == 2 {
223                    match (&arr.array[0], &arr.array[1]) {
224                        (IdlArrayElementSnapshot::TypeName(t), IdlArrayElementSnapshot::Size(size)) => {
225                            format!("[{}; {}]", Self::map_simple_type(t), size)
226                        }
227                        (IdlArrayElementSnapshot::Type(nested), IdlArrayElementSnapshot::Size(size)) => {
228                            format!("[{}; {}]", nested.to_rust_type_string(), size)
229                        }
230                        _ => "Vec<u8>".to_string(),
231                    }
232                } else {
233                    "Vec<u8>".to_string()
234                }
235            }
236            IdlTypeSnapshot::Option(opt) => {
237                format!("Option<{}>", opt.option.to_rust_type_string())
238            }
239            IdlTypeSnapshot::Vec(vec) => {
240                format!("Vec<{}>", vec.vec.to_rust_type_string())
241            }
242            IdlTypeSnapshot::Defined(def) => {
243                match &def.defined {
244                    IdlDefinedInnerSnapshot::Named { name } => name.clone(),
245                    IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
246                }
247            }
248        }
249    }
250
251    fn map_simple_type(idl_type: &str) -> String {
252        match idl_type {
253            "u8" => "u8".to_string(),
254            "u16" => "u16".to_string(),
255            "u32" => "u32".to_string(),
256            "u64" => "u64".to_string(),
257            "u128" => "u128".to_string(),
258            "i8" => "i8".to_string(),
259            "i16" => "i16".to_string(),
260            "i32" => "i32".to_string(),
261            "i64" => "i64".to_string(),
262            "i128" => "i128".to_string(),
263            "bool" => "bool".to_string(),
264            "string" => "String".to_string(),
265            "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
266            "bytes" => "Vec<u8>".to_string(),
267            _ => idl_type.to_string(),
268        }
269    }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub struct FieldPath {
274    pub segments: Vec<String>,
275    pub offsets: Option<Vec<usize>>,
276}
277
278impl FieldPath {
279    pub fn new(segments: &[&str]) -> Self {
280        FieldPath {
281            segments: segments.iter().map(|s| s.to_string()).collect(),
282            offsets: None,
283        }
284    }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
288pub enum Transformation {
289    HexEncode,
290    HexDecode,
291    Base58Encode,
292    Base58Decode,
293    ToString,
294    ToNumber,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum PopulationStrategy {
299    SetOnce,
300    LastWrite,
301    Append,
302    Merge,
303    Max,
304    /// Sum numeric values (accumulator pattern for aggregations)
305    Sum,
306    /// Count occurrences (increments by 1 for each update)
307    Count,
308    /// Track minimum value
309    Min,
310    /// Track unique values and store the count
311    /// Internally maintains a HashSet, exposes only the count
312    UniqueCount,
313}
314
315// ============================================================================
316// Computed Field Expression AST
317// ============================================================================
318
319/// Specification for a computed/derived field
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct ComputedFieldSpec {
322    /// Target field path (e.g., "trading.total_volume")
323    pub target_path: String,
324    /// Expression AST
325    pub expression: ComputedExpr,
326    /// Result type (e.g., "Option<u64>", "Option<f64>")
327    pub result_type: String,
328}
329
330/// AST for computed field expressions
331/// Supports a subset of Rust expressions needed for computed fields:
332/// - Field references (possibly from other sections)
333/// - Unwrap with defaults
334/// - Basic arithmetic and comparisons
335/// - Type casts
336/// - Method calls
337/// - Let bindings and conditionals
338/// - Byte array operations
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub enum ComputedExpr {
341    // Existing variants
342    /// Reference to a field: "field_name" or "section.field_name"
343    FieldRef { path: String },
344    
345    /// Unwrap with default: expr.unwrap_or(default)
346    UnwrapOr { expr: Box<ComputedExpr>, default: serde_json::Value },
347    
348    /// Binary operation: left op right
349    Binary { op: BinaryOp, left: Box<ComputedExpr>, right: Box<ComputedExpr> },
350    
351    /// Type cast: expr as type
352    Cast { expr: Box<ComputedExpr>, to_type: String },
353    
354    /// Method call: expr.method(args)
355    MethodCall { expr: Box<ComputedExpr>, method: String, args: Vec<ComputedExpr> },
356    
357    /// Literal value: numbers, booleans, strings
358    Literal { value: serde_json::Value },
359    
360    /// Parenthesized expression for grouping
361    Paren { expr: Box<ComputedExpr> },
362    
363    // Variable reference (for let bindings)
364    Var { name: String },
365    
366    // Let binding: let name = value; body
367    Let { name: String, value: Box<ComputedExpr>, body: Box<ComputedExpr> },
368    
369    // Conditional: if condition { then_branch } else { else_branch }
370    If { condition: Box<ComputedExpr>, then_branch: Box<ComputedExpr>, else_branch: Box<ComputedExpr> },
371    
372    // Option constructors
373    None,
374    Some { value: Box<ComputedExpr> },
375    
376    // Byte/array operations
377    Slice { expr: Box<ComputedExpr>, start: usize, end: usize },
378    Index { expr: Box<ComputedExpr>, index: usize },
379    
380    // Byte conversion functions
381    U64FromLeBytes { bytes: Box<ComputedExpr> },
382    U64FromBeBytes { bytes: Box<ComputedExpr> },
383    
384    // Byte array literals: [0u8; 32] or [1, 2, 3]
385    ByteArray { bytes: Vec<u8> },
386    
387    // Closure for map operations: |x| body
388    Closure { param: String, body: Box<ComputedExpr> },
389    
390    // Unary operations
391    Unary { op: UnaryOp, expr: Box<ComputedExpr> },
392    
393    // JSON array to bytes conversion (for working with captured byte arrays)
394    JsonToBytes { expr: Box<ComputedExpr> },
395}
396
397/// Binary operators for computed expressions
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub enum BinaryOp {
400    // Arithmetic
401    Add, Sub, Mul, Div, Mod,
402    // Comparison
403    Gt, Lt, Gte, Lte, Eq, Ne,
404    // Logical
405    And, Or,
406    // Bitwise
407    Xor, BitAnd, BitOr, Shl, Shr,
408}
409
410/// Unary operators for computed expressions
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub enum UnaryOp {
413    Not,
414    ReverseBits,
415}
416
417/// Serializable version of StreamSpec without phantom types
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct SerializableStreamSpec {
420    pub state_name: String,
421    /// Program ID (Solana address) - extracted from IDL
422    #[serde(default)]
423    pub program_id: Option<String>,
424    /// Embedded IDL for AST-only compilation
425    #[serde(default)]
426    pub idl: Option<IdlSnapshot>,
427    pub identity: IdentitySpec,
428    pub handlers: Vec<SerializableHandlerSpec>,
429    pub sections: Vec<EntitySection>,
430    pub field_mappings: BTreeMap<String, FieldTypeInfo>,
431    pub resolver_hooks: Vec<ResolverHook>,
432    pub instruction_hooks: Vec<InstructionHook>,
433    /// Computed field paths (legacy, for backward compatibility)
434    #[serde(default)]
435    pub computed_fields: Vec<String>,
436    /// Computed field specifications with full expression AST
437    #[serde(default)]
438    pub computed_field_specs: Vec<ComputedFieldSpec>,
439    /// Deterministic content hash (SHA256 of canonical JSON, excluding this field)
440    /// Used for deduplication and version tracking
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub content_hash: Option<String>,
443}
444
445#[derive(Debug, Clone)]
446pub struct TypedStreamSpec<S> {
447    pub state_name: String,
448    pub identity: IdentitySpec,
449    pub handlers: Vec<TypedHandlerSpec<S>>,
450    pub sections: Vec<EntitySection>,  // NEW: Complete structural information
451    pub field_mappings: BTreeMap<String, FieldTypeInfo>, // NEW: All field type info by target path
452    pub resolver_hooks: Vec<ResolverHook>,  // NEW: Resolver hooks for PDA key resolution
453    pub instruction_hooks: Vec<InstructionHook>,  // NEW: Instruction hooks for PDA registration
454    pub computed_fields: Vec<String>, // List of computed field paths
455    _phantom: PhantomData<S>,
456}
457
458impl<S> TypedStreamSpec<S> {
459    pub fn new(
460        state_name: String,
461        identity: IdentitySpec,
462        handlers: Vec<TypedHandlerSpec<S>>,
463    ) -> Self {
464        TypedStreamSpec {
465            state_name,
466            identity,
467            handlers,
468            sections: Vec::new(),
469            field_mappings: BTreeMap::new(),
470            resolver_hooks: Vec::new(),
471            instruction_hooks: Vec::new(),
472            computed_fields: Vec::new(),
473            _phantom: PhantomData,
474        }
475    }
476
477    /// Enhanced constructor with type information
478    pub fn with_type_info(
479        state_name: String,
480        identity: IdentitySpec,
481        handlers: Vec<TypedHandlerSpec<S>>,
482        sections: Vec<EntitySection>,
483        field_mappings: BTreeMap<String, FieldTypeInfo>,
484    ) -> Self {
485        TypedStreamSpec {
486            state_name,
487            identity,
488            handlers,
489            sections,
490            field_mappings,
491            resolver_hooks: Vec::new(),
492            instruction_hooks: Vec::new(),
493            computed_fields: Vec::new(),
494            _phantom: PhantomData,
495        }
496    }
497
498    /// Get type information for a specific field path
499    pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
500        self.field_mappings.get(path)
501    }
502
503    /// Get all fields for a specific section
504    pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
505        self.sections.iter()
506            .find(|s| s.name == section_name)
507            .map(|s| &s.fields)
508    }
509
510    /// Get all section names
511    pub fn get_section_names(&self) -> Vec<&String> {
512        self.sections.iter().map(|s| &s.name).collect()
513    }
514
515    /// Convert to serializable format
516    pub fn to_serializable(&self) -> SerializableStreamSpec {
517        let mut spec = SerializableStreamSpec {
518            state_name: self.state_name.clone(),
519            program_id: None, // Set externally when IDL is available
520            idl: None,        // Set externally when IDL is available
521            identity: self.identity.clone(),
522            handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
523            sections: self.sections.clone(),
524            field_mappings: self.field_mappings.clone(),
525            resolver_hooks: self.resolver_hooks.clone(),
526            instruction_hooks: self.instruction_hooks.clone(),
527            computed_fields: self.computed_fields.clone(),
528            computed_field_specs: Vec::new(), // Set externally when expression parsing is available
529            content_hash: None,
530        };
531        // Compute and set the content hash
532        spec.content_hash = Some(spec.compute_content_hash());
533        spec
534    }
535
536    /// Create from serializable format
537    pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
538        TypedStreamSpec {
539            state_name: spec.state_name,
540            identity: spec.identity,
541            handlers: spec.handlers.into_iter().map(|h| TypedHandlerSpec::from_serializable(h)).collect(),
542            sections: spec.sections,
543            field_mappings: spec.field_mappings,
544            resolver_hooks: spec.resolver_hooks,
545            instruction_hooks: spec.instruction_hooks,
546            computed_fields: spec.computed_fields,
547            _phantom: PhantomData,
548        }
549    }
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct IdentitySpec {
554    pub primary_keys: Vec<String>,
555    pub lookup_indexes: Vec<LookupIndexSpec>,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct LookupIndexSpec {
560    pub field_name: String,
561    pub temporal_field: Option<String>,
562}
563
564// ============================================================================
565// Level 1: Declarative Hook Extensions
566// ============================================================================
567
568/// Declarative resolver hook specification
569#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct ResolverHook {
571    /// Account type this resolver applies to (e.g., "BondingCurveState")
572    pub account_type: String,
573    
574    /// Resolution strategy
575    pub strategy: ResolverStrategy,
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub enum ResolverStrategy {
580    /// Look up PDA in reverse lookup table, queue if not found
581    PdaReverseLookup {
582        lookup_name: String,
583        /// Instruction discriminators to queue until (8 bytes each)
584        queue_discriminators: Vec<Vec<u8>>,
585    },
586    
587    /// Extract primary key directly from account data (future)
588    DirectField {
589        field_path: FieldPath,
590    },
591}
592
593/// Declarative instruction hook specification
594#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct InstructionHook {
596    /// Instruction type this hook applies to (e.g., "CreateIxState")
597    pub instruction_type: String,
598    
599    /// Actions to perform when this instruction is processed
600    pub actions: Vec<HookAction>,
601    
602    /// Lookup strategy for finding the entity
603    pub lookup_by: Option<FieldPath>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607pub enum HookAction {
608    /// Register a PDA mapping for reverse lookup
609    RegisterPdaMapping {
610        pda_field: FieldPath,
611        seed_field: FieldPath,
612        lookup_name: String,
613    },
614    
615    /// Set a field value (for #[track_from])
616    SetField {
617        target_field: String,
618        source: MappingSource,
619        condition: Option<ConditionExpr>,
620    },
621    
622    /// Increment a field value (for conditional aggregations)
623    IncrementField {
624        target_field: String,
625        increment_by: i64,
626        condition: Option<ConditionExpr>,
627    },
628}
629
630/// Simple condition expression (Level 1 - basic comparisons only)
631#[derive(Debug, Clone, Serialize, Deserialize)]
632pub struct ConditionExpr {
633    /// Expression as string (will be parsed and validated)
634    pub expression: String,
635    
636    /// Parsed representation (for validation and execution)
637    pub parsed: Option<ParsedCondition>,
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize)]
641pub enum ParsedCondition {
642    /// Binary comparison: field op value
643    Comparison {
644        field: FieldPath,
645        op: ComparisonOp,
646        value: serde_json::Value,
647    },
648    
649    /// Logical AND/OR
650    Logical {
651        op: LogicalOp,
652        conditions: Vec<ParsedCondition>,
653    },
654}
655
656#[derive(Debug, Clone, Serialize, Deserialize)]
657pub enum ComparisonOp {
658    Equal,
659    NotEqual,
660    GreaterThan,
661    GreaterThanOrEqual,
662    LessThan,
663    LessThanOrEqual,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
667pub enum LogicalOp {
668    And,
669    Or,
670}
671
672/// Serializable version of HandlerSpec without phantom types
673#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct SerializableHandlerSpec {
675    pub source: SourceSpec,
676    pub key_resolution: KeyResolutionStrategy,
677    pub mappings: Vec<SerializableFieldMapping>,
678    pub conditions: Vec<Condition>,
679    pub emit: bool,
680}
681
682#[derive(Debug, Clone)]
683pub struct TypedHandlerSpec<S> {
684    pub source: SourceSpec,
685    pub key_resolution: KeyResolutionStrategy,
686    pub mappings: Vec<TypedFieldMapping<S>>,
687    pub conditions: Vec<Condition>,
688    pub emit: bool,
689    _phantom: PhantomData<S>,
690}
691
692impl<S> TypedHandlerSpec<S> {
693    pub fn new(
694        source: SourceSpec,
695        key_resolution: KeyResolutionStrategy,
696        mappings: Vec<TypedFieldMapping<S>>,
697        emit: bool,
698    ) -> Self {
699        TypedHandlerSpec {
700            source,
701            key_resolution,
702            mappings,
703            conditions: vec![],
704            emit,
705            _phantom: PhantomData,
706        }
707    }
708
709    /// Convert to serializable format
710    pub fn to_serializable(&self) -> SerializableHandlerSpec {
711        SerializableHandlerSpec {
712            source: self.source.clone(),
713            key_resolution: self.key_resolution.clone(),
714            mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
715            conditions: self.conditions.clone(),
716            emit: self.emit,
717        }
718    }
719
720    /// Create from serializable format
721    pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
722        TypedHandlerSpec {
723            source: spec.source,
724            key_resolution: spec.key_resolution,
725            mappings: spec.mappings.into_iter().map(|m| TypedFieldMapping::from_serializable(m)).collect(),
726            conditions: spec.conditions,
727            emit: spec.emit,
728            _phantom: PhantomData,
729        }
730    }
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
734pub enum KeyResolutionStrategy {
735    Embedded {
736        primary_field: FieldPath,
737    },
738    Lookup {
739        primary_field: FieldPath,
740    },
741    Computed {
742        primary_field: FieldPath,
743        compute_partition: ComputeFunction,
744    },
745    TemporalLookup {
746        lookup_field: FieldPath,
747        timestamp_field: FieldPath,
748        index_name: String,
749    },
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize)]
753pub enum SourceSpec {
754    Source {
755        program_id: Option<String>,
756        discriminator: Option<Vec<u8>>,
757        type_name: String,
758    },
759}
760
761/// Serializable version of FieldMapping without phantom types
762#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct SerializableFieldMapping {
764    pub target_path: String,
765    pub source: MappingSource,
766    pub transform: Option<Transformation>,
767    pub population: PopulationStrategy,
768}
769
770#[derive(Debug, Clone)]
771pub struct TypedFieldMapping<S> {
772    pub target_path: String,
773    pub source: MappingSource,
774    pub transform: Option<Transformation>,
775    pub population: PopulationStrategy,
776    _phantom: PhantomData<S>,
777}
778
779impl<S> TypedFieldMapping<S> {
780    pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
781        TypedFieldMapping {
782            target_path,
783            source,
784            transform: None,
785            population,
786            _phantom: PhantomData,
787        }
788    }
789
790    pub fn with_transform(mut self, transform: Transformation) -> Self {
791        self.transform = Some(transform);
792        self
793    }
794
795    /// Convert to serializable format
796    pub fn to_serializable(&self) -> SerializableFieldMapping {
797        SerializableFieldMapping {
798            target_path: self.target_path.clone(),
799            source: self.source.clone(),
800            transform: self.transform.clone(),
801            population: self.population.clone(),
802        }
803    }
804
805    /// Create from serializable format
806    pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
807        TypedFieldMapping {
808            target_path: mapping.target_path,
809            source: mapping.source,
810            transform: mapping.transform,
811            population: mapping.population,
812            _phantom: PhantomData,
813        }
814    }
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
818pub enum MappingSource {
819    FromSource {
820        path: FieldPath,
821        default: Option<Value>,
822        transform: Option<Transformation>,
823    },
824    Constant(Value),
825    Computed {
826        inputs: Vec<FieldPath>,
827        function: ComputeFunction,
828    },
829    FromState {
830        path: String,
831    },
832    AsEvent {
833        fields: Vec<Box<MappingSource>>,
834    },
835    WholeSource,
836    /// Similar to WholeSource but with field-level transformations
837    /// Used by #[capture] macro to apply transforms to specific fields in an account
838    AsCapture {
839        field_transforms: BTreeMap<String, Transformation>,
840    },
841    /// From instruction context (timestamp, slot, signature)
842    /// Used by #[track_from] with special fields like __timestamp
843    FromContext {
844        field: String,
845    },
846}
847
848impl MappingSource {
849    pub fn with_transform(self, transform: Transformation) -> Self {
850        match self {
851            MappingSource::FromSource { path, default, transform: _ } => {
852                MappingSource::FromSource {
853                    path,
854                    default,
855                    transform: Some(transform),
856                }
857            }
858            other => other,
859        }
860    }
861}
862
863#[derive(Debug, Clone, Serialize, Deserialize)]
864pub enum ComputeFunction {
865    Sum,
866    Concat,
867    Format(String),
868    Custom(String),
869}
870
871#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct Condition {
873    pub field: FieldPath,
874    pub operator: ConditionOp,
875    pub value: Value,
876}
877
878#[derive(Debug, Clone, Serialize, Deserialize)]
879pub enum ConditionOp {
880    Equals,
881    NotEquals,
882    GreaterThan,
883    LessThan,
884    Contains,
885    Exists,
886}
887
888/// Language-agnostic type information for fields
889#[derive(Debug, Clone, Serialize, Deserialize)]
890pub struct FieldTypeInfo {
891    pub field_name: String,
892    pub rust_type_name: String,     // Full Rust type: "Option<i64>", "Vec<Value>", etc.
893    pub base_type: BaseType,        // Fundamental type classification
894    pub is_optional: bool,          // true for Option<T>
895    pub is_array: bool,             // true for Vec<T>
896    pub inner_type: Option<String>, // For Option<T> or Vec<T>, store the inner type
897    pub source_path: Option<String>, // Path to source field if this is mapped
898    /// Resolved type information for complex types (instructions, accounts, custom types)
899    #[serde(default)]
900    pub resolved_type: Option<ResolvedStructType>,
901}
902
903/// Resolved structure type with field information from IDL
904#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct ResolvedStructType {
906    pub type_name: String,
907    pub fields: Vec<ResolvedField>,
908    pub is_instruction: bool,
909    pub is_account: bool,
910    pub is_event: bool,
911    /// If true, this is an enum type and enum_variants should be used instead of fields
912    #[serde(default)]
913    pub is_enum: bool,
914    /// For enum types, list of variant names
915    #[serde(default)]
916    pub enum_variants: Vec<String>,
917}
918
919/// A resolved field within a complex type
920#[derive(Debug, Clone, Serialize, Deserialize)]
921pub struct ResolvedField {
922    pub field_name: String,
923    pub field_type: String,
924    pub base_type: BaseType,
925    pub is_optional: bool,
926    pub is_array: bool,
927}
928
929/// Language-agnostic base type classification
930#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
931pub enum BaseType {
932    // Numeric types
933    Integer,    // i8, i16, i32, i64, u8, u16, u32, u64, usize, isize
934    Float,      // f32, f64
935    // Text types
936    String,     // String, &str
937    // Boolean
938    Boolean,    // bool
939    // Complex types
940    Object,     // Custom structs, HashMap, etc.
941    Array,      // Vec<T>, arrays
942    Binary,     // Bytes, binary data
943    // Special types
944    Timestamp,  // Detected from field names ending in _at, _time, etc.
945    Pubkey,     // Solana public key (Base58 encoded)
946    Any,        // serde_json::Value, unknown types
947}
948
949/// Represents a logical section/group of fields in the entity
950#[derive(Debug, Clone, Serialize, Deserialize)]
951pub struct EntitySection {
952    pub name: String,
953    pub fields: Vec<FieldTypeInfo>,
954    pub is_nested_struct: bool,
955    pub parent_field: Option<String>, // If this section comes from a nested struct field
956}
957
958
959
960impl FieldTypeInfo {
961    pub fn new(field_name: String, rust_type_name: String) -> Self {
962        let (base_type, is_optional, is_array, inner_type) = Self::analyze_rust_type(&rust_type_name);
963        
964        FieldTypeInfo {
965            field_name: field_name.clone(),
966            rust_type_name,
967            base_type: Self::infer_semantic_type(&field_name, base_type),
968            is_optional,
969            is_array,
970            inner_type,
971            source_path: None,
972            resolved_type: None,
973        }
974    }
975
976    pub fn with_source_path(mut self, source_path: String) -> Self {
977        self.source_path = Some(source_path);
978        self
979    }
980
981    /// Analyze a Rust type string and extract structural information
982    fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
983        let type_str = rust_type.trim();
984        
985        // Handle Option<T>
986        if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
987            let (inner_base_type, _, inner_is_array, inner_inner_type) = Self::analyze_rust_type(&inner);
988            return (inner_base_type, true, inner_is_array, inner_inner_type.or(Some(inner)));
989        }
990        
991        // Handle Vec<T>
992        if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
993            let (_inner_base_type, inner_is_optional, _, inner_inner_type) = Self::analyze_rust_type(&inner);
994            return (BaseType::Array, inner_is_optional, true, inner_inner_type.or(Some(inner)));
995        }
996        
997        // Handle primitive types
998        let base_type = match type_str {
999            "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => BaseType::Integer,
1000            "f32" | "f64" => BaseType::Float,
1001            "bool" => BaseType::Boolean,
1002            "String" | "&str" | "str" => BaseType::String,
1003            "Value" | "serde_json::Value" => BaseType::Any,
1004            "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1005            _ => {
1006                // Check for binary types
1007                if type_str.contains("Bytes") || type_str.contains("bytes") {
1008                    BaseType::Binary
1009                } else if type_str.contains("Pubkey") {
1010                    BaseType::Pubkey
1011                } else {
1012                    BaseType::Object
1013                }
1014            }
1015        };
1016        
1017        (base_type, false, false, None)
1018    }
1019    
1020    /// Extract inner type from generic like "Option<T>" -> "T"
1021    fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1022        let pattern = format!("{}<", generic_name);
1023        if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1024            let start = pattern.len();
1025            let end = type_str.len() - 1;
1026            if end > start {
1027                return Some(type_str[start..end].trim().to_string());
1028            }
1029        }
1030        None
1031    }
1032    
1033    /// Infer semantic type based on field name patterns
1034    fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1035        let lower_name = field_name.to_lowercase();
1036        
1037        // If already classified as integer, check if it should be timestamp
1038        if base_type == BaseType::Integer
1039            && (lower_name.ends_with("_at")
1040                || lower_name.ends_with("_time")
1041                || lower_name.contains("timestamp")
1042                || lower_name.contains("created")
1043                || lower_name.contains("settled")
1044                || lower_name.contains("activated"))
1045        {
1046            return BaseType::Timestamp;
1047        }
1048        
1049        base_type
1050    }
1051}
1052
1053pub trait FieldAccessor<S> {
1054    fn path(&self) -> String;
1055}
1056
1057// ============================================================================
1058// SerializableStreamSpec Implementation
1059// ============================================================================
1060
1061impl SerializableStreamSpec {
1062    /// Compute deterministic content hash (SHA256 of canonical JSON).
1063    /// 
1064    /// The hash is computed over the entire spec except the content_hash field itself,
1065    /// ensuring the same AST always produces the same hash regardless of when it was
1066    /// generated or by whom.
1067    pub fn compute_content_hash(&self) -> String {
1068        use sha2::{Sha256, Digest};
1069        
1070        // Clone and clear the hash field for computation
1071        let mut spec_for_hash = self.clone();
1072        spec_for_hash.content_hash = None;
1073        
1074        // Serialize to JSON (serde_json produces consistent output for the same struct)
1075        let json = serde_json::to_string(&spec_for_hash)
1076            .expect("Failed to serialize spec for hashing");
1077        
1078        // Compute SHA256 hash
1079        let mut hasher = Sha256::new();
1080        hasher.update(json.as_bytes());
1081        let result = hasher.finalize();
1082        
1083        // Return hex-encoded hash
1084        hex::encode(result)
1085    }
1086    
1087    /// Verify that the content_hash matches the computed hash.
1088    /// Returns true if hash is valid or not set.
1089    pub fn verify_content_hash(&self) -> bool {
1090        match &self.content_hash {
1091            Some(hash) => {
1092                let computed = self.compute_content_hash();
1093                hash == &computed
1094            }
1095            None => true, // No hash to verify
1096        }
1097    }
1098    
1099    /// Set the content_hash field to the computed hash.
1100    pub fn with_content_hash(mut self) -> Self {
1101        self.content_hash = Some(self.compute_content_hash());
1102        self
1103    }
1104}
1105
1106#[macro_export]
1107macro_rules! define_accessor {
1108    ($name:ident, $state:ty, $path:expr) => {
1109        pub struct $name;
1110
1111        impl $crate::ast::FieldAccessor<$state> for $name {
1112            fn path(&self) -> String {
1113                $path.to_string()
1114            }
1115        }
1116    };
1117}