Skip to main content

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