hyperstack_interpreter/
ast.rs

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