Skip to main content

arete_interpreter/
ast.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4use std::marker::PhantomData;
5
6pub use arete_idl::snapshot::*;
7
8/// Current AST version for SerializableStreamSpec and SerializableStackSpec
9///
10/// ⚠️ IMPORTANT: This constant is duplicated in arete-macros/src/ast/types.rs due to
11/// circular dependency between proc-macro crates and their output crates.
12/// When bumping this version, you MUST also update the constant in the
13/// arete-macros crate. A test in versioned.rs verifies they stay in sync.
14// 0.0.2: enum variants may carry `fields` (data-carrying enum variants).
15// 0.0.3: field metadata may carry integer-kind and raw/canonical name hints.
16// 0.0.4: instruction args may carry amount-hint metadata.
17// 0.0.5: stack specs may carry exact ordered public ProgramSpecV1 values.
18// Additive over 0.0.1 — every older file deserializes unchanged.
19pub const CURRENT_AST_VERSION: &str = "0.0.5";
20
21/// Older versions this build can still deserialize directly (all changes
22/// since were additive with serde defaults).
23pub const COMPATIBLE_AST_VERSIONS: &[&str] = &["0.0.1", "0.0.2", "0.0.3", "0.0.4"];
24
25fn default_ast_version() -> String {
26    CURRENT_AST_VERSION.to_string()
27}
28
29pub fn idl_type_snapshot_to_rust_string(ty: &IdlTypeSnapshot) -> String {
30    match ty {
31        IdlTypeSnapshot::Simple(s) => map_simple_idl_type(s),
32        IdlTypeSnapshot::Array(arr) => {
33            if arr.array.len() == 2 {
34                match (&arr.array[0], &arr.array[1]) {
35                    (IdlArrayElementSnapshot::TypeName(t), IdlArrayElementSnapshot::Size(size)) => {
36                        format!("[{}; {}]", map_simple_idl_type(t), size)
37                    }
38                    (
39                        IdlArrayElementSnapshot::Type(nested),
40                        IdlArrayElementSnapshot::Size(size),
41                    ) => {
42                        format!("[{}; {}]", idl_type_snapshot_to_rust_string(nested), size)
43                    }
44                    _ => "Vec<u8>".to_string(),
45                }
46            } else {
47                "Vec<u8>".to_string()
48            }
49        }
50        IdlTypeSnapshot::Option(opt) => {
51            format!("Option<{}>", idl_type_snapshot_to_rust_string(&opt.option))
52        }
53        IdlTypeSnapshot::Vec(vec) => {
54            let inner = idl_type_snapshot_to_rust_string(&vec.vec);
55            match vec.length_prefix {
56                Some(arete_idl::types::IdlLengthPrefix::U64) => format!("VecU64Len<{}>", inner),
57                _ => format!("Vec<{}>", inner),
58            }
59        }
60        IdlTypeSnapshot::String(string) if string.is_u64_prefixed() => "StringU64Len".to_string(),
61        IdlTypeSnapshot::String(_) => "String".to_string(),
62        IdlTypeSnapshot::HashMap(map) => {
63            let key_type = idl_type_snapshot_to_rust_string(&map.hash_map.0);
64            let val_type = idl_type_snapshot_to_rust_string(&map.hash_map.1);
65            format!("std::collections::HashMap<{}, {}>", key_type, val_type)
66        }
67        IdlTypeSnapshot::Tuple(tuple) => format!(
68            "({})",
69            tuple
70                .tuple
71                .iter()
72                .map(idl_type_snapshot_to_rust_string)
73                .collect::<Vec<_>>()
74                .join(", ")
75        ),
76        IdlTypeSnapshot::Defined(def) => match &def.defined {
77            IdlDefinedInnerSnapshot::Named { name } => name.clone(),
78            IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
79        },
80    }
81}
82
83fn map_simple_idl_type(idl_type: &str) -> String {
84    match idl_type {
85        "u8" => "u8".to_string(),
86        "u16" => "u16".to_string(),
87        "u32" => "u32".to_string(),
88        "u64" => "u64".to_string(),
89        "u128" => "u128".to_string(),
90        "i8" => "i8".to_string(),
91        "i16" => "i16".to_string(),
92        "i32" => "i32".to_string(),
93        "i64" => "i64".to_string(),
94        "i128" => "i128".to_string(),
95        "bool" => "bool".to_string(),
96        "string" => "String".to_string(),
97        "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
98        "bytes" => "Vec<u8>".to_string(),
99        _ => idl_type.to_string(),
100    }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
104pub struct FieldPath {
105    pub segments: Vec<String>,
106    pub offsets: Option<Vec<usize>>,
107}
108
109impl FieldPath {
110    pub fn new(segments: &[&str]) -> Self {
111        FieldPath {
112            segments: segments.iter().map(|s| s.to_string()).collect(),
113            offsets: None,
114        }
115    }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
119pub enum Transformation {
120    HexEncode,
121    HexDecode,
122    Base58Encode,
123    Base58Decode,
124    ToString,
125    ToNumber,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub enum PopulationStrategy {
130    SetOnce,
131    LastWrite,
132    Append,
133    Merge,
134    Max,
135    /// Sum numeric values (accumulator pattern for aggregations)
136    Sum,
137    /// Count occurrences (increments by 1 for each update)
138    Count,
139    /// Track minimum value
140    Min,
141    /// Track unique values and store the count
142    /// Internally maintains a HashSet, exposes only the count
143    UniqueCount,
144}
145
146// ============================================================================
147// Computed Field Expression AST
148// ============================================================================
149
150/// Specification for a computed/derived field
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ComputedFieldSpec {
153    /// Target field path (e.g., "trading.total_volume")
154    pub target_path: String,
155    /// Expression AST
156    pub expression: ComputedExpr,
157    /// Result type (e.g., "Option<u64>", "Option<f64>")
158    pub result_type: String,
159}
160
161// ============================================================================
162// Resolver Specifications
163// ============================================================================
164
165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
166#[serde(rename_all = "lowercase")]
167pub enum ResolverType {
168    Token,
169    Url(UrlResolverConfig),
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
173#[serde(rename_all = "lowercase")]
174pub enum HttpMethod {
175    #[default]
176    Get,
177    Post,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
181pub enum UrlTemplatePart {
182    Literal(String),
183    FieldRef(String),
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
187pub enum UrlSource {
188    FieldPath(String),
189    Template(Vec<UrlTemplatePart>),
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
193pub struct UrlResolverConfig {
194    pub url_source: UrlSource,
195    #[serde(default)]
196    pub method: HttpMethod,
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub extract_path: Option<String>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
202pub struct ResolverExtractSpec {
203    pub target_path: String,
204    #[serde(default, skip_serializing_if = "Option::is_none")]
205    pub source_path: Option<String>,
206    #[serde(default, skip_serializing_if = "Option::is_none")]
207    pub transform: Option<Transformation>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
211pub enum ResolveStrategy {
212    #[default]
213    SetOnce,
214    LastWrite,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218pub struct ResolverCondition {
219    pub field_path: String,
220    pub op: ComparisonOp,
221    pub value: Value,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct ResolverSpec {
226    pub resolver: ResolverType,
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub input_path: Option<String>,
229    #[serde(default, skip_serializing_if = "Option::is_none")]
230    pub input_value: Option<Value>,
231    #[serde(default)]
232    pub strategy: ResolveStrategy,
233    pub extracts: Vec<ResolverExtractSpec>,
234    #[serde(default, skip_serializing_if = "Option::is_none")]
235    pub condition: Option<ResolverCondition>,
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub schedule_at: Option<String>,
238}
239
240/// AST for computed field expressions
241/// Supports a subset of Rust expressions needed for computed fields:
242/// - Field references (possibly from other sections)
243/// - Unwrap with defaults
244/// - Basic arithmetic and comparisons
245/// - Type casts
246/// - Method calls
247/// - Let bindings and conditionals
248/// - Byte array operations
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub enum ComputedExpr {
251    // Existing variants
252    /// Reference to a field: "field_name" or "section.field_name"
253    FieldRef {
254        path: String,
255    },
256
257    /// Unwrap with default: expr.unwrap_or(default)
258    UnwrapOr {
259        expr: Box<ComputedExpr>,
260        default: serde_json::Value,
261    },
262
263    /// Binary operation: left op right
264    Binary {
265        op: BinaryOp,
266        left: Box<ComputedExpr>,
267        right: Box<ComputedExpr>,
268    },
269
270    /// Type cast: expr as type
271    Cast {
272        expr: Box<ComputedExpr>,
273        to_type: String,
274    },
275
276    /// Method call: expr.method(args)
277    MethodCall {
278        expr: Box<ComputedExpr>,
279        method: String,
280        args: Vec<ComputedExpr>,
281    },
282
283    /// Computation provided by a resolver
284    ResolverComputed {
285        resolver: String,
286        method: String,
287        args: Vec<ComputedExpr>,
288    },
289
290    /// Literal value: numbers, booleans, strings
291    Literal {
292        value: serde_json::Value,
293    },
294
295    /// Parenthesized expression for grouping
296    Paren {
297        expr: Box<ComputedExpr>,
298    },
299
300    // Variable reference (for let bindings)
301    Var {
302        name: String,
303    },
304
305    // Let binding: let name = value; body
306    Let {
307        name: String,
308        value: Box<ComputedExpr>,
309        body: Box<ComputedExpr>,
310    },
311
312    // Conditional: if condition { then_branch } else { else_branch }
313    If {
314        condition: Box<ComputedExpr>,
315        then_branch: Box<ComputedExpr>,
316        else_branch: Box<ComputedExpr>,
317    },
318
319    // Option constructors
320    None,
321    Some {
322        value: Box<ComputedExpr>,
323    },
324
325    // Byte/array operations
326    Slice {
327        expr: Box<ComputedExpr>,
328        start: usize,
329        end: usize,
330    },
331    Index {
332        expr: Box<ComputedExpr>,
333        index: usize,
334    },
335
336    // Byte conversion functions
337    U64FromLeBytes {
338        bytes: Box<ComputedExpr>,
339    },
340    U64FromBeBytes {
341        bytes: Box<ComputedExpr>,
342    },
343
344    // Byte array literals: [0u8; 32] or [1, 2, 3]
345    ByteArray {
346        bytes: Vec<u8>,
347    },
348
349    // Closure for map operations: |x| body
350    Closure {
351        param: String,
352        body: Box<ComputedExpr>,
353    },
354
355    // Unary operations
356    Unary {
357        op: UnaryOp,
358        expr: Box<ComputedExpr>,
359    },
360
361    // JSON array to bytes conversion (for working with captured byte arrays)
362    JsonToBytes {
363        expr: Box<ComputedExpr>,
364    },
365
366    // Context access - slot and timestamp from the update that triggered evaluation
367    /// Access the slot number from the current update context
368    ContextSlot,
369    /// Access the unix timestamp from the current update context
370    ContextTimestamp,
371
372    /// Keccak256 hash function for computing Ethereum-compatible hashes
373    /// Takes a byte array expression and returns the 32-byte hash as a Vec<u8>
374    Keccak256 {
375        expr: Box<ComputedExpr>,
376    },
377}
378
379/// Binary operators for computed expressions
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub enum BinaryOp {
382    // Arithmetic
383    Add,
384    Sub,
385    Mul,
386    Div,
387    Mod,
388    // Comparison
389    Gt,
390    Lt,
391    Gte,
392    Lte,
393    Eq,
394    Ne,
395    // Logical
396    And,
397    Or,
398    // Bitwise
399    Xor,
400    BitAnd,
401    BitOr,
402    Shl,
403    Shr,
404}
405
406/// Unary operators for computed expressions
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub enum UnaryOp {
409    Not,
410    ReverseBits,
411}
412
413/// Serializable version of StreamSpec without phantom types
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct SerializableStreamSpec {
416    /// AST schema version for backward compatibility
417    /// Uses semver format (e.g., "0.0.1")
418    #[serde(default = "default_ast_version")]
419    pub ast_version: String,
420    pub state_name: String,
421    /// Program ID (Solana address) - extracted from IDL
422    #[serde(default)]
423    pub program_id: Option<String>,
424    /// Embedded IDL for AST-only compilation
425    #[serde(default)]
426    pub idl: Option<IdlSnapshot>,
427    pub identity: IdentitySpec,
428    pub handlers: Vec<SerializableHandlerSpec>,
429    pub sections: Vec<EntitySection>,
430    pub field_mappings: BTreeMap<String, FieldTypeInfo>,
431    pub resolver_hooks: Vec<ResolverHook>,
432    pub instruction_hooks: Vec<InstructionHook>,
433    #[serde(default)]
434    pub resolver_specs: Vec<ResolverSpec>,
435    /// Computed field paths (legacy, for backward compatibility)
436    #[serde(default)]
437    pub computed_fields: Vec<String>,
438    /// Computed field specifications with full expression AST
439    #[serde(default)]
440    pub computed_field_specs: Vec<ComputedFieldSpec>,
441    /// Deterministic content hash (SHA256 of canonical JSON, excluding this field)
442    /// Used for deduplication and version tracking
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub content_hash: Option<String>,
445    /// View definitions for derived/projected views
446    #[serde(default)]
447    pub views: Vec<ViewDef>,
448    /// Row scoping: the dotted path of the string field that holds each
449    /// row's scope id. A server delivers a row only to sessions whose row
450    /// scopes cover its scope; rows with no scope are public. Omitted when
451    /// the entity is not scoped, so existing ASTs keep their bytes.
452    #[serde(default, skip_serializing_if = "Option::is_none")]
453    pub scope_field: Option<String>,
454}
455
456impl SerializableStreamSpec {
457    /// Normalize legacy event names emitted by older AST generators.
458    pub fn normalize_event_names(&mut self) {
459        for handler in &mut self.handlers {
460            handler.normalize_event_names();
461        }
462
463        for hook in &mut self.instruction_hooks {
464            hook.instruction_type =
465                crate::event_type_helpers::canonicalize_event_type_name(&hook.instruction_type);
466        }
467    }
468}
469
470#[derive(Debug, Clone)]
471pub struct TypedStreamSpec<S> {
472    pub state_name: String,
473    pub identity: IdentitySpec,
474    pub handlers: Vec<TypedHandlerSpec<S>>,
475    pub sections: Vec<EntitySection>, // NEW: Complete structural information
476    pub field_mappings: BTreeMap<String, FieldTypeInfo>, // NEW: All field type info by target path
477    pub resolver_hooks: Vec<ResolverHook>, // NEW: Resolver hooks for PDA key resolution
478    pub instruction_hooks: Vec<InstructionHook>, // NEW: Instruction hooks for PDA registration
479    pub resolver_specs: Vec<ResolverSpec>,
480    pub computed_fields: Vec<String>, // List of computed field paths
481    _phantom: PhantomData<S>,
482}
483
484impl<S> TypedStreamSpec<S> {
485    pub fn new(
486        state_name: String,
487        identity: IdentitySpec,
488        handlers: Vec<TypedHandlerSpec<S>>,
489    ) -> Self {
490        TypedStreamSpec {
491            state_name,
492            identity,
493            handlers,
494            sections: Vec::new(),
495            field_mappings: BTreeMap::new(),
496            resolver_hooks: Vec::new(),
497            instruction_hooks: Vec::new(),
498            resolver_specs: Vec::new(),
499            computed_fields: Vec::new(),
500            _phantom: PhantomData,
501        }
502    }
503
504    /// Enhanced constructor with type information
505    pub fn with_type_info(
506        state_name: String,
507        identity: IdentitySpec,
508        handlers: Vec<TypedHandlerSpec<S>>,
509        sections: Vec<EntitySection>,
510        field_mappings: BTreeMap<String, FieldTypeInfo>,
511    ) -> Self {
512        TypedStreamSpec {
513            state_name,
514            identity,
515            handlers,
516            sections,
517            field_mappings,
518            resolver_hooks: Vec::new(),
519            instruction_hooks: Vec::new(),
520            resolver_specs: Vec::new(),
521            computed_fields: Vec::new(),
522            _phantom: PhantomData,
523        }
524    }
525
526    pub fn with_resolver_specs(mut self, resolver_specs: Vec<ResolverSpec>) -> Self {
527        self.resolver_specs = resolver_specs;
528        self
529    }
530
531    /// Get type information for a specific field path
532    pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
533        self.field_mappings.get(path)
534    }
535
536    /// Get all fields for a specific section
537    pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
538        self.sections
539            .iter()
540            .find(|s| s.name == section_name)
541            .map(|s| &s.fields)
542    }
543
544    /// Get all section names
545    pub fn get_section_names(&self) -> Vec<&String> {
546        self.sections.iter().map(|s| &s.name).collect()
547    }
548
549    /// Convert to serializable format
550    pub fn to_serializable(&self) -> SerializableStreamSpec {
551        let mut spec = SerializableStreamSpec {
552            ast_version: CURRENT_AST_VERSION.to_string(),
553            state_name: self.state_name.clone(),
554            program_id: None,
555            idl: None,
556            identity: self.identity.clone(),
557            handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
558            sections: self.sections.clone(),
559            field_mappings: self.field_mappings.clone(),
560            resolver_hooks: self.resolver_hooks.clone(),
561            instruction_hooks: self.instruction_hooks.clone(),
562            resolver_specs: self.resolver_specs.clone(),
563            computed_fields: self.computed_fields.clone(),
564            computed_field_specs: Vec::new(),
565            content_hash: None,
566            views: Vec::new(),
567            scope_field: None,
568        };
569        spec.content_hash = Some(spec.compute_content_hash());
570        spec
571    }
572
573    /// Create from serializable format
574    pub fn from_serializable(mut spec: SerializableStreamSpec) -> Self {
575        spec.normalize_event_names();
576        TypedStreamSpec {
577            state_name: spec.state_name,
578            identity: spec.identity,
579            handlers: spec
580                .handlers
581                .into_iter()
582                .map(|h| TypedHandlerSpec::from_serializable(h))
583                .collect(),
584            sections: spec.sections,
585            field_mappings: spec.field_mappings,
586            resolver_hooks: spec.resolver_hooks,
587            instruction_hooks: spec.instruction_hooks,
588            resolver_specs: spec.resolver_specs,
589            computed_fields: spec.computed_fields,
590            _phantom: PhantomData,
591        }
592    }
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
596pub struct IdentitySpec {
597    pub primary_keys: Vec<String>,
598    pub lookup_indexes: Vec<LookupIndexSpec>,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct LookupIndexSpec {
603    pub field_name: String,
604    pub temporal_field: Option<String>,
605}
606
607// ============================================================================
608// Level 1: Declarative Hook Extensions
609// ============================================================================
610
611/// Declarative resolver hook specification
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct ResolverHook {
614    /// Account type this resolver applies to (e.g., "BondingCurveState")
615    pub account_type: String,
616
617    /// Resolution strategy
618    pub strategy: ResolverStrategy,
619}
620
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub enum ResolverStrategy {
623    /// Look up PDA in reverse lookup table, queue if not found
624    PdaReverseLookup {
625        lookup_name: String,
626        /// Instruction discriminators to queue until (8 bytes each)
627        queue_discriminators: Vec<Vec<u8>>,
628    },
629
630    /// Extract primary key directly from account data (future)
631    DirectField { field_path: FieldPath },
632}
633
634/// Declarative instruction hook specification
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct InstructionHook {
637    /// Instruction type this hook applies to (e.g., "CreateIxState")
638    pub instruction_type: String,
639
640    /// Actions to perform when this instruction is processed
641    pub actions: Vec<HookAction>,
642
643    /// Lookup strategy for finding the entity
644    pub lookup_by: Option<FieldPath>,
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize)]
648pub enum HookAction {
649    /// Register a PDA mapping for reverse lookup
650    RegisterPdaMapping {
651        pda_field: FieldPath,
652        seed_field: FieldPath,
653        lookup_name: String,
654        /// Read `pda_field` and/or `seed_field` from another instruction of
655        /// the same transaction rather than from the instruction this hook
656        /// runs on. Absent for same-instruction registration.
657        #[serde(default, skip_serializing_if = "Option::is_none")]
658        sibling: Option<SiblingSelector>,
659        /// The seed as an expression over the hook's instruction (as in
660        /// `MappingSource::FromExpr`), replacing `seed_field`, e.g. a part of
661        /// a memo string.
662        #[serde(default, skip_serializing_if = "Option::is_none")]
663        seed_expr: Option<ComputedExpr>,
664        /// Register only when this boolean expression over the hook's
665        /// instruction is `true` (see `ParsedCondition::Guard`).
666        #[serde(default, skip_serializing_if = "Option::is_none")]
667        guard: Option<ComputedExpr>,
668    },
669
670    /// Set a field value (for #[track_from])
671    SetField {
672        target_field: String,
673        source: MappingSource,
674        condition: Option<ConditionExpr>,
675    },
676
677    /// Increment a field value (for conditional aggregations)
678    IncrementField {
679        target_field: String,
680        increment_by: i64,
681        condition: Option<ConditionExpr>,
682    },
683}
684
685/// A sibling instruction a [`HookAction::RegisterPdaMapping`] reads from: every
686/// instruction of `instruction_type` in the same transaction (other than the
687/// hook's own instruction), whether it arrived before or after the hook's.
688#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
689pub struct SiblingSelector {
690    /// The sibling's event type, e.g. `raise::ContributeIxState`.
691    pub instruction_type: String,
692    /// `pda_field` is a path in the sibling rather than in the hook's
693    /// instruction.
694    #[serde(default, skip_serializing_if = "is_false")]
695    pub pda_from_sibling: bool,
696    /// `seed_field` is a path in the sibling rather than in the hook's
697    /// instruction.
698    #[serde(default, skip_serializing_if = "is_false")]
699    pub seed_from_sibling: bool,
700}
701
702fn is_false(value: &bool) -> bool {
703    !*value
704}
705
706/// Simple condition expression (Level 1 - basic comparisons only)
707#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct ConditionExpr {
709    /// Expression as string (will be parsed and validated)
710    pub expression: String,
711
712    /// Parsed representation (for validation and execution)
713    pub parsed: Option<ParsedCondition>,
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize)]
717pub enum ParsedCondition {
718    /// Binary comparison: field op value
719    Comparison {
720        field: FieldPath,
721        op: ComparisonOp,
722        value: serde_json::Value,
723    },
724
725    /// Logical AND/OR
726    Logical {
727        op: LogicalOp,
728        conditions: Vec<ParsedCondition>,
729    },
730
731    /// A boolean expression over the incoming update that gates the write
732    /// (write-gating conditions, v2). Field references read the source event,
733    /// as in `MappingSource::FromExpr`. The mapping applies only when it
734    /// evaluates to `true`, for every population strategy, and `&&`, `||`
735    /// and `!` are honoured; `null`, `false` or a failing expression skip it.
736    /// The other variants keep their original semantics.
737    Guard { expr: ComputedExpr },
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
741pub enum ComparisonOp {
742    Equal,
743    NotEqual,
744    GreaterThan,
745    GreaterThanOrEqual,
746    LessThan,
747    LessThanOrEqual,
748}
749
750#[derive(Debug, Clone, Serialize, Deserialize)]
751pub enum LogicalOp {
752    And,
753    Or,
754}
755
756/// Serializable version of HandlerSpec without phantom types
757#[derive(Debug, Clone, Serialize, Deserialize)]
758pub struct SerializableHandlerSpec {
759    pub source: SourceSpec,
760    pub key_resolution: KeyResolutionStrategy,
761    pub mappings: Vec<SerializableFieldMapping>,
762    pub conditions: Vec<Condition>,
763    pub emit: bool,
764}
765
766impl SerializableHandlerSpec {
767    pub fn normalize_event_names(&mut self) {
768        let SourceSpec::Source { type_name, .. } = &mut self.source;
769        *type_name = crate::event_type_helpers::canonicalize_event_type_name(type_name);
770
771        for mapping in &mut self.mappings {
772            if let Some(when) = &mut mapping.when {
773                *when = crate::event_type_helpers::canonicalize_event_type_name(when);
774            }
775            if let Some(stop) = &mut mapping.stop {
776                *stop = crate::event_type_helpers::canonicalize_event_type_name(stop);
777            }
778        }
779    }
780}
781
782#[derive(Debug, Clone)]
783pub struct TypedHandlerSpec<S> {
784    pub source: SourceSpec,
785    pub key_resolution: KeyResolutionStrategy,
786    pub mappings: Vec<TypedFieldMapping<S>>,
787    pub conditions: Vec<Condition>,
788    pub emit: bool,
789    _phantom: PhantomData<S>,
790}
791
792impl<S> TypedHandlerSpec<S> {
793    pub fn new(
794        source: SourceSpec,
795        key_resolution: KeyResolutionStrategy,
796        mappings: Vec<TypedFieldMapping<S>>,
797        emit: bool,
798    ) -> Self {
799        TypedHandlerSpec {
800            source,
801            key_resolution,
802            mappings,
803            conditions: vec![],
804            emit,
805            _phantom: PhantomData,
806        }
807    }
808
809    /// Convert to serializable format
810    pub fn to_serializable(&self) -> SerializableHandlerSpec {
811        SerializableHandlerSpec {
812            source: self.source.clone(),
813            key_resolution: self.key_resolution.clone(),
814            mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
815            conditions: self.conditions.clone(),
816            emit: self.emit,
817        }
818    }
819
820    /// Create from serializable format
821    pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
822        TypedHandlerSpec {
823            source: spec.source,
824            key_resolution: spec.key_resolution,
825            mappings: spec
826                .mappings
827                .into_iter()
828                .map(|m| TypedFieldMapping::from_serializable(m))
829                .collect(),
830            conditions: spec.conditions,
831            emit: spec.emit,
832            _phantom: PhantomData,
833        }
834    }
835}
836
837#[derive(Debug, Clone, Serialize, Deserialize)]
838pub enum KeyResolutionStrategy {
839    Embedded {
840        primary_field: FieldPath,
841    },
842    /// The event field holds a value a lookup index maps to the primary key.
843    Lookup {
844        primary_field: FieldPath,
845        /// The lookup index this handler declares it resolves through (a
846        /// compiled index name, `<field>_lookup_index`). A declared lookup is
847        /// strict: it consults only this index, reached directly or after PDA
848        /// reverse lookups; a guarded write to the index's field updates the
849        /// index only when the guard passes; and the handler is deduplicated
850        /// per route, so a second route of the same instruction to the same
851        /// row still applies. Without it the runtime infers the index and
852        /// falls back to every other lookup index of the entity.
853        ///
854        /// Omitted when absent, so ASTs that do not declare one keep their
855        /// bytes and hashes. Runtimes that predate it ignore the field (the
856        /// strategy does not deny unknown fields) and resolve the lookup the
857        /// undeclared way.
858        #[serde(default, skip_serializing_if = "Option::is_none")]
859        index_name: Option<String>,
860    },
861    Computed {
862        primary_field: FieldPath,
863        compute_partition: ComputeFunction,
864    },
865    TemporalLookup {
866        lookup_field: FieldPath,
867        timestamp_field: FieldPath,
868        index_name: String,
869    },
870}
871
872#[derive(Debug, Clone, Serialize, Deserialize)]
873pub enum SourceSpec {
874    Source {
875        program_id: Option<String>,
876        discriminator: Option<Vec<u8>>,
877        type_name: String,
878        #[serde(default, skip_serializing_if = "Option::is_none")]
879        serialization: Option<IdlSerializationSnapshot>,
880        /// True when this handler listens to an account-state event (not an
881        /// instruction or custom event).  Set at code-generation time from
882        /// the structural source kind so the compiler does not need to rely
883        /// on naming-convention heuristics.
884        #[serde(default)]
885        is_account: bool,
886    },
887}
888
889/// Serializable version of FieldMapping without phantom types
890#[derive(Debug, Clone, Serialize, Deserialize)]
891pub struct SerializableFieldMapping {
892    pub target_path: String,
893    pub source: MappingSource,
894    pub transform: Option<Transformation>,
895    pub population: PopulationStrategy,
896    #[serde(default, skip_serializing_if = "Option::is_none")]
897    pub condition: Option<ConditionExpr>,
898    #[serde(default, skip_serializing_if = "Option::is_none")]
899    pub when: Option<String>,
900    #[serde(default, skip_serializing_if = "Option::is_none")]
901    pub stop: Option<String>,
902    #[serde(default = "default_emit", skip_serializing_if = "is_true")]
903    pub emit: bool,
904}
905
906fn default_emit() -> bool {
907    true
908}
909
910fn default_instruction_discriminant_size() -> usize {
911    8
912}
913
914fn is_true(value: &bool) -> bool {
915    *value
916}
917
918#[derive(Debug, Clone)]
919pub struct TypedFieldMapping<S> {
920    pub target_path: String,
921    pub source: MappingSource,
922    pub transform: Option<Transformation>,
923    pub population: PopulationStrategy,
924    pub condition: Option<ConditionExpr>,
925    pub when: Option<String>,
926    pub stop: Option<String>,
927    pub emit: bool,
928    _phantom: PhantomData<S>,
929}
930
931impl<S> TypedFieldMapping<S> {
932    pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
933        TypedFieldMapping {
934            target_path,
935            source,
936            transform: None,
937            population,
938            condition: None,
939            when: None,
940            stop: None,
941            emit: true,
942            _phantom: PhantomData,
943        }
944    }
945
946    pub fn with_transform(mut self, transform: Transformation) -> Self {
947        self.transform = Some(transform);
948        self
949    }
950
951    pub fn with_condition(mut self, condition: ConditionExpr) -> Self {
952        self.condition = Some(condition);
953        self
954    }
955
956    pub fn with_when(mut self, when: String) -> Self {
957        self.when = Some(when);
958        self
959    }
960
961    pub fn with_stop(mut self, stop: String) -> Self {
962        self.stop = Some(stop);
963        self
964    }
965
966    pub fn with_emit(mut self, emit: bool) -> Self {
967        self.emit = emit;
968        self
969    }
970
971    /// Convert to serializable format
972    pub fn to_serializable(&self) -> SerializableFieldMapping {
973        SerializableFieldMapping {
974            target_path: self.target_path.clone(),
975            source: self.source.clone(),
976            transform: self.transform.clone(),
977            population: self.population.clone(),
978            condition: self.condition.clone(),
979            when: self.when.clone(),
980            stop: self.stop.clone(),
981            emit: self.emit,
982        }
983    }
984
985    /// Create from serializable format
986    pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
987        TypedFieldMapping {
988            target_path: mapping.target_path,
989            source: mapping.source,
990            transform: mapping.transform,
991            population: mapping.population,
992            condition: mapping.condition,
993            when: mapping.when,
994            stop: mapping.stop,
995            emit: mapping.emit,
996            _phantom: PhantomData,
997        }
998    }
999}
1000
1001#[derive(Debug, Clone, Serialize, Deserialize)]
1002pub enum MappingSource {
1003    FromSource {
1004        path: FieldPath,
1005        default: Option<Value>,
1006        transform: Option<Transformation>,
1007    },
1008    Constant(Value),
1009    Computed {
1010        inputs: Vec<FieldPath>,
1011        function: ComputeFunction,
1012    },
1013    FromState {
1014        path: String,
1015    },
1016    AsEvent {
1017        fields: Vec<Box<MappingSource>>,
1018    },
1019    WholeSource,
1020    /// Similar to WholeSource but with field-level transformations
1021    /// Used by #[capture] macro to apply transforms to specific fields in an account
1022    AsCapture {
1023        field_transforms: BTreeMap<String, Transformation>,
1024    },
1025    /// From instruction context (timestamp, slot, signature)
1026    /// Used by #[track_from] with special fields like __timestamp
1027    FromContext {
1028        field: String,
1029    },
1030    /// An expression evaluated against the incoming event when the handler
1031    /// runs. Field references read the event; `ContextSlot` and
1032    /// `ContextTimestamp` read the update context. Feeds any population
1033    /// strategy, and supplies the key when it maps the primary key.
1034    FromExpr {
1035        expr: ComputedExpr,
1036    },
1037}
1038
1039impl MappingSource {
1040    pub fn with_transform(self, transform: Transformation) -> Self {
1041        match self {
1042            MappingSource::FromSource {
1043                path,
1044                default,
1045                transform: _,
1046            } => MappingSource::FromSource {
1047                path,
1048                default,
1049                transform: Some(transform),
1050            },
1051            other => other,
1052        }
1053    }
1054}
1055
1056#[derive(Debug, Clone, Serialize, Deserialize)]
1057pub enum ComputeFunction {
1058    Sum,
1059    Concat,
1060    Format(String),
1061    Custom(String),
1062}
1063
1064#[derive(Debug, Clone, Serialize, Deserialize)]
1065pub struct Condition {
1066    pub field: FieldPath,
1067    pub operator: ConditionOp,
1068    pub value: Value,
1069}
1070
1071#[derive(Debug, Clone, Serialize, Deserialize)]
1072pub enum ConditionOp {
1073    Equals,
1074    NotEquals,
1075    GreaterThan,
1076    LessThan,
1077    Contains,
1078    Exists,
1079}
1080
1081/// Language-agnostic type information for fields
1082#[derive(Debug, Clone, Serialize, Deserialize)]
1083pub struct FieldTypeInfo {
1084    pub field_name: String,
1085    #[serde(default, skip_serializing_if = "Option::is_none")]
1086    pub raw_name: Option<String>,
1087    #[serde(default, skip_serializing_if = "Option::is_none")]
1088    pub canonical_name: Option<String>,
1089    pub rust_type_name: String, // Full Rust type: "Option<i64>", "Vec<Value>", etc.
1090    pub base_type: BaseType,    // Fundamental type classification
1091    #[serde(default, skip_serializing_if = "Option::is_none")]
1092    pub integer_kind: Option<IntegerKind>,
1093    pub is_optional: bool,           // true for Option<T>
1094    pub is_array: bool,              // true for Vec<T>
1095    pub inner_type: Option<String>,  // For Option<T> or Vec<T>, store the inner type
1096    pub source_path: Option<String>, // Path to source field if this is mapped
1097    /// Resolved type information for complex types (instructions, accounts, custom types)
1098    #[serde(default)]
1099    pub resolved_type: Option<ResolvedStructType>,
1100    #[serde(default = "default_emit", skip_serializing_if = "is_true")]
1101    pub emit: bool,
1102}
1103
1104/// Resolved structure type with field information from IDL
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1106pub struct ResolvedStructType {
1107    pub type_name: String,
1108    pub fields: Vec<ResolvedField>,
1109    pub is_instruction: bool,
1110    pub is_account: bool,
1111    pub is_event: bool,
1112    /// If true, this is an enum type and enum_variants should be used instead of fields
1113    #[serde(default)]
1114    pub is_enum: bool,
1115    /// For enum types, list of variant names
1116    #[serde(default)]
1117    pub enum_variants: Vec<String>,
1118}
1119
1120/// A resolved field within a complex type
1121#[derive(Debug, Clone, Serialize, Deserialize)]
1122pub struct ResolvedField {
1123    pub field_name: String,
1124    #[serde(default, skip_serializing_if = "Option::is_none")]
1125    pub raw_name: Option<String>,
1126    #[serde(default, skip_serializing_if = "Option::is_none")]
1127    pub canonical_name: Option<String>,
1128    pub field_type: String,
1129    pub base_type: BaseType,
1130    #[serde(default, skip_serializing_if = "Option::is_none")]
1131    pub integer_kind: Option<IntegerKind>,
1132    pub is_optional: bool,
1133    pub is_array: bool,
1134}
1135
1136#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1137pub enum IntegerKind {
1138    U8,
1139    U16,
1140    U32,
1141    U64,
1142    U128,
1143    Usize,
1144    I8,
1145    I16,
1146    I32,
1147    I64,
1148    I128,
1149    Isize,
1150}
1151
1152impl IntegerKind {
1153    pub fn from_rust_type(type_str: &str) -> Option<Self> {
1154        match type_str.trim() {
1155            "u8" => Some(Self::U8),
1156            "u16" => Some(Self::U16),
1157            "u32" => Some(Self::U32),
1158            "u64" => Some(Self::U64),
1159            "u128" => Some(Self::U128),
1160            "usize" => Some(Self::Usize),
1161            "i8" => Some(Self::I8),
1162            "i16" => Some(Self::I16),
1163            "i32" => Some(Self::I32),
1164            "i64" => Some(Self::I64),
1165            "i128" => Some(Self::I128),
1166            "isize" => Some(Self::Isize),
1167            _ => None,
1168        }
1169    }
1170
1171    pub fn is_bigint(self) -> bool {
1172        matches!(self, Self::U64 | Self::U128 | Self::I64 | Self::I128)
1173    }
1174}
1175
1176/// Language-agnostic base type classification
1177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1178pub enum BaseType {
1179    // Numeric types
1180    Integer, // i8, i16, i32, i64, u8, u16, u32, u64, usize, isize
1181    Float,   // f32, f64
1182    // Text types
1183    String, // String, &str
1184    // Boolean
1185    Boolean, // bool
1186    // Complex types
1187    Object, // Custom structs, HashMap, etc.
1188    Array,  // Vec<T>, arrays
1189    Binary, // Bytes, binary data
1190    // Special types
1191    Timestamp, // Detected from field names ending in _at, _time, etc.
1192    Pubkey,    // Solana public key (Base58 encoded)
1193    Any,       // serde_json::Value, unknown types
1194}
1195
1196/// Represents a logical section/group of fields in the entity
1197#[derive(Debug, Clone, Serialize, Deserialize)]
1198pub struct EntitySection {
1199    pub name: String,
1200    pub fields: Vec<FieldTypeInfo>,
1201    pub is_nested_struct: bool,
1202    pub parent_field: Option<String>, // If this section comes from a nested struct field
1203}
1204
1205impl FieldTypeInfo {
1206    pub fn new(field_name: String, rust_type_name: String) -> Self {
1207        let (base_type, integer_kind, is_optional, is_array, inner_type) =
1208            Self::analyze_rust_type(&rust_type_name);
1209        let canonical_name = to_camel_case_owned(&field_name);
1210
1211        FieldTypeInfo {
1212            field_name: field_name.clone(),
1213            raw_name: Some(field_name.clone()),
1214            canonical_name: Some(canonical_name),
1215            rust_type_name,
1216            base_type: Self::infer_semantic_type(&field_name, base_type),
1217            integer_kind,
1218            is_optional,
1219            is_array,
1220            inner_type,
1221            source_path: None,
1222            resolved_type: None,
1223            emit: true,
1224        }
1225    }
1226
1227    pub fn with_source_path(mut self, source_path: String) -> Self {
1228        self.source_path = Some(source_path);
1229        self
1230    }
1231
1232    pub fn raw_field_name(&self) -> &str {
1233        self.raw_name.as_deref().unwrap_or(self.field_name.as_str())
1234    }
1235
1236    pub fn canonical_field_name(&self) -> String {
1237        self.canonical_name
1238            .clone()
1239            .unwrap_or_else(|| to_camel_case_owned(self.raw_field_name()))
1240    }
1241
1242    pub fn effective_integer_kind(&self) -> Option<IntegerKind> {
1243        self.integer_kind.or_else(|| {
1244            IntegerKind::from_rust_type(
1245                self.inner_type
1246                    .as_deref()
1247                    .unwrap_or(self.rust_type_name.as_str()),
1248            )
1249        })
1250    }
1251
1252    /// Analyze a Rust type string and extract structural information
1253    fn analyze_rust_type(
1254        rust_type: &str,
1255    ) -> (BaseType, Option<IntegerKind>, bool, bool, Option<String>) {
1256        let type_str = rust_type.trim();
1257
1258        // Handle Option<T>
1259        if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1260            let (inner_base_type, inner_integer_kind, _, inner_is_array, inner_inner_type) =
1261                Self::analyze_rust_type(&inner);
1262            return (
1263                inner_base_type,
1264                inner_integer_kind,
1265                true,
1266                inner_is_array,
1267                inner_inner_type.or(Some(inner)),
1268            );
1269        }
1270
1271        // Handle Vec<T>
1272        if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1273            let (_inner_base_type, inner_integer_kind, inner_is_optional, _, inner_inner_type) =
1274                Self::analyze_rust_type(&inner);
1275            return (
1276                BaseType::Array,
1277                inner_integer_kind,
1278                inner_is_optional,
1279                true,
1280                inner_inner_type.or(Some(inner)),
1281            );
1282        }
1283
1284        // Handle primitive types
1285        let integer_kind = IntegerKind::from_rust_type(type_str);
1286        let base_type = match integer_kind {
1287            Some(_) => BaseType::Integer,
1288            None => match type_str {
1289                "f32" | "f64" => BaseType::Float,
1290                "bool" => BaseType::Boolean,
1291                "String" | "&str" | "str" => BaseType::String,
1292                "Value" | "serde_json::Value" => BaseType::Any,
1293                "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1294                _ => {
1295                    // Check for binary types
1296                    if type_str.contains("Bytes") || type_str.contains("bytes") {
1297                        BaseType::Binary
1298                    } else if type_str.contains("Pubkey") {
1299                        BaseType::Pubkey
1300                    } else {
1301                        BaseType::Object
1302                    }
1303                }
1304            },
1305        };
1306
1307        (base_type, integer_kind, false, false, None)
1308    }
1309
1310    /// Extract inner type from generic like "Option<T>" -> "T"
1311    fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1312        let pattern = format!("{}<", generic_name);
1313        if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1314            let start = pattern.len();
1315            let end = type_str.len() - 1;
1316            if end > start {
1317                return Some(type_str[start..end].trim().to_string());
1318            }
1319        }
1320        None
1321    }
1322
1323    /// Infer semantic type based on field name patterns
1324    fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1325        let lower_name = field_name.to_lowercase();
1326
1327        // If already classified as integer, check if it should be timestamp
1328        if base_type == BaseType::Integer
1329            && (lower_name.ends_with("_at")
1330                || lower_name.ends_with("_time")
1331                || lower_name.contains("timestamp")
1332                || lower_name.contains("created")
1333                || lower_name.contains("settled")
1334                || lower_name.contains("activated"))
1335        {
1336            return BaseType::Timestamp;
1337        }
1338
1339        base_type
1340    }
1341}
1342
1343impl ResolvedField {
1344    pub fn raw_field_name(&self) -> &str {
1345        self.raw_name.as_deref().unwrap_or(self.field_name.as_str())
1346    }
1347
1348    pub fn canonical_field_name(&self) -> String {
1349        self.canonical_name
1350            .clone()
1351            .unwrap_or_else(|| to_camel_case_owned(self.raw_field_name()))
1352    }
1353
1354    pub fn effective_integer_kind(&self) -> Option<IntegerKind> {
1355        self.integer_kind
1356            .or_else(|| IntegerKind::from_rust_type(self.field_type.as_str()))
1357    }
1358}
1359
1360pub(crate) fn to_camel_case_owned(s: &str) -> String {
1361    let mut result = String::new();
1362    let mut uppercase_next = false;
1363
1364    for ch in s.chars() {
1365        if matches!(ch, '_' | '-' | '.') {
1366            uppercase_next = true;
1367            continue;
1368        }
1369
1370        if result.is_empty() {
1371            result.extend(ch.to_lowercase());
1372            continue;
1373        }
1374
1375        if uppercase_next {
1376            result.extend(ch.to_uppercase());
1377            uppercase_next = false;
1378        } else {
1379            result.push(ch);
1380        }
1381    }
1382
1383    result
1384}
1385
1386pub trait FieldAccessor<S> {
1387    fn path(&self) -> String;
1388}
1389
1390// ============================================================================
1391// SerializableStreamSpec Implementation
1392// ============================================================================
1393
1394impl SerializableStreamSpec {
1395    /// Compute deterministic content hash (SHA256 of canonical JSON).
1396    ///
1397    /// The hash is computed over the entire spec except the content_hash field itself,
1398    /// ensuring the same AST always produces the same hash regardless of when it was
1399    /// generated or by whom.
1400    pub fn compute_content_hash(&self) -> String {
1401        use sha2::{Digest, Sha256};
1402
1403        // Clone and clear the hash field for computation
1404        let mut spec_for_hash = self.clone();
1405        spec_for_hash.content_hash = None;
1406
1407        // Serialize to JSON (serde_json produces consistent output for the same struct)
1408        let json =
1409            serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1410
1411        // Compute SHA256 hash
1412        let mut hasher = Sha256::new();
1413        hasher.update(json.as_bytes());
1414        let result = hasher.finalize();
1415
1416        // Return hex-encoded hash
1417        hex::encode(result)
1418    }
1419
1420    /// Verify that the content_hash matches the computed hash.
1421    /// Returns true if hash is valid or not set.
1422    pub fn verify_content_hash(&self) -> bool {
1423        match &self.content_hash {
1424            Some(hash) => {
1425                let computed = self.compute_content_hash();
1426                hash == &computed
1427            }
1428            None => true, // No hash to verify
1429        }
1430    }
1431
1432    /// Set the content_hash field to the computed hash.
1433    pub fn with_content_hash(mut self) -> Self {
1434        self.content_hash = Some(self.compute_content_hash());
1435        self
1436    }
1437}
1438
1439// ============================================================================
1440// PDA and Instruction Types — For SDK code generation
1441// ============================================================================
1442
1443/// PDA (Program-Derived Address) definition for the stack-level registry.
1444/// PDAs defined here can be referenced by instructions via `pdaRef`.
1445#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1446pub struct PdaDefinition {
1447    /// Human-readable name (e.g., "miner", "bondingCurve")
1448    pub name: String,
1449
1450    /// Seeds for PDA derivation, in order
1451    pub seeds: Vec<PdaSeedDef>,
1452
1453    /// Program ID that owns this PDA.
1454    /// If None, uses the stack's primary programId.
1455    #[serde(default, skip_serializing_if = "Option::is_none")]
1456    pub program_id: Option<String>,
1457
1458    /// Dynamic program selector for cross-program PDAs. Static programs stay
1459    /// in `program_id` for backward-compatible artifact serialization.
1460    #[serde(default, skip_serializing_if = "Option::is_none")]
1461    pub program: Option<PdaProgramDef>,
1462}
1463
1464#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1465#[serde(tag = "type", rename_all = "camelCase")]
1466pub enum PdaProgramDef {
1467    AccountRef { account_name: String },
1468    ArgRef { arg_name: String },
1469}
1470
1471/// Single seed in a PDA derivation.
1472#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1473#[serde(tag = "type", rename_all = "camelCase")]
1474pub enum PdaSeedDef {
1475    /// Static string seed: "miner" → "miner".as_bytes()
1476    Literal { value: String },
1477
1478    /// Static byte array (for non-UTF8 seeds)
1479    Bytes { value: Vec<u8> },
1480
1481    /// Reference to an instruction argument: arg("roundId") → args.roundId as bytes
1482    ArgRef {
1483        arg_name: String,
1484        /// Optional type hint for serialization (e.g., "u64", "pubkey")
1485        #[serde(default, skip_serializing_if = "Option::is_none")]
1486        arg_type: Option<String>,
1487    },
1488
1489    /// Reference to another account in the instruction: account("mint") → accounts.mint pubkey
1490    AccountRef { account_name: String },
1491}
1492
1493/// How an instruction account's address is determined.
1494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1495#[serde(tag = "category", rename_all = "camelCase")]
1496pub enum AccountResolution {
1497    /// Must sign the transaction. Generated handlers require the caller to
1498    /// provide this account unless the account metadata explicitly opts into
1499    /// wallet or generated signer behavior.
1500    Signer,
1501
1502    /// Fixed known address (e.g., System Program, Token Program)
1503    Known { address: String },
1504
1505    /// Reference to a PDA in the stack's pdas registry
1506    PdaRef { pda_name: String },
1507
1508    /// Inline PDA definition (for one-off PDAs not in the registry)
1509    PdaInline {
1510        seeds: Vec<PdaSeedDef>,
1511        #[serde(default, skip_serializing_if = "Option::is_none")]
1512        program_id: Option<String>,
1513        #[serde(default, skip_serializing_if = "Option::is_none")]
1514        program: Option<PdaProgramDef>,
1515    },
1516
1517    /// User must provide at call time via options.accounts
1518    UserProvided,
1519}
1520
1521/// Account metadata for an instruction.
1522#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1523pub struct InstructionAccountDef {
1524    /// Account name (e.g., "user", "mint", "bondingCurve")
1525    pub name: String,
1526
1527    /// Whether this account must sign the transaction
1528    #[serde(default)]
1529    pub is_signer: bool,
1530
1531    /// Whether this account is writable
1532    #[serde(default)]
1533    pub is_writable: bool,
1534
1535    /// How this account's address is resolved
1536    pub resolution: AccountResolution,
1537
1538    /// Whether this account can be omitted (optional accounts)
1539    #[serde(default)]
1540    pub is_optional: bool,
1541
1542    /// Documentation from IDL
1543    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1544    pub docs: Vec<String>,
1545}
1546
1547/// Argument definition for an instruction.
1548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1549#[serde(rename_all = "camelCase")]
1550pub struct InstructionAmountHint {
1551    pub decimals_source: AmountDecimalsSource,
1552}
1553
1554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1555#[serde(
1556    tag = "kind",
1557    rename_all = "camelCase",
1558    rename_all_fields = "camelCase"
1559)]
1560pub enum AmountDecimalsSource {
1561    ArgMint { arg_name: String },
1562    ArgDecimals { arg_name: String },
1563    KnownAccount { account_name: String },
1564    Constant { decimals: u8 },
1565}
1566
1567/// Argument definition for an instruction.
1568#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1569pub struct InstructionArgDef {
1570    /// Argument name
1571    pub name: String,
1572
1573    /// Type from IDL (e.g., "u64", "bool", "pubkey", "Option<u64>")
1574    #[serde(rename = "type")]
1575    pub arg_type: String,
1576
1577    /// Documentation from IDL
1578    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1579    pub docs: Vec<String>,
1580
1581    /// Optional amount-resolution metadata for semantic SDK wrappers.
1582    #[serde(default, skip_serializing_if = "Option::is_none")]
1583    pub amount_hint: Option<InstructionAmountHint>,
1584}
1585
1586/// Full instruction definition in the AST.
1587#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1588pub struct InstructionDef {
1589    /// Instruction name (e.g., "buy", "sell", "automate")
1590    pub name: String,
1591
1592    /// Discriminator bytes (8 bytes for Anchor, 1 byte for Steel)
1593    pub discriminator: Vec<u8>,
1594
1595    /// Size of discriminator in bytes (for buffer allocation)
1596    #[serde(default = "default_instruction_discriminant_size")]
1597    pub discriminator_size: usize,
1598
1599    /// Accounts required by this instruction, in order
1600    pub accounts: Vec<InstructionAccountDef>,
1601
1602    /// Arguments for this instruction, in order
1603    pub args: Vec<InstructionArgDef>,
1604
1605    /// Error definitions specific to this instruction
1606    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1607    pub errors: Vec<IdlErrorSnapshot>,
1608
1609    /// Program ID for this instruction (usually same as stack's programId)
1610    #[serde(default, skip_serializing_if = "Option::is_none")]
1611    pub program_id: Option<String>,
1612
1613    /// Documentation from IDL
1614    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1615    pub docs: Vec<String>,
1616}
1617
1618// ============================================================================
1619// Stack Spec — Unified multi-entity AST format
1620// ============================================================================
1621
1622/// A unified stack specification containing all entities.
1623/// Internal compiled stack representation embedded by generated code.
1624#[derive(Debug, Clone, Serialize, Deserialize)]
1625pub struct SerializableStackSpec {
1626    /// AST schema version for backward compatibility
1627    /// Uses semver format (e.g., "0.0.1")
1628    #[serde(default = "default_ast_version")]
1629    pub ast_version: String,
1630    /// Stack name (PascalCase, derived from module ident)
1631    pub stack_name: String,
1632    /// Program IDs (one per IDL, in order)
1633    #[serde(default)]
1634    pub program_ids: Vec<String>,
1635    /// IDL snapshots (one per program)
1636    #[serde(default)]
1637    pub idls: Vec<IdlSnapshot>,
1638    /// Exact public program specifications, in the same order as `program_ids` and `idls`.
1639    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1640    pub program_specs: Vec<arete_hash::ProgramSpecV1>,
1641    /// All entity specifications in this stack
1642    pub entities: Vec<SerializableStreamSpec>,
1643    /// PDA registry - defines all PDAs for the stack, grouped by program name
1644    /// Outer key is program name (e.g., "ore", "entropy"), inner key is PDA name
1645    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1646    pub pdas: BTreeMap<String, BTreeMap<String, PdaDefinition>>,
1647    /// Instruction definitions for SDK code generation
1648    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1649    pub instructions: Vec<InstructionDef>,
1650    /// Deterministic content hash of the entire stack
1651    #[serde(default, skip_serializing_if = "Option::is_none")]
1652    pub content_hash: Option<String>,
1653}
1654
1655impl SerializableStackSpec {
1656    /// Normalize legacy event names emitted by older AST generators.
1657    pub fn normalize_event_names(&mut self) {
1658        for entity in &mut self.entities {
1659            entity.normalize_event_names();
1660        }
1661    }
1662
1663    /// Compute deterministic content hash (SHA256 of canonical JSON).
1664    pub fn compute_content_hash(&self) -> String {
1665        use sha2::{Digest, Sha256};
1666        let mut spec_for_hash = self.clone();
1667        spec_for_hash.content_hash = None;
1668        let json = serde_json::to_string(&spec_for_hash)
1669            .expect("Failed to serialize stack spec for hashing");
1670        let mut hasher = Sha256::new();
1671        hasher.update(json.as_bytes());
1672        hex::encode(hasher.finalize())
1673    }
1674
1675    pub fn with_content_hash(mut self) -> Self {
1676        self.content_hash = Some(self.compute_content_hash());
1677        self
1678    }
1679}
1680
1681#[cfg(test)]
1682mod tests {
1683    use super::{
1684        HookAction, IdentitySpec, InstructionHook, MappingSource, PopulationStrategy,
1685        SerializableFieldMapping, SerializableHandlerSpec, SerializableStackSpec,
1686        SerializableStreamSpec, SourceSpec, TypedStreamSpec, CURRENT_AST_VERSION,
1687    };
1688    use serde_json::Value;
1689
1690    fn legacy_stream_spec() -> SerializableStreamSpec {
1691        SerializableStreamSpec {
1692            ast_version: CURRENT_AST_VERSION.to_string(),
1693            state_name: "PumpfunToken".to_string(),
1694            program_id: None,
1695            idl: None,
1696            identity: IdentitySpec {
1697                primary_keys: vec!["id.mint".to_string()],
1698                lookup_indexes: vec![],
1699            },
1700            handlers: vec![SerializableHandlerSpec {
1701                source: SourceSpec::Source {
1702                    program_id: None,
1703                    discriminator: None,
1704                    type_name: "pump::buyIxState".to_string(),
1705                    serialization: None,
1706                    is_account: false,
1707                },
1708                key_resolution: super::KeyResolutionStrategy::Embedded {
1709                    primary_field: super::FieldPath::new(&["accounts", "mint"]),
1710                },
1711                mappings: vec![SerializableFieldMapping {
1712                    target_path: "info.last_buy_at".to_string(),
1713                    source: MappingSource::Constant(Value::Null),
1714                    transform: None,
1715                    population: PopulationStrategy::SetOnce,
1716                    condition: None,
1717                    when: Some("pump::sellIxState".to_string()),
1718                    stop: Some("pump::buy_exact_sol_inIxState".to_string()),
1719                    emit: true,
1720                }],
1721                conditions: vec![],
1722                emit: true,
1723            }],
1724            sections: vec![],
1725            field_mappings: Default::default(),
1726            resolver_hooks: vec![],
1727            instruction_hooks: vec![InstructionHook {
1728                instruction_type: "pump::buyIxState".to_string(),
1729                actions: vec![HookAction::SetField {
1730                    target_field: "info.last_buy_at".to_string(),
1731                    source: MappingSource::Constant(Value::Null),
1732                    condition: None,
1733                }],
1734                lookup_by: None,
1735            }],
1736            resolver_specs: vec![],
1737            computed_fields: vec![],
1738            computed_field_specs: vec![],
1739            content_hash: None,
1740            views: vec![],
1741            scope_field: None,
1742        }
1743    }
1744
1745    #[test]
1746    fn typed_stream_spec_from_serializable_normalizes_legacy_instruction_event_names() {
1747        let typed = TypedStreamSpec::<Value>::from_serializable(legacy_stream_spec());
1748
1749        let handler = &typed.handlers[0];
1750        let SourceSpec::Source { type_name, .. } = &handler.source;
1751        assert_eq!(type_name, "pump::BuyIxState");
1752        assert_eq!(
1753            handler.mappings[0].when.as_deref(),
1754            Some("pump::SellIxState")
1755        );
1756        assert_eq!(
1757            handler.mappings[0].stop.as_deref(),
1758            Some("pump::BuyExactSolInIxState")
1759        );
1760        assert_eq!(
1761            typed.instruction_hooks[0].instruction_type,
1762            "pump::BuyIxState"
1763        );
1764    }
1765
1766    #[test]
1767    fn stack_spec_normalize_event_names_updates_all_entities() {
1768        let mut stack = SerializableStackSpec {
1769            ast_version: CURRENT_AST_VERSION.to_string(),
1770            stack_name: "PumpStack".to_string(),
1771            program_ids: vec![],
1772            idls: vec![],
1773            program_specs: vec![],
1774            entities: vec![legacy_stream_spec()],
1775            pdas: Default::default(),
1776            instructions: vec![],
1777            content_hash: None,
1778        };
1779
1780        stack.normalize_event_names();
1781
1782        let SourceSpec::Source { type_name, .. } = &stack.entities[0].handlers[0].source;
1783        assert_eq!(type_name, "pump::BuyIxState");
1784        assert_eq!(
1785            stack.entities[0].instruction_hooks[0].instruction_type,
1786            "pump::BuyIxState"
1787        );
1788    }
1789
1790    #[test]
1791    fn field_type_info_recognizes_128_bit_integers() {
1792        let unsigned = super::FieldTypeInfo::new("amount".to_string(), "u128".to_string());
1793        assert_eq!(unsigned.base_type, super::BaseType::Integer);
1794        assert!(!unsigned.is_optional);
1795        assert_eq!(unsigned.integer_kind, Some(super::IntegerKind::U128));
1796        assert_eq!(unsigned.raw_field_name(), "amount");
1797        assert_eq!(unsigned.canonical_field_name(), "amount");
1798
1799        let signed = super::FieldTypeInfo::new("delta".to_string(), "Option<i128>".to_string());
1800        assert_eq!(signed.base_type, super::BaseType::Integer);
1801        assert!(signed.is_optional);
1802        assert_eq!(signed.integer_kind, Some(super::IntegerKind::I128));
1803        assert_eq!(signed.inner_type.as_deref(), Some("i128"));
1804    }
1805
1806    #[test]
1807    fn field_type_info_preserves_timestamp_integer_kind_and_names() {
1808        let timestamp =
1809            super::FieldTypeInfo::new("last_updated_at".to_string(), "Option<i64>".to_string());
1810
1811        assert_eq!(timestamp.base_type, super::BaseType::Timestamp);
1812        assert_eq!(timestamp.integer_kind, Some(super::IntegerKind::I64));
1813        assert!(timestamp.is_optional);
1814        assert_eq!(timestamp.raw_field_name(), "last_updated_at");
1815        assert_eq!(timestamp.canonical_field_name(), "lastUpdatedAt");
1816    }
1817}
1818
1819// ============================================================================
1820// View Pipeline Types - Composable View Definitions
1821// ============================================================================
1822
1823/// Sort order for view transforms
1824#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1825#[serde(rename_all = "lowercase")]
1826pub enum SortOrder {
1827    #[default]
1828    Asc,
1829    Desc,
1830}
1831
1832/// Comparison operators for predicates
1833#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1834pub enum CompareOp {
1835    Eq,
1836    Ne,
1837    Gt,
1838    Gte,
1839    Lt,
1840    Lte,
1841}
1842
1843/// Value in a predicate comparison
1844#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1845pub enum PredicateValue {
1846    /// Literal JSON value
1847    Literal(serde_json::Value),
1848    /// Dynamic runtime value (e.g., "now()" for current timestamp)
1849    Dynamic(String),
1850    /// Reference to another field
1851    Field(FieldPath),
1852}
1853
1854/// Predicate for filtering entities
1855#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1856pub enum Predicate {
1857    /// Field comparison: field op value
1858    Compare {
1859        field: FieldPath,
1860        op: CompareOp,
1861        value: PredicateValue,
1862    },
1863    /// Logical AND of predicates
1864    And(Vec<Predicate>),
1865    /// Logical OR of predicates
1866    Or(Vec<Predicate>),
1867    /// Negation
1868    Not(Box<Predicate>),
1869    /// Field exists (is not null)
1870    Exists { field: FieldPath },
1871}
1872
1873/// Transform operation in a view pipeline
1874#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1875pub enum ViewTransform {
1876    /// Filter entities matching a predicate
1877    Filter { predicate: Predicate },
1878
1879    /// Sort entities by a field
1880    Sort {
1881        key: FieldPath,
1882        #[serde(default)]
1883        order: SortOrder,
1884    },
1885
1886    /// Take first N entities (after sort)
1887    Take { count: usize },
1888
1889    /// Skip first N entities
1890    Skip { count: usize },
1891
1892    /// Take only the first entity (after sort) - produces Single output
1893    First,
1894
1895    /// Take only the last entity (after sort) - produces Single output
1896    Last,
1897
1898    /// Get entity with maximum value for field - produces Single output
1899    MaxBy { key: FieldPath },
1900
1901    /// Get entity with minimum value for field - produces Single output
1902    MinBy { key: FieldPath },
1903}
1904
1905/// Source for a view definition
1906#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1907pub enum ViewSource {
1908    /// Derive directly from entity mutations
1909    Entity { name: String },
1910    /// Derive from another view's output
1911    View { id: String },
1912}
1913
1914/// Output mode for a view
1915#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1916pub enum ViewOutput {
1917    /// Multiple entities (list-like semantics)
1918    #[default]
1919    Collection,
1920    /// Single entity (state-like semantics)
1921    Single,
1922    /// Keyed lookup by a specific field
1923    Keyed { key_field: FieldPath },
1924}
1925
1926/// Definition of a view in the pipeline
1927#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1928pub struct ViewDef {
1929    /// Unique view identifier (e.g., "OreRound/latest")
1930    pub id: String,
1931
1932    /// Source this view derives from
1933    pub source: ViewSource,
1934
1935    /// Pipeline of transforms to apply (in order)
1936    #[serde(default)]
1937    pub pipeline: Vec<ViewTransform>,
1938
1939    /// Output mode for this view
1940    #[serde(default)]
1941    pub output: ViewOutput,
1942}
1943
1944impl ViewDef {
1945    /// Create a new list view for an entity
1946    pub fn list(entity_name: &str) -> Self {
1947        ViewDef {
1948            id: format!("{}/list", entity_name),
1949            source: ViewSource::Entity {
1950                name: entity_name.to_string(),
1951            },
1952            pipeline: vec![],
1953            output: ViewOutput::Collection,
1954        }
1955    }
1956
1957    /// Create a new state view for an entity
1958    pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1959        ViewDef {
1960            id: format!("{}/state", entity_name),
1961            source: ViewSource::Entity {
1962                name: entity_name.to_string(),
1963            },
1964            pipeline: vec![],
1965            output: ViewOutput::Keyed {
1966                key_field: FieldPath::new(key_field),
1967            },
1968        }
1969    }
1970
1971    /// Check if this view produces a single entity
1972    pub fn is_single(&self) -> bool {
1973        matches!(self.output, ViewOutput::Single)
1974    }
1975
1976    /// Check if any transform in the pipeline produces a single result
1977    pub fn has_single_transform(&self) -> bool {
1978        self.pipeline.iter().any(|t| {
1979            matches!(
1980                t,
1981                ViewTransform::First
1982                    | ViewTransform::Last
1983                    | ViewTransform::MaxBy { .. }
1984                    | ViewTransform::MinBy { .. }
1985            )
1986        })
1987    }
1988}
1989
1990#[macro_export]
1991macro_rules! define_accessor {
1992    ($name:ident, $state:ty, $path:expr) => {
1993        pub struct $name;
1994
1995        impl $crate::ast::FieldAccessor<$state> for $name {
1996            fn path(&self) -> String {
1997                $path.to_string()
1998            }
1999        }
2000    };
2001}