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    /// Exact predicates over captured sibling fields, including runtime context.
701    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
702    pub required_fields: std::collections::BTreeMap<String, serde_json::Value>,
703}
704
705fn is_false(value: &bool) -> bool {
706    !*value
707}
708
709/// Simple condition expression (Level 1 - basic comparisons only)
710#[derive(Debug, Clone, Serialize, Deserialize)]
711pub struct ConditionExpr {
712    /// Expression as string (will be parsed and validated)
713    pub expression: String,
714
715    /// Parsed representation (for validation and execution)
716    pub parsed: Option<ParsedCondition>,
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize)]
720pub enum ParsedCondition {
721    /// Binary comparison: field op value
722    Comparison {
723        field: FieldPath,
724        op: ComparisonOp,
725        value: serde_json::Value,
726    },
727
728    /// Logical AND/OR
729    Logical {
730        op: LogicalOp,
731        conditions: Vec<ParsedCondition>,
732    },
733
734    /// A boolean expression over the incoming update that gates the write
735    /// (write-gating conditions, v2). Field references read the source event,
736    /// as in `MappingSource::FromExpr`. The mapping applies only when it
737    /// evaluates to `true`, for every population strategy, and `&&`, `||`
738    /// and `!` are honoured; `null`, `false` or a failing expression skip it.
739    /// The other variants keep their original semantics.
740    Guard { expr: ComputedExpr },
741}
742
743#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
744pub enum ComparisonOp {
745    Equal,
746    NotEqual,
747    GreaterThan,
748    GreaterThanOrEqual,
749    LessThan,
750    LessThanOrEqual,
751}
752
753#[derive(Debug, Clone, Serialize, Deserialize)]
754pub enum LogicalOp {
755    And,
756    Or,
757}
758
759/// Serializable version of HandlerSpec without phantom types
760#[derive(Debug, Clone, Serialize, Deserialize)]
761pub struct SerializableHandlerSpec {
762    pub source: SourceSpec,
763    pub key_resolution: KeyResolutionStrategy,
764    pub mappings: Vec<SerializableFieldMapping>,
765    pub conditions: Vec<Condition>,
766    pub emit: bool,
767}
768
769impl SerializableHandlerSpec {
770    pub fn normalize_event_names(&mut self) {
771        let SourceSpec::Source { type_name, .. } = &mut self.source;
772        *type_name = crate::event_type_helpers::canonicalize_event_type_name(type_name);
773
774        for mapping in &mut self.mappings {
775            if let Some(when) = &mut mapping.when {
776                *when = crate::event_type_helpers::canonicalize_event_type_name(when);
777            }
778            if let Some(stop) = &mut mapping.stop {
779                *stop = crate::event_type_helpers::canonicalize_event_type_name(stop);
780            }
781        }
782    }
783}
784
785#[derive(Debug, Clone)]
786pub struct TypedHandlerSpec<S> {
787    pub source: SourceSpec,
788    pub key_resolution: KeyResolutionStrategy,
789    pub mappings: Vec<TypedFieldMapping<S>>,
790    pub conditions: Vec<Condition>,
791    pub emit: bool,
792    _phantom: PhantomData<S>,
793}
794
795impl<S> TypedHandlerSpec<S> {
796    pub fn new(
797        source: SourceSpec,
798        key_resolution: KeyResolutionStrategy,
799        mappings: Vec<TypedFieldMapping<S>>,
800        emit: bool,
801    ) -> Self {
802        TypedHandlerSpec {
803            source,
804            key_resolution,
805            mappings,
806            conditions: vec![],
807            emit,
808            _phantom: PhantomData,
809        }
810    }
811
812    /// Convert to serializable format
813    pub fn to_serializable(&self) -> SerializableHandlerSpec {
814        SerializableHandlerSpec {
815            source: self.source.clone(),
816            key_resolution: self.key_resolution.clone(),
817            mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
818            conditions: self.conditions.clone(),
819            emit: self.emit,
820        }
821    }
822
823    /// Create from serializable format
824    pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
825        TypedHandlerSpec {
826            source: spec.source,
827            key_resolution: spec.key_resolution,
828            mappings: spec
829                .mappings
830                .into_iter()
831                .map(|m| TypedFieldMapping::from_serializable(m))
832                .collect(),
833            conditions: spec.conditions,
834            emit: spec.emit,
835            _phantom: PhantomData,
836        }
837    }
838}
839
840#[derive(Debug, Clone, Serialize, Deserialize)]
841pub enum KeyResolutionStrategy {
842    Embedded {
843        primary_field: FieldPath,
844    },
845    /// The event field holds a value a lookup index maps to the primary key.
846    Lookup {
847        primary_field: FieldPath,
848        /// The lookup index this handler declares it resolves through (a
849        /// compiled index name, `<field>_lookup_index`). A declared lookup is
850        /// strict: it consults only this index, reached directly or after PDA
851        /// reverse lookups; a guarded write to the index's field updates the
852        /// index only when the guard passes; and the handler is deduplicated
853        /// per route, so a second route of the same instruction to the same
854        /// row still applies. Without it the runtime infers the index and
855        /// falls back to every other lookup index of the entity.
856        ///
857        /// Omitted when absent, so ASTs that do not declare one keep their
858        /// bytes and hashes. Runtimes that predate it ignore the field (the
859        /// strategy does not deny unknown fields) and resolve the lookup the
860        /// undeclared way.
861        #[serde(default, skip_serializing_if = "Option::is_none")]
862        index_name: Option<String>,
863    },
864    Computed {
865        primary_field: FieldPath,
866        compute_partition: ComputeFunction,
867    },
868    TemporalLookup {
869        lookup_field: FieldPath,
870        timestamp_field: FieldPath,
871        index_name: String,
872    },
873}
874
875#[derive(Debug, Clone, Serialize, Deserialize)]
876pub enum SourceSpec {
877    Source {
878        program_id: Option<String>,
879        discriminator: Option<Vec<u8>>,
880        type_name: String,
881        #[serde(default, skip_serializing_if = "Option::is_none")]
882        serialization: Option<IdlSerializationSnapshot>,
883        /// True when this handler listens to an account-state event (not an
884        /// instruction or custom event).  Set at code-generation time from
885        /// the structural source kind so the compiler does not need to rely
886        /// on naming-convention heuristics.
887        #[serde(default)]
888        is_account: bool,
889    },
890}
891
892/// Serializable version of FieldMapping without phantom types
893#[derive(Debug, Clone, Serialize, Deserialize)]
894pub struct SerializableFieldMapping {
895    pub target_path: String,
896    pub source: MappingSource,
897    pub transform: Option<Transformation>,
898    pub population: PopulationStrategy,
899    #[serde(default, skip_serializing_if = "Option::is_none")]
900    pub condition: Option<ConditionExpr>,
901    #[serde(default, skip_serializing_if = "Option::is_none")]
902    pub when: Option<String>,
903    #[serde(default, skip_serializing_if = "Option::is_none")]
904    pub stop: Option<String>,
905    #[serde(default = "default_emit", skip_serializing_if = "is_true")]
906    pub emit: bool,
907}
908
909fn default_emit() -> bool {
910    true
911}
912
913fn default_instruction_discriminant_size() -> usize {
914    8
915}
916
917fn is_true(value: &bool) -> bool {
918    *value
919}
920
921#[derive(Debug, Clone)]
922pub struct TypedFieldMapping<S> {
923    pub target_path: String,
924    pub source: MappingSource,
925    pub transform: Option<Transformation>,
926    pub population: PopulationStrategy,
927    pub condition: Option<ConditionExpr>,
928    pub when: Option<String>,
929    pub stop: Option<String>,
930    pub emit: bool,
931    _phantom: PhantomData<S>,
932}
933
934impl<S> TypedFieldMapping<S> {
935    pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
936        TypedFieldMapping {
937            target_path,
938            source,
939            transform: None,
940            population,
941            condition: None,
942            when: None,
943            stop: None,
944            emit: true,
945            _phantom: PhantomData,
946        }
947    }
948
949    pub fn with_transform(mut self, transform: Transformation) -> Self {
950        self.transform = Some(transform);
951        self
952    }
953
954    pub fn with_condition(mut self, condition: ConditionExpr) -> Self {
955        self.condition = Some(condition);
956        self
957    }
958
959    pub fn with_when(mut self, when: String) -> Self {
960        self.when = Some(when);
961        self
962    }
963
964    pub fn with_stop(mut self, stop: String) -> Self {
965        self.stop = Some(stop);
966        self
967    }
968
969    pub fn with_emit(mut self, emit: bool) -> Self {
970        self.emit = emit;
971        self
972    }
973
974    /// Convert to serializable format
975    pub fn to_serializable(&self) -> SerializableFieldMapping {
976        SerializableFieldMapping {
977            target_path: self.target_path.clone(),
978            source: self.source.clone(),
979            transform: self.transform.clone(),
980            population: self.population.clone(),
981            condition: self.condition.clone(),
982            when: self.when.clone(),
983            stop: self.stop.clone(),
984            emit: self.emit,
985        }
986    }
987
988    /// Create from serializable format
989    pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
990        TypedFieldMapping {
991            target_path: mapping.target_path,
992            source: mapping.source,
993            transform: mapping.transform,
994            population: mapping.population,
995            condition: mapping.condition,
996            when: mapping.when,
997            stop: mapping.stop,
998            emit: mapping.emit,
999            _phantom: PhantomData,
1000        }
1001    }
1002}
1003
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub enum MappingSource {
1006    FromSource {
1007        path: FieldPath,
1008        default: Option<Value>,
1009        transform: Option<Transformation>,
1010    },
1011    Constant(Value),
1012    Computed {
1013        inputs: Vec<FieldPath>,
1014        function: ComputeFunction,
1015    },
1016    FromState {
1017        path: String,
1018    },
1019    AsEvent {
1020        fields: Vec<Box<MappingSource>>,
1021    },
1022    WholeSource,
1023    /// Similar to WholeSource but with field-level transformations
1024    /// Used by #[capture] macro to apply transforms to specific fields in an account
1025    AsCapture {
1026        field_transforms: BTreeMap<String, Transformation>,
1027    },
1028    /// From instruction context (timestamp, slot, signature)
1029    /// Used by #[track_from] with special fields like __timestamp
1030    FromContext {
1031        field: String,
1032    },
1033    /// An expression evaluated against the incoming event when the handler
1034    /// runs. Field references read the event; `ContextSlot` and
1035    /// `ContextTimestamp` read the update context. Feeds any population
1036    /// strategy, and supplies the key when it maps the primary key.
1037    FromExpr {
1038        expr: ComputedExpr,
1039    },
1040}
1041
1042impl MappingSource {
1043    pub fn with_transform(self, transform: Transformation) -> Self {
1044        match self {
1045            MappingSource::FromSource {
1046                path,
1047                default,
1048                transform: _,
1049            } => MappingSource::FromSource {
1050                path,
1051                default,
1052                transform: Some(transform),
1053            },
1054            other => other,
1055        }
1056    }
1057}
1058
1059#[derive(Debug, Clone, Serialize, Deserialize)]
1060pub enum ComputeFunction {
1061    Sum,
1062    Concat,
1063    Format(String),
1064    Custom(String),
1065}
1066
1067#[derive(Debug, Clone, Serialize, Deserialize)]
1068pub struct Condition {
1069    pub field: FieldPath,
1070    pub operator: ConditionOp,
1071    pub value: Value,
1072}
1073
1074#[derive(Debug, Clone, Serialize, Deserialize)]
1075pub enum ConditionOp {
1076    Equals,
1077    NotEquals,
1078    GreaterThan,
1079    LessThan,
1080    Contains,
1081    Exists,
1082}
1083
1084/// Language-agnostic type information for fields
1085#[derive(Debug, Clone, Serialize, Deserialize)]
1086pub struct FieldTypeInfo {
1087    pub field_name: String,
1088    #[serde(default, skip_serializing_if = "Option::is_none")]
1089    pub raw_name: Option<String>,
1090    #[serde(default, skip_serializing_if = "Option::is_none")]
1091    pub canonical_name: Option<String>,
1092    pub rust_type_name: String, // Full Rust type: "Option<i64>", "Vec<Value>", etc.
1093    pub base_type: BaseType,    // Fundamental type classification
1094    #[serde(default, skip_serializing_if = "Option::is_none")]
1095    pub integer_kind: Option<IntegerKind>,
1096    pub is_optional: bool,           // true for Option<T>
1097    pub is_array: bool,              // true for Vec<T>
1098    pub inner_type: Option<String>,  // For Option<T> or Vec<T>, store the inner type
1099    pub source_path: Option<String>, // Path to source field if this is mapped
1100    /// Resolved type information for complex types (instructions, accounts, custom types)
1101    #[serde(default)]
1102    pub resolved_type: Option<ResolvedStructType>,
1103    #[serde(default = "default_emit", skip_serializing_if = "is_true")]
1104    pub emit: bool,
1105}
1106
1107/// Resolved structure type with field information from IDL
1108#[derive(Debug, Clone, Serialize, Deserialize)]
1109pub struct ResolvedStructType {
1110    pub type_name: String,
1111    pub fields: Vec<ResolvedField>,
1112    pub is_instruction: bool,
1113    pub is_account: bool,
1114    pub is_event: bool,
1115    /// If true, this is an enum type and enum_variants should be used instead of fields
1116    #[serde(default)]
1117    pub is_enum: bool,
1118    /// For enum types, list of variant names
1119    #[serde(default)]
1120    pub enum_variants: Vec<String>,
1121}
1122
1123/// A resolved field within a complex type
1124#[derive(Debug, Clone, Serialize, Deserialize)]
1125pub struct ResolvedField {
1126    pub field_name: String,
1127    #[serde(default, skip_serializing_if = "Option::is_none")]
1128    pub raw_name: Option<String>,
1129    #[serde(default, skip_serializing_if = "Option::is_none")]
1130    pub canonical_name: Option<String>,
1131    pub field_type: String,
1132    pub base_type: BaseType,
1133    #[serde(default, skip_serializing_if = "Option::is_none")]
1134    pub integer_kind: Option<IntegerKind>,
1135    pub is_optional: bool,
1136    pub is_array: bool,
1137}
1138
1139#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1140pub enum IntegerKind {
1141    U8,
1142    U16,
1143    U32,
1144    U64,
1145    U128,
1146    Usize,
1147    I8,
1148    I16,
1149    I32,
1150    I64,
1151    I128,
1152    Isize,
1153}
1154
1155impl IntegerKind {
1156    pub fn from_rust_type(type_str: &str) -> Option<Self> {
1157        match type_str.trim() {
1158            "u8" => Some(Self::U8),
1159            "u16" => Some(Self::U16),
1160            "u32" => Some(Self::U32),
1161            "u64" => Some(Self::U64),
1162            "u128" => Some(Self::U128),
1163            "usize" => Some(Self::Usize),
1164            "i8" => Some(Self::I8),
1165            "i16" => Some(Self::I16),
1166            "i32" => Some(Self::I32),
1167            "i64" => Some(Self::I64),
1168            "i128" => Some(Self::I128),
1169            "isize" => Some(Self::Isize),
1170            _ => None,
1171        }
1172    }
1173
1174    pub fn is_bigint(self) -> bool {
1175        matches!(self, Self::U64 | Self::U128 | Self::I64 | Self::I128)
1176    }
1177}
1178
1179/// Language-agnostic base type classification
1180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1181pub enum BaseType {
1182    // Numeric types
1183    Integer, // i8, i16, i32, i64, u8, u16, u32, u64, usize, isize
1184    Float,   // f32, f64
1185    // Text types
1186    String, // String, &str
1187    // Boolean
1188    Boolean, // bool
1189    // Complex types
1190    Object, // Custom structs, HashMap, etc.
1191    Array,  // Vec<T>, arrays
1192    Binary, // Bytes, binary data
1193    // Special types
1194    Timestamp, // Detected from field names ending in _at, _time, etc.
1195    Pubkey,    // Solana public key (Base58 encoded)
1196    Any,       // serde_json::Value, unknown types
1197}
1198
1199/// Represents a logical section/group of fields in the entity
1200#[derive(Debug, Clone, Serialize, Deserialize)]
1201pub struct EntitySection {
1202    pub name: String,
1203    pub fields: Vec<FieldTypeInfo>,
1204    pub is_nested_struct: bool,
1205    pub parent_field: Option<String>, // If this section comes from a nested struct field
1206}
1207
1208impl FieldTypeInfo {
1209    pub fn new(field_name: String, rust_type_name: String) -> Self {
1210        let (base_type, integer_kind, is_optional, is_array, inner_type) =
1211            Self::analyze_rust_type(&rust_type_name);
1212        let canonical_name = to_camel_case_owned(&field_name);
1213
1214        FieldTypeInfo {
1215            field_name: field_name.clone(),
1216            raw_name: Some(field_name.clone()),
1217            canonical_name: Some(canonical_name),
1218            rust_type_name,
1219            base_type: Self::infer_semantic_type(&field_name, base_type),
1220            integer_kind,
1221            is_optional,
1222            is_array,
1223            inner_type,
1224            source_path: None,
1225            resolved_type: None,
1226            emit: true,
1227        }
1228    }
1229
1230    pub fn with_source_path(mut self, source_path: String) -> Self {
1231        self.source_path = Some(source_path);
1232        self
1233    }
1234
1235    pub fn raw_field_name(&self) -> &str {
1236        self.raw_name.as_deref().unwrap_or(self.field_name.as_str())
1237    }
1238
1239    pub fn canonical_field_name(&self) -> String {
1240        self.canonical_name
1241            .clone()
1242            .unwrap_or_else(|| to_camel_case_owned(self.raw_field_name()))
1243    }
1244
1245    pub fn effective_integer_kind(&self) -> Option<IntegerKind> {
1246        self.integer_kind.or_else(|| {
1247            IntegerKind::from_rust_type(
1248                self.inner_type
1249                    .as_deref()
1250                    .unwrap_or(self.rust_type_name.as_str()),
1251            )
1252        })
1253    }
1254
1255    /// Analyze a Rust type string and extract structural information
1256    fn analyze_rust_type(
1257        rust_type: &str,
1258    ) -> (BaseType, Option<IntegerKind>, bool, bool, Option<String>) {
1259        let type_str = rust_type.trim();
1260
1261        // Handle Option<T>
1262        if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1263            let (inner_base_type, inner_integer_kind, _, inner_is_array, inner_inner_type) =
1264                Self::analyze_rust_type(&inner);
1265            return (
1266                inner_base_type,
1267                inner_integer_kind,
1268                true,
1269                inner_is_array,
1270                inner_inner_type.or(Some(inner)),
1271            );
1272        }
1273
1274        // Handle Vec<T>
1275        if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1276            let (_inner_base_type, inner_integer_kind, inner_is_optional, _, inner_inner_type) =
1277                Self::analyze_rust_type(&inner);
1278            return (
1279                BaseType::Array,
1280                inner_integer_kind,
1281                inner_is_optional,
1282                true,
1283                inner_inner_type.or(Some(inner)),
1284            );
1285        }
1286
1287        // Handle primitive types
1288        let integer_kind = IntegerKind::from_rust_type(type_str);
1289        let base_type = match integer_kind {
1290            Some(_) => BaseType::Integer,
1291            None => match type_str {
1292                "f32" | "f64" => BaseType::Float,
1293                "bool" => BaseType::Boolean,
1294                "String" | "&str" | "str" => BaseType::String,
1295                "Value" | "serde_json::Value" => BaseType::Any,
1296                "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1297                _ => {
1298                    // Check for binary types
1299                    if type_str.contains("Bytes") || type_str.contains("bytes") {
1300                        BaseType::Binary
1301                    } else if type_str.contains("Pubkey") {
1302                        BaseType::Pubkey
1303                    } else {
1304                        BaseType::Object
1305                    }
1306                }
1307            },
1308        };
1309
1310        (base_type, integer_kind, false, false, None)
1311    }
1312
1313    /// Extract inner type from generic like "Option<T>" -> "T"
1314    fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1315        let pattern = format!("{}<", generic_name);
1316        if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1317            let start = pattern.len();
1318            let end = type_str.len() - 1;
1319            if end > start {
1320                return Some(type_str[start..end].trim().to_string());
1321            }
1322        }
1323        None
1324    }
1325
1326    /// Infer semantic type based on field name patterns
1327    fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1328        let lower_name = field_name.to_lowercase();
1329
1330        // If already classified as integer, check if it should be timestamp
1331        if base_type == BaseType::Integer
1332            && (lower_name.ends_with("_at")
1333                || lower_name.ends_with("_time")
1334                || lower_name.contains("timestamp")
1335                || lower_name.contains("created")
1336                || lower_name.contains("settled")
1337                || lower_name.contains("activated"))
1338        {
1339            return BaseType::Timestamp;
1340        }
1341
1342        base_type
1343    }
1344}
1345
1346impl ResolvedField {
1347    pub fn raw_field_name(&self) -> &str {
1348        self.raw_name.as_deref().unwrap_or(self.field_name.as_str())
1349    }
1350
1351    pub fn canonical_field_name(&self) -> String {
1352        self.canonical_name
1353            .clone()
1354            .unwrap_or_else(|| to_camel_case_owned(self.raw_field_name()))
1355    }
1356
1357    pub fn effective_integer_kind(&self) -> Option<IntegerKind> {
1358        self.integer_kind
1359            .or_else(|| IntegerKind::from_rust_type(self.field_type.as_str()))
1360    }
1361}
1362
1363pub(crate) fn to_camel_case_owned(s: &str) -> String {
1364    let mut result = String::new();
1365    let mut uppercase_next = false;
1366
1367    for ch in s.chars() {
1368        if matches!(ch, '_' | '-' | '.') {
1369            uppercase_next = true;
1370            continue;
1371        }
1372
1373        if result.is_empty() {
1374            result.extend(ch.to_lowercase());
1375            continue;
1376        }
1377
1378        if uppercase_next {
1379            result.extend(ch.to_uppercase());
1380            uppercase_next = false;
1381        } else {
1382            result.push(ch);
1383        }
1384    }
1385
1386    result
1387}
1388
1389pub trait FieldAccessor<S> {
1390    fn path(&self) -> String;
1391}
1392
1393// ============================================================================
1394// SerializableStreamSpec Implementation
1395// ============================================================================
1396
1397impl SerializableStreamSpec {
1398    /// Compute deterministic content hash (SHA256 of canonical JSON).
1399    ///
1400    /// The hash is computed over the entire spec except the content_hash field itself,
1401    /// ensuring the same AST always produces the same hash regardless of when it was
1402    /// generated or by whom.
1403    pub fn compute_content_hash(&self) -> String {
1404        use sha2::{Digest, Sha256};
1405
1406        // Clone and clear the hash field for computation
1407        let mut spec_for_hash = self.clone();
1408        spec_for_hash.content_hash = None;
1409
1410        // Serialize to JSON (serde_json produces consistent output for the same struct)
1411        let json =
1412            serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1413
1414        // Compute SHA256 hash
1415        let mut hasher = Sha256::new();
1416        hasher.update(json.as_bytes());
1417        let result = hasher.finalize();
1418
1419        // Return hex-encoded hash
1420        hex::encode(result)
1421    }
1422
1423    /// Verify that the content_hash matches the computed hash.
1424    /// Returns true if hash is valid or not set.
1425    pub fn verify_content_hash(&self) -> bool {
1426        match &self.content_hash {
1427            Some(hash) => {
1428                let computed = self.compute_content_hash();
1429                hash == &computed
1430            }
1431            None => true, // No hash to verify
1432        }
1433    }
1434
1435    /// Set the content_hash field to the computed hash.
1436    pub fn with_content_hash(mut self) -> Self {
1437        self.content_hash = Some(self.compute_content_hash());
1438        self
1439    }
1440}
1441
1442// ============================================================================
1443// PDA and Instruction Types — For SDK code generation
1444// ============================================================================
1445
1446/// PDA (Program-Derived Address) definition for the stack-level registry.
1447/// PDAs defined here can be referenced by instructions via `pdaRef`.
1448#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1449pub struct PdaDefinition {
1450    /// Human-readable name (e.g., "miner", "bondingCurve")
1451    pub name: String,
1452
1453    /// Seeds for PDA derivation, in order
1454    pub seeds: Vec<PdaSeedDef>,
1455
1456    /// Program ID that owns this PDA.
1457    /// If None, uses the stack's primary programId.
1458    #[serde(default, skip_serializing_if = "Option::is_none")]
1459    pub program_id: Option<String>,
1460
1461    /// Dynamic program selector for cross-program PDAs. Static programs stay
1462    /// in `program_id` for backward-compatible artifact serialization.
1463    #[serde(default, skip_serializing_if = "Option::is_none")]
1464    pub program: Option<PdaProgramDef>,
1465}
1466
1467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1468#[serde(tag = "type", rename_all = "camelCase")]
1469pub enum PdaProgramDef {
1470    AccountRef { account_name: String },
1471    ArgRef { arg_name: String },
1472}
1473
1474/// Single seed in a PDA derivation.
1475#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1476#[serde(tag = "type", rename_all = "camelCase")]
1477pub enum PdaSeedDef {
1478    /// Static string seed: "miner" → "miner".as_bytes()
1479    Literal { value: String },
1480
1481    /// Static byte array (for non-UTF8 seeds)
1482    Bytes { value: Vec<u8> },
1483
1484    /// Reference to an instruction argument: arg("roundId") → args.roundId as bytes
1485    ArgRef {
1486        arg_name: String,
1487        /// Optional type hint for serialization (e.g., "u64", "pubkey")
1488        #[serde(default, skip_serializing_if = "Option::is_none")]
1489        arg_type: Option<String>,
1490    },
1491
1492    /// Reference to another account in the instruction: account("mint") → accounts.mint pubkey
1493    AccountRef { account_name: String },
1494}
1495
1496/// How an instruction account's address is determined.
1497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1498#[serde(tag = "category", rename_all = "camelCase")]
1499pub enum AccountResolution {
1500    /// Must sign the transaction. Generated handlers require the caller to
1501    /// provide this account unless the account metadata explicitly opts into
1502    /// wallet or generated signer behavior.
1503    Signer,
1504
1505    /// Fixed known address (e.g., System Program, Token Program)
1506    Known { address: String },
1507
1508    /// Reference to a PDA in the stack's pdas registry
1509    PdaRef { pda_name: String },
1510
1511    /// Inline PDA definition (for one-off PDAs not in the registry)
1512    PdaInline {
1513        seeds: Vec<PdaSeedDef>,
1514        #[serde(default, skip_serializing_if = "Option::is_none")]
1515        program_id: Option<String>,
1516        #[serde(default, skip_serializing_if = "Option::is_none")]
1517        program: Option<PdaProgramDef>,
1518    },
1519
1520    /// User must provide at call time via options.accounts
1521    UserProvided,
1522}
1523
1524/// Account metadata for an instruction.
1525#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1526pub struct InstructionAccountDef {
1527    /// Account name (e.g., "user", "mint", "bondingCurve")
1528    pub name: String,
1529
1530    /// Whether this account must sign the transaction
1531    #[serde(default)]
1532    pub is_signer: bool,
1533
1534    /// Whether this account is writable
1535    #[serde(default)]
1536    pub is_writable: bool,
1537
1538    /// How this account's address is resolved
1539    pub resolution: AccountResolution,
1540
1541    /// Whether this account can be omitted (optional accounts)
1542    #[serde(default)]
1543    pub is_optional: bool,
1544
1545    /// Documentation from IDL
1546    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1547    pub docs: Vec<String>,
1548}
1549
1550/// Argument definition for an instruction.
1551#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1552#[serde(rename_all = "camelCase")]
1553pub struct InstructionAmountHint {
1554    pub decimals_source: AmountDecimalsSource,
1555}
1556
1557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1558#[serde(
1559    tag = "kind",
1560    rename_all = "camelCase",
1561    rename_all_fields = "camelCase"
1562)]
1563pub enum AmountDecimalsSource {
1564    ArgMint { arg_name: String },
1565    ArgDecimals { arg_name: String },
1566    KnownAccount { account_name: String },
1567    Constant { decimals: u8 },
1568}
1569
1570/// Argument definition for an instruction.
1571#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1572pub struct InstructionArgDef {
1573    /// Argument name
1574    pub name: String,
1575
1576    /// Type from IDL (e.g., "u64", "bool", "pubkey", "Option<u64>")
1577    #[serde(rename = "type")]
1578    pub arg_type: String,
1579
1580    /// Documentation from IDL
1581    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1582    pub docs: Vec<String>,
1583
1584    /// Optional amount-resolution metadata for semantic SDK wrappers.
1585    #[serde(default, skip_serializing_if = "Option::is_none")]
1586    pub amount_hint: Option<InstructionAmountHint>,
1587}
1588
1589/// Full instruction definition in the AST.
1590#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1591pub struct InstructionDef {
1592    /// Instruction name (e.g., "buy", "sell", "automate")
1593    pub name: String,
1594
1595    /// Discriminator bytes (8 bytes for Anchor, 1 byte for Steel)
1596    pub discriminator: Vec<u8>,
1597
1598    /// Size of discriminator in bytes (for buffer allocation)
1599    #[serde(default = "default_instruction_discriminant_size")]
1600    pub discriminator_size: usize,
1601
1602    /// Accounts required by this instruction, in order
1603    pub accounts: Vec<InstructionAccountDef>,
1604
1605    /// Arguments for this instruction, in order
1606    pub args: Vec<InstructionArgDef>,
1607
1608    /// Error definitions specific to this instruction
1609    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1610    pub errors: Vec<IdlErrorSnapshot>,
1611
1612    /// Program ID for this instruction (usually same as stack's programId)
1613    #[serde(default, skip_serializing_if = "Option::is_none")]
1614    pub program_id: Option<String>,
1615
1616    /// Documentation from IDL
1617    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1618    pub docs: Vec<String>,
1619}
1620
1621// ============================================================================
1622// Stack Spec — Unified multi-entity AST format
1623// ============================================================================
1624
1625/// A unified stack specification containing all entities.
1626/// Internal compiled stack representation embedded by generated code.
1627#[derive(Debug, Clone, Serialize, Deserialize)]
1628pub struct SerializableStackSpec {
1629    /// AST schema version for backward compatibility
1630    /// Uses semver format (e.g., "0.0.1")
1631    #[serde(default = "default_ast_version")]
1632    pub ast_version: String,
1633    /// Stack name (PascalCase, derived from module ident)
1634    pub stack_name: String,
1635    /// Program IDs (one per IDL, in order)
1636    #[serde(default)]
1637    pub program_ids: Vec<String>,
1638    /// IDL snapshots (one per program)
1639    #[serde(default)]
1640    pub idls: Vec<IdlSnapshot>,
1641    /// Exact public program specifications, in the same order as `program_ids` and `idls`.
1642    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1643    pub program_specs: Vec<arete_hash::ProgramSpecV1>,
1644    /// All entity specifications in this stack
1645    pub entities: Vec<SerializableStreamSpec>,
1646    /// PDA registry - defines all PDAs for the stack, grouped by program name
1647    /// Outer key is program name (e.g., "ore", "entropy"), inner key is PDA name
1648    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1649    pub pdas: BTreeMap<String, BTreeMap<String, PdaDefinition>>,
1650    /// Instruction definitions for SDK code generation
1651    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1652    pub instructions: Vec<InstructionDef>,
1653    /// Deterministic content hash of the entire stack
1654    #[serde(default, skip_serializing_if = "Option::is_none")]
1655    pub content_hash: Option<String>,
1656}
1657
1658impl SerializableStackSpec {
1659    /// Normalize legacy event names emitted by older AST generators.
1660    pub fn normalize_event_names(&mut self) {
1661        for entity in &mut self.entities {
1662            entity.normalize_event_names();
1663        }
1664    }
1665
1666    /// Compute deterministic content hash (SHA256 of canonical JSON).
1667    pub fn compute_content_hash(&self) -> String {
1668        use sha2::{Digest, Sha256};
1669        let mut spec_for_hash = self.clone();
1670        spec_for_hash.content_hash = None;
1671        let json = serde_json::to_string(&spec_for_hash)
1672            .expect("Failed to serialize stack spec for hashing");
1673        let mut hasher = Sha256::new();
1674        hasher.update(json.as_bytes());
1675        hex::encode(hasher.finalize())
1676    }
1677
1678    pub fn with_content_hash(mut self) -> Self {
1679        self.content_hash = Some(self.compute_content_hash());
1680        self
1681    }
1682}
1683
1684#[cfg(test)]
1685mod tests {
1686    use super::{
1687        HookAction, IdentitySpec, InstructionHook, MappingSource, PopulationStrategy,
1688        SerializableFieldMapping, SerializableHandlerSpec, SerializableStackSpec,
1689        SerializableStreamSpec, SourceSpec, TypedStreamSpec, CURRENT_AST_VERSION,
1690    };
1691    use serde_json::Value;
1692
1693    fn legacy_stream_spec() -> SerializableStreamSpec {
1694        SerializableStreamSpec {
1695            ast_version: CURRENT_AST_VERSION.to_string(),
1696            state_name: "PumpfunToken".to_string(),
1697            program_id: None,
1698            idl: None,
1699            identity: IdentitySpec {
1700                primary_keys: vec!["id.mint".to_string()],
1701                lookup_indexes: vec![],
1702            },
1703            handlers: vec![SerializableHandlerSpec {
1704                source: SourceSpec::Source {
1705                    program_id: None,
1706                    discriminator: None,
1707                    type_name: "pump::buyIxState".to_string(),
1708                    serialization: None,
1709                    is_account: false,
1710                },
1711                key_resolution: super::KeyResolutionStrategy::Embedded {
1712                    primary_field: super::FieldPath::new(&["accounts", "mint"]),
1713                },
1714                mappings: vec![SerializableFieldMapping {
1715                    target_path: "info.last_buy_at".to_string(),
1716                    source: MappingSource::Constant(Value::Null),
1717                    transform: None,
1718                    population: PopulationStrategy::SetOnce,
1719                    condition: None,
1720                    when: Some("pump::sellIxState".to_string()),
1721                    stop: Some("pump::buy_exact_sol_inIxState".to_string()),
1722                    emit: true,
1723                }],
1724                conditions: vec![],
1725                emit: true,
1726            }],
1727            sections: vec![],
1728            field_mappings: Default::default(),
1729            resolver_hooks: vec![],
1730            instruction_hooks: vec![InstructionHook {
1731                instruction_type: "pump::buyIxState".to_string(),
1732                actions: vec![HookAction::SetField {
1733                    target_field: "info.last_buy_at".to_string(),
1734                    source: MappingSource::Constant(Value::Null),
1735                    condition: None,
1736                }],
1737                lookup_by: None,
1738            }],
1739            resolver_specs: vec![],
1740            computed_fields: vec![],
1741            computed_field_specs: vec![],
1742            content_hash: None,
1743            views: vec![],
1744            scope_field: None,
1745        }
1746    }
1747
1748    #[test]
1749    fn typed_stream_spec_from_serializable_normalizes_legacy_instruction_event_names() {
1750        let typed = TypedStreamSpec::<Value>::from_serializable(legacy_stream_spec());
1751
1752        let handler = &typed.handlers[0];
1753        let SourceSpec::Source { type_name, .. } = &handler.source;
1754        assert_eq!(type_name, "pump::BuyIxState");
1755        assert_eq!(
1756            handler.mappings[0].when.as_deref(),
1757            Some("pump::SellIxState")
1758        );
1759        assert_eq!(
1760            handler.mappings[0].stop.as_deref(),
1761            Some("pump::BuyExactSolInIxState")
1762        );
1763        assert_eq!(
1764            typed.instruction_hooks[0].instruction_type,
1765            "pump::BuyIxState"
1766        );
1767    }
1768
1769    #[test]
1770    fn stack_spec_normalize_event_names_updates_all_entities() {
1771        let mut stack = SerializableStackSpec {
1772            ast_version: CURRENT_AST_VERSION.to_string(),
1773            stack_name: "PumpStack".to_string(),
1774            program_ids: vec![],
1775            idls: vec![],
1776            program_specs: vec![],
1777            entities: vec![legacy_stream_spec()],
1778            pdas: Default::default(),
1779            instructions: vec![],
1780            content_hash: None,
1781        };
1782
1783        stack.normalize_event_names();
1784
1785        let SourceSpec::Source { type_name, .. } = &stack.entities[0].handlers[0].source;
1786        assert_eq!(type_name, "pump::BuyIxState");
1787        assert_eq!(
1788            stack.entities[0].instruction_hooks[0].instruction_type,
1789            "pump::BuyIxState"
1790        );
1791    }
1792
1793    #[test]
1794    fn field_type_info_recognizes_128_bit_integers() {
1795        let unsigned = super::FieldTypeInfo::new("amount".to_string(), "u128".to_string());
1796        assert_eq!(unsigned.base_type, super::BaseType::Integer);
1797        assert!(!unsigned.is_optional);
1798        assert_eq!(unsigned.integer_kind, Some(super::IntegerKind::U128));
1799        assert_eq!(unsigned.raw_field_name(), "amount");
1800        assert_eq!(unsigned.canonical_field_name(), "amount");
1801
1802        let signed = super::FieldTypeInfo::new("delta".to_string(), "Option<i128>".to_string());
1803        assert_eq!(signed.base_type, super::BaseType::Integer);
1804        assert!(signed.is_optional);
1805        assert_eq!(signed.integer_kind, Some(super::IntegerKind::I128));
1806        assert_eq!(signed.inner_type.as_deref(), Some("i128"));
1807    }
1808
1809    #[test]
1810    fn field_type_info_preserves_timestamp_integer_kind_and_names() {
1811        let timestamp =
1812            super::FieldTypeInfo::new("last_updated_at".to_string(), "Option<i64>".to_string());
1813
1814        assert_eq!(timestamp.base_type, super::BaseType::Timestamp);
1815        assert_eq!(timestamp.integer_kind, Some(super::IntegerKind::I64));
1816        assert!(timestamp.is_optional);
1817        assert_eq!(timestamp.raw_field_name(), "last_updated_at");
1818        assert_eq!(timestamp.canonical_field_name(), "lastUpdatedAt");
1819    }
1820}
1821
1822// ============================================================================
1823// View Pipeline Types - Composable View Definitions
1824// ============================================================================
1825
1826/// Sort order for view transforms
1827#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1828#[serde(rename_all = "lowercase")]
1829pub enum SortOrder {
1830    #[default]
1831    Asc,
1832    Desc,
1833}
1834
1835/// Comparison operators for predicates
1836#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1837pub enum CompareOp {
1838    Eq,
1839    Ne,
1840    Gt,
1841    Gte,
1842    Lt,
1843    Lte,
1844}
1845
1846/// Value in a predicate comparison
1847#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1848pub enum PredicateValue {
1849    /// Literal JSON value
1850    Literal(serde_json::Value),
1851    /// Dynamic runtime value (e.g., "now()" for current timestamp)
1852    Dynamic(String),
1853    /// Reference to another field
1854    Field(FieldPath),
1855}
1856
1857/// Predicate for filtering entities
1858#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1859pub enum Predicate {
1860    /// Field comparison: field op value
1861    Compare {
1862        field: FieldPath,
1863        op: CompareOp,
1864        value: PredicateValue,
1865    },
1866    /// Logical AND of predicates
1867    And(Vec<Predicate>),
1868    /// Logical OR of predicates
1869    Or(Vec<Predicate>),
1870    /// Negation
1871    Not(Box<Predicate>),
1872    /// Field exists (is not null)
1873    Exists { field: FieldPath },
1874}
1875
1876/// Transform operation in a view pipeline
1877#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1878pub enum ViewTransform {
1879    /// Filter entities matching a predicate
1880    Filter { predicate: Predicate },
1881
1882    /// Sort entities by a field
1883    Sort {
1884        key: FieldPath,
1885        #[serde(default)]
1886        order: SortOrder,
1887    },
1888
1889    /// Take first N entities (after sort)
1890    Take { count: usize },
1891
1892    /// Skip first N entities
1893    Skip { count: usize },
1894
1895    /// Take only the first entity (after sort) - produces Single output
1896    First,
1897
1898    /// Take only the last entity (after sort) - produces Single output
1899    Last,
1900
1901    /// Get entity with maximum value for field - produces Single output
1902    MaxBy { key: FieldPath },
1903
1904    /// Get entity with minimum value for field - produces Single output
1905    MinBy { key: FieldPath },
1906}
1907
1908/// Source for a view definition
1909#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1910pub enum ViewSource {
1911    /// Derive directly from entity mutations
1912    Entity { name: String },
1913    /// Derive from another view's output
1914    View { id: String },
1915}
1916
1917/// Output mode for a view
1918#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1919pub enum ViewOutput {
1920    /// Multiple entities (list-like semantics)
1921    #[default]
1922    Collection,
1923    /// Single entity (state-like semantics)
1924    Single,
1925    /// Keyed lookup by a specific field
1926    Keyed { key_field: FieldPath },
1927}
1928
1929/// Definition of a view in the pipeline
1930#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1931pub struct ViewDef {
1932    /// Unique view identifier (e.g., "OreRound/latest")
1933    pub id: String,
1934
1935    /// Source this view derives from
1936    pub source: ViewSource,
1937
1938    /// Pipeline of transforms to apply (in order)
1939    #[serde(default)]
1940    pub pipeline: Vec<ViewTransform>,
1941
1942    /// Output mode for this view
1943    #[serde(default)]
1944    pub output: ViewOutput,
1945}
1946
1947impl ViewDef {
1948    /// Create a new list view for an entity
1949    pub fn list(entity_name: &str) -> Self {
1950        ViewDef {
1951            id: format!("{}/list", entity_name),
1952            source: ViewSource::Entity {
1953                name: entity_name.to_string(),
1954            },
1955            pipeline: vec![],
1956            output: ViewOutput::Collection,
1957        }
1958    }
1959
1960    /// Create a new state view for an entity
1961    pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1962        ViewDef {
1963            id: format!("{}/state", entity_name),
1964            source: ViewSource::Entity {
1965                name: entity_name.to_string(),
1966            },
1967            pipeline: vec![],
1968            output: ViewOutput::Keyed {
1969                key_field: FieldPath::new(key_field),
1970            },
1971        }
1972    }
1973
1974    /// Check if this view produces a single entity
1975    pub fn is_single(&self) -> bool {
1976        matches!(self.output, ViewOutput::Single)
1977    }
1978
1979    /// Check if any transform in the pipeline produces a single result
1980    pub fn has_single_transform(&self) -> bool {
1981        self.pipeline.iter().any(|t| {
1982            matches!(
1983                t,
1984                ViewTransform::First
1985                    | ViewTransform::Last
1986                    | ViewTransform::MaxBy { .. }
1987                    | ViewTransform::MinBy { .. }
1988            )
1989        })
1990    }
1991}
1992
1993#[macro_export]
1994macro_rules! define_accessor {
1995    ($name:ident, $state:ty, $path:expr) => {
1996        pub struct $name;
1997
1998        impl $crate::ast::FieldAccessor<$state> for $name {
1999            fn path(&self) -> String {
2000                $path.to_string()
2001            }
2002        }
2003    };
2004}