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