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