1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::marker::PhantomData;
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct IdlSnapshot {
14 pub name: String,
16 pub version: String,
18 pub accounts: Vec<IdlAccountSnapshot>,
20 pub instructions: Vec<IdlInstructionSnapshot>,
22 #[serde(default)]
24 pub types: Vec<IdlTypeDefSnapshot>,
25 #[serde(default)]
27 pub events: Vec<IdlEventSnapshot>,
28 #[serde(default)]
30 pub errors: Vec<IdlErrorSnapshot>,
31 #[serde(default = "default_discriminant_size")]
34 pub discriminant_size: usize,
35}
36
37fn default_discriminant_size() -> usize {
38 8
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct IdlAccountSnapshot {
44 pub name: String,
46 pub discriminator: Vec<u8>,
48 #[serde(default)]
50 pub docs: Vec<String>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct IdlInstructionSnapshot {
56 pub name: String,
58 pub discriminator: Vec<u8>,
60 #[serde(default)]
62 pub docs: Vec<String>,
63 pub accounts: Vec<IdlInstructionAccountSnapshot>,
65 pub args: Vec<IdlFieldSnapshot>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct IdlInstructionAccountSnapshot {
72 pub name: String,
74 #[serde(default)]
76 pub writable: bool,
77 #[serde(default)]
79 pub signer: bool,
80 #[serde(default)]
82 pub optional: bool,
83 #[serde(default)]
85 pub address: Option<String>,
86 #[serde(default)]
88 pub docs: Vec<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct IdlFieldSnapshot {
94 pub name: String,
96 #[serde(rename = "type")]
98 pub type_: IdlTypeSnapshot,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(untagged)]
104pub enum IdlTypeSnapshot {
105 Simple(String),
107 Array(IdlArrayTypeSnapshot),
109 Option(IdlOptionTypeSnapshot),
111 Vec(IdlVecTypeSnapshot),
113 Defined(IdlDefinedTypeSnapshot),
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct IdlArrayTypeSnapshot {
120 pub array: Vec<IdlArrayElementSnapshot>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(untagged)]
127pub enum IdlArrayElementSnapshot {
128 Type(IdlTypeSnapshot),
130 TypeName(String),
132 Size(u32),
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct IdlOptionTypeSnapshot {
139 pub option: Box<IdlTypeSnapshot>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct IdlVecTypeSnapshot {
145 pub vec: Box<IdlTypeSnapshot>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct IdlDefinedTypeSnapshot {
151 pub defined: IdlDefinedInnerSnapshot,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(untagged)]
157pub enum IdlDefinedInnerSnapshot {
158 Named { name: String },
160 Simple(String),
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct IdlTypeDefSnapshot {
167 pub name: String,
169 #[serde(default)]
171 pub docs: Vec<String>,
172 #[serde(rename = "type")]
174 pub type_def: IdlTypeDefKindSnapshot,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(untagged)]
180pub enum IdlTypeDefKindSnapshot {
181 Struct { kind: String, fields: Vec<IdlFieldSnapshot> },
183 Enum { kind: String, variants: Vec<IdlEnumVariantSnapshot> },
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct IdlEnumVariantSnapshot {
190 pub name: String,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct IdlEventSnapshot {
196 pub name: String,
198 pub discriminator: Vec<u8>,
200 #[serde(default)]
202 pub docs: Vec<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct IdlErrorSnapshot {
208 pub code: u32,
210 pub name: String,
212 pub msg: String,
214}
215
216impl IdlTypeSnapshot {
217 pub fn to_rust_type_string(&self) -> String {
219 match self {
220 IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
221 IdlTypeSnapshot::Array(arr) => {
222 if arr.array.len() == 2 {
223 match (&arr.array[0], &arr.array[1]) {
224 (IdlArrayElementSnapshot::TypeName(t), IdlArrayElementSnapshot::Size(size)) => {
225 format!("[{}; {}]", Self::map_simple_type(t), size)
226 }
227 (IdlArrayElementSnapshot::Type(nested), IdlArrayElementSnapshot::Size(size)) => {
228 format!("[{}; {}]", nested.to_rust_type_string(), size)
229 }
230 _ => "Vec<u8>".to_string(),
231 }
232 } else {
233 "Vec<u8>".to_string()
234 }
235 }
236 IdlTypeSnapshot::Option(opt) => {
237 format!("Option<{}>", opt.option.to_rust_type_string())
238 }
239 IdlTypeSnapshot::Vec(vec) => {
240 format!("Vec<{}>", vec.vec.to_rust_type_string())
241 }
242 IdlTypeSnapshot::Defined(def) => {
243 match &def.defined {
244 IdlDefinedInnerSnapshot::Named { name } => name.clone(),
245 IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
246 }
247 }
248 }
249 }
250
251 fn map_simple_type(idl_type: &str) -> String {
252 match idl_type {
253 "u8" => "u8".to_string(),
254 "u16" => "u16".to_string(),
255 "u32" => "u32".to_string(),
256 "u64" => "u64".to_string(),
257 "u128" => "u128".to_string(),
258 "i8" => "i8".to_string(),
259 "i16" => "i16".to_string(),
260 "i32" => "i32".to_string(),
261 "i64" => "i64".to_string(),
262 "i128" => "i128".to_string(),
263 "bool" => "bool".to_string(),
264 "string" => "String".to_string(),
265 "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
266 "bytes" => "Vec<u8>".to_string(),
267 _ => idl_type.to_string(),
268 }
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub struct FieldPath {
274 pub segments: Vec<String>,
275 pub offsets: Option<Vec<usize>>,
276}
277
278impl FieldPath {
279 pub fn new(segments: &[&str]) -> Self {
280 FieldPath {
281 segments: segments.iter().map(|s| s.to_string()).collect(),
282 offsets: None,
283 }
284 }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
288pub enum Transformation {
289 HexEncode,
290 HexDecode,
291 Base58Encode,
292 Base58Decode,
293 ToString,
294 ToNumber,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum PopulationStrategy {
299 SetOnce,
300 LastWrite,
301 Append,
302 Merge,
303 Max,
304 Sum,
306 Count,
308 Min,
310 UniqueCount,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct ComputedFieldSpec {
322 pub target_path: String,
324 pub expression: ComputedExpr,
326 pub result_type: String,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
340pub enum ComputedExpr {
341 FieldRef { path: String },
344
345 UnwrapOr { expr: Box<ComputedExpr>, default: serde_json::Value },
347
348 Binary { op: BinaryOp, left: Box<ComputedExpr>, right: Box<ComputedExpr> },
350
351 Cast { expr: Box<ComputedExpr>, to_type: String },
353
354 MethodCall { expr: Box<ComputedExpr>, method: String, args: Vec<ComputedExpr> },
356
357 Literal { value: serde_json::Value },
359
360 Paren { expr: Box<ComputedExpr> },
362
363 Var { name: String },
365
366 Let { name: String, value: Box<ComputedExpr>, body: Box<ComputedExpr> },
368
369 If { condition: Box<ComputedExpr>, then_branch: Box<ComputedExpr>, else_branch: Box<ComputedExpr> },
371
372 None,
374 Some { value: Box<ComputedExpr> },
375
376 Slice { expr: Box<ComputedExpr>, start: usize, end: usize },
378 Index { expr: Box<ComputedExpr>, index: usize },
379
380 U64FromLeBytes { bytes: Box<ComputedExpr> },
382 U64FromBeBytes { bytes: Box<ComputedExpr> },
383
384 ByteArray { bytes: Vec<u8> },
386
387 Closure { param: String, body: Box<ComputedExpr> },
389
390 Unary { op: UnaryOp, expr: Box<ComputedExpr> },
392
393 JsonToBytes { expr: Box<ComputedExpr> },
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize)]
399pub enum BinaryOp {
400 Add, Sub, Mul, Div, Mod,
402 Gt, Lt, Gte, Lte, Eq, Ne,
404 And, Or,
406 Xor, BitAnd, BitOr, Shl, Shr,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412pub enum UnaryOp {
413 Not,
414 ReverseBits,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct SerializableStreamSpec {
420 pub state_name: String,
421 #[serde(default)]
423 pub program_id: Option<String>,
424 #[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)]
435 pub computed_fields: Vec<String>,
436 #[serde(default)]
438 pub computed_field_specs: Vec<ComputedFieldSpec>,
439 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub content_hash: Option<String>,
443}
444
445#[derive(Debug, Clone)]
446pub struct TypedStreamSpec<S> {
447 pub state_name: String,
448 pub identity: IdentitySpec,
449 pub handlers: Vec<TypedHandlerSpec<S>>,
450 pub sections: Vec<EntitySection>, pub field_mappings: BTreeMap<String, FieldTypeInfo>, pub resolver_hooks: Vec<ResolverHook>, pub instruction_hooks: Vec<InstructionHook>, pub computed_fields: Vec<String>, _phantom: PhantomData<S>,
456}
457
458impl<S> TypedStreamSpec<S> {
459 pub fn new(
460 state_name: String,
461 identity: IdentitySpec,
462 handlers: Vec<TypedHandlerSpec<S>>,
463 ) -> Self {
464 TypedStreamSpec {
465 state_name,
466 identity,
467 handlers,
468 sections: Vec::new(),
469 field_mappings: BTreeMap::new(),
470 resolver_hooks: Vec::new(),
471 instruction_hooks: Vec::new(),
472 computed_fields: Vec::new(),
473 _phantom: PhantomData,
474 }
475 }
476
477 pub fn with_type_info(
479 state_name: String,
480 identity: IdentitySpec,
481 handlers: Vec<TypedHandlerSpec<S>>,
482 sections: Vec<EntitySection>,
483 field_mappings: BTreeMap<String, FieldTypeInfo>,
484 ) -> Self {
485 TypedStreamSpec {
486 state_name,
487 identity,
488 handlers,
489 sections,
490 field_mappings,
491 resolver_hooks: Vec::new(),
492 instruction_hooks: Vec::new(),
493 computed_fields: Vec::new(),
494 _phantom: PhantomData,
495 }
496 }
497
498 pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
500 self.field_mappings.get(path)
501 }
502
503 pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
505 self.sections.iter()
506 .find(|s| s.name == section_name)
507 .map(|s| &s.fields)
508 }
509
510 pub fn get_section_names(&self) -> Vec<&String> {
512 self.sections.iter().map(|s| &s.name).collect()
513 }
514
515 pub fn to_serializable(&self) -> SerializableStreamSpec {
517 let mut spec = SerializableStreamSpec {
518 state_name: self.state_name.clone(),
519 program_id: None, idl: None, identity: self.identity.clone(),
522 handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
523 sections: self.sections.clone(),
524 field_mappings: self.field_mappings.clone(),
525 resolver_hooks: self.resolver_hooks.clone(),
526 instruction_hooks: self.instruction_hooks.clone(),
527 computed_fields: self.computed_fields.clone(),
528 computed_field_specs: Vec::new(), content_hash: None,
530 };
531 spec.content_hash = Some(spec.compute_content_hash());
533 spec
534 }
535
536 pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
538 TypedStreamSpec {
539 state_name: spec.state_name,
540 identity: spec.identity,
541 handlers: spec.handlers.into_iter().map(|h| TypedHandlerSpec::from_serializable(h)).collect(),
542 sections: spec.sections,
543 field_mappings: spec.field_mappings,
544 resolver_hooks: spec.resolver_hooks,
545 instruction_hooks: spec.instruction_hooks,
546 computed_fields: spec.computed_fields,
547 _phantom: PhantomData,
548 }
549 }
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct IdentitySpec {
554 pub primary_keys: Vec<String>,
555 pub lookup_indexes: Vec<LookupIndexSpec>,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct LookupIndexSpec {
560 pub field_name: String,
561 pub temporal_field: Option<String>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct ResolverHook {
571 pub account_type: String,
573
574 pub strategy: ResolverStrategy,
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub enum ResolverStrategy {
580 PdaReverseLookup {
582 lookup_name: String,
583 queue_discriminators: Vec<Vec<u8>>,
585 },
586
587 DirectField {
589 field_path: FieldPath,
590 },
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct InstructionHook {
596 pub instruction_type: String,
598
599 pub actions: Vec<HookAction>,
601
602 pub lookup_by: Option<FieldPath>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607pub enum HookAction {
608 RegisterPdaMapping {
610 pda_field: FieldPath,
611 seed_field: FieldPath,
612 lookup_name: String,
613 },
614
615 SetField {
617 target_field: String,
618 source: MappingSource,
619 condition: Option<ConditionExpr>,
620 },
621
622 IncrementField {
624 target_field: String,
625 increment_by: i64,
626 condition: Option<ConditionExpr>,
627 },
628}
629
630#[derive(Debug, Clone, Serialize, Deserialize)]
632pub struct ConditionExpr {
633 pub expression: String,
635
636 pub parsed: Option<ParsedCondition>,
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize)]
641pub enum ParsedCondition {
642 Comparison {
644 field: FieldPath,
645 op: ComparisonOp,
646 value: serde_json::Value,
647 },
648
649 Logical {
651 op: LogicalOp,
652 conditions: Vec<ParsedCondition>,
653 },
654}
655
656#[derive(Debug, Clone, Serialize, Deserialize)]
657pub enum ComparisonOp {
658 Equal,
659 NotEqual,
660 GreaterThan,
661 GreaterThanOrEqual,
662 LessThan,
663 LessThanOrEqual,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
667pub enum LogicalOp {
668 And,
669 Or,
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct SerializableHandlerSpec {
675 pub source: SourceSpec,
676 pub key_resolution: KeyResolutionStrategy,
677 pub mappings: Vec<SerializableFieldMapping>,
678 pub conditions: Vec<Condition>,
679 pub emit: bool,
680}
681
682#[derive(Debug, Clone)]
683pub struct TypedHandlerSpec<S> {
684 pub source: SourceSpec,
685 pub key_resolution: KeyResolutionStrategy,
686 pub mappings: Vec<TypedFieldMapping<S>>,
687 pub conditions: Vec<Condition>,
688 pub emit: bool,
689 _phantom: PhantomData<S>,
690}
691
692impl<S> TypedHandlerSpec<S> {
693 pub fn new(
694 source: SourceSpec,
695 key_resolution: KeyResolutionStrategy,
696 mappings: Vec<TypedFieldMapping<S>>,
697 emit: bool,
698 ) -> Self {
699 TypedHandlerSpec {
700 source,
701 key_resolution,
702 mappings,
703 conditions: vec![],
704 emit,
705 _phantom: PhantomData,
706 }
707 }
708
709 pub fn to_serializable(&self) -> SerializableHandlerSpec {
711 SerializableHandlerSpec {
712 source: self.source.clone(),
713 key_resolution: self.key_resolution.clone(),
714 mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
715 conditions: self.conditions.clone(),
716 emit: self.emit,
717 }
718 }
719
720 pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
722 TypedHandlerSpec {
723 source: spec.source,
724 key_resolution: spec.key_resolution,
725 mappings: spec.mappings.into_iter().map(|m| TypedFieldMapping::from_serializable(m)).collect(),
726 conditions: spec.conditions,
727 emit: spec.emit,
728 _phantom: PhantomData,
729 }
730 }
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
734pub enum KeyResolutionStrategy {
735 Embedded {
736 primary_field: FieldPath,
737 },
738 Lookup {
739 primary_field: FieldPath,
740 },
741 Computed {
742 primary_field: FieldPath,
743 compute_partition: ComputeFunction,
744 },
745 TemporalLookup {
746 lookup_field: FieldPath,
747 timestamp_field: FieldPath,
748 index_name: String,
749 },
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize)]
753pub enum SourceSpec {
754 Source {
755 program_id: Option<String>,
756 discriminator: Option<Vec<u8>>,
757 type_name: String,
758 },
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct SerializableFieldMapping {
764 pub target_path: String,
765 pub source: MappingSource,
766 pub transform: Option<Transformation>,
767 pub population: PopulationStrategy,
768}
769
770#[derive(Debug, Clone)]
771pub struct TypedFieldMapping<S> {
772 pub target_path: String,
773 pub source: MappingSource,
774 pub transform: Option<Transformation>,
775 pub population: PopulationStrategy,
776 _phantom: PhantomData<S>,
777}
778
779impl<S> TypedFieldMapping<S> {
780 pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
781 TypedFieldMapping {
782 target_path,
783 source,
784 transform: None,
785 population,
786 _phantom: PhantomData,
787 }
788 }
789
790 pub fn with_transform(mut self, transform: Transformation) -> Self {
791 self.transform = Some(transform);
792 self
793 }
794
795 pub fn to_serializable(&self) -> SerializableFieldMapping {
797 SerializableFieldMapping {
798 target_path: self.target_path.clone(),
799 source: self.source.clone(),
800 transform: self.transform.clone(),
801 population: self.population.clone(),
802 }
803 }
804
805 pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
807 TypedFieldMapping {
808 target_path: mapping.target_path,
809 source: mapping.source,
810 transform: mapping.transform,
811 population: mapping.population,
812 _phantom: PhantomData,
813 }
814 }
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
818pub enum MappingSource {
819 FromSource {
820 path: FieldPath,
821 default: Option<Value>,
822 transform: Option<Transformation>,
823 },
824 Constant(Value),
825 Computed {
826 inputs: Vec<FieldPath>,
827 function: ComputeFunction,
828 },
829 FromState {
830 path: String,
831 },
832 AsEvent {
833 fields: Vec<Box<MappingSource>>,
834 },
835 WholeSource,
836 AsCapture {
839 field_transforms: BTreeMap<String, Transformation>,
840 },
841 FromContext {
844 field: String,
845 },
846}
847
848impl MappingSource {
849 pub fn with_transform(self, transform: Transformation) -> Self {
850 match self {
851 MappingSource::FromSource { path, default, transform: _ } => {
852 MappingSource::FromSource {
853 path,
854 default,
855 transform: Some(transform),
856 }
857 }
858 other => other,
859 }
860 }
861}
862
863#[derive(Debug, Clone, Serialize, Deserialize)]
864pub enum ComputeFunction {
865 Sum,
866 Concat,
867 Format(String),
868 Custom(String),
869}
870
871#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct Condition {
873 pub field: FieldPath,
874 pub operator: ConditionOp,
875 pub value: Value,
876}
877
878#[derive(Debug, Clone, Serialize, Deserialize)]
879pub enum ConditionOp {
880 Equals,
881 NotEquals,
882 GreaterThan,
883 LessThan,
884 Contains,
885 Exists,
886}
887
888#[derive(Debug, Clone, Serialize, Deserialize)]
890pub struct FieldTypeInfo {
891 pub field_name: String,
892 pub rust_type_name: String, pub base_type: BaseType, pub is_optional: bool, pub is_array: bool, pub inner_type: Option<String>, pub source_path: Option<String>, #[serde(default)]
900 pub resolved_type: Option<ResolvedStructType>,
901}
902
903#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct ResolvedStructType {
906 pub type_name: String,
907 pub fields: Vec<ResolvedField>,
908 pub is_instruction: bool,
909 pub is_account: bool,
910 pub is_event: bool,
911 #[serde(default)]
913 pub is_enum: bool,
914 #[serde(default)]
916 pub enum_variants: Vec<String>,
917}
918
919#[derive(Debug, Clone, Serialize, Deserialize)]
921pub struct ResolvedField {
922 pub field_name: String,
923 pub field_type: String,
924 pub base_type: BaseType,
925 pub is_optional: bool,
926 pub is_array: bool,
927}
928
929#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
931pub enum BaseType {
932 Integer, Float, String, Boolean, Object, Array, Binary, Timestamp, Pubkey, Any, }
948
949#[derive(Debug, Clone, Serialize, Deserialize)]
951pub struct EntitySection {
952 pub name: String,
953 pub fields: Vec<FieldTypeInfo>,
954 pub is_nested_struct: bool,
955 pub parent_field: Option<String>, }
957
958
959
960impl FieldTypeInfo {
961 pub fn new(field_name: String, rust_type_name: String) -> Self {
962 let (base_type, is_optional, is_array, inner_type) = Self::analyze_rust_type(&rust_type_name);
963
964 FieldTypeInfo {
965 field_name: field_name.clone(),
966 rust_type_name,
967 base_type: Self::infer_semantic_type(&field_name, base_type),
968 is_optional,
969 is_array,
970 inner_type,
971 source_path: None,
972 resolved_type: None,
973 }
974 }
975
976 pub fn with_source_path(mut self, source_path: String) -> Self {
977 self.source_path = Some(source_path);
978 self
979 }
980
981 fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
983 let type_str = rust_type.trim();
984
985 if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
987 let (inner_base_type, _, inner_is_array, inner_inner_type) = Self::analyze_rust_type(&inner);
988 return (inner_base_type, true, inner_is_array, inner_inner_type.or(Some(inner)));
989 }
990
991 if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
993 let (_inner_base_type, inner_is_optional, _, inner_inner_type) = Self::analyze_rust_type(&inner);
994 return (BaseType::Array, inner_is_optional, true, inner_inner_type.or(Some(inner)));
995 }
996
997 let base_type = match type_str {
999 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => BaseType::Integer,
1000 "f32" | "f64" => BaseType::Float,
1001 "bool" => BaseType::Boolean,
1002 "String" | "&str" | "str" => BaseType::String,
1003 "Value" | "serde_json::Value" => BaseType::Any,
1004 "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1005 _ => {
1006 if type_str.contains("Bytes") || type_str.contains("bytes") {
1008 BaseType::Binary
1009 } else if type_str.contains("Pubkey") {
1010 BaseType::Pubkey
1011 } else {
1012 BaseType::Object
1013 }
1014 }
1015 };
1016
1017 (base_type, false, false, None)
1018 }
1019
1020 fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1022 let pattern = format!("{}<", generic_name);
1023 if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1024 let start = pattern.len();
1025 let end = type_str.len() - 1;
1026 if end > start {
1027 return Some(type_str[start..end].trim().to_string());
1028 }
1029 }
1030 None
1031 }
1032
1033 fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1035 let lower_name = field_name.to_lowercase();
1036
1037 if base_type == BaseType::Integer
1039 && (lower_name.ends_with("_at")
1040 || lower_name.ends_with("_time")
1041 || lower_name.contains("timestamp")
1042 || lower_name.contains("created")
1043 || lower_name.contains("settled")
1044 || lower_name.contains("activated"))
1045 {
1046 return BaseType::Timestamp;
1047 }
1048
1049 base_type
1050 }
1051}
1052
1053pub trait FieldAccessor<S> {
1054 fn path(&self) -> String;
1055}
1056
1057impl SerializableStreamSpec {
1062 pub fn compute_content_hash(&self) -> String {
1068 use sha2::{Sha256, Digest};
1069
1070 let mut spec_for_hash = self.clone();
1072 spec_for_hash.content_hash = None;
1073
1074 let json = serde_json::to_string(&spec_for_hash)
1076 .expect("Failed to serialize spec for hashing");
1077
1078 let mut hasher = Sha256::new();
1080 hasher.update(json.as_bytes());
1081 let result = hasher.finalize();
1082
1083 hex::encode(result)
1085 }
1086
1087 pub fn verify_content_hash(&self) -> bool {
1090 match &self.content_hash {
1091 Some(hash) => {
1092 let computed = self.compute_content_hash();
1093 hash == &computed
1094 }
1095 None => true, }
1097 }
1098
1099 pub fn with_content_hash(mut self) -> Self {
1101 self.content_hash = Some(self.compute_content_hash());
1102 self
1103 }
1104}
1105
1106#[macro_export]
1107macro_rules! define_accessor {
1108 ($name:ident, $state:ty, $path:expr) => {
1109 pub struct $name;
1110
1111 impl $crate::ast::FieldAccessor<$state> for $name {
1112 fn path(&self) -> String {
1113 $path.to_string()
1114 }
1115 }
1116 };
1117}