1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4use std::marker::PhantomData;
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 {
183 kind: String,
184 fields: Vec<IdlFieldSnapshot>,
185 },
186 Enum {
188 kind: String,
189 variants: Vec<IdlEnumVariantSnapshot>,
190 },
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct IdlEnumVariantSnapshot {
196 pub name: String,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct IdlEventSnapshot {
202 pub name: String,
204 pub discriminator: Vec<u8>,
206 #[serde(default)]
208 pub docs: Vec<String>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct IdlErrorSnapshot {
214 pub code: u32,
216 pub name: String,
218 pub msg: String,
220}
221
222impl IdlTypeSnapshot {
223 pub fn to_rust_type_string(&self) -> String {
225 match self {
226 IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
227 IdlTypeSnapshot::Array(arr) => {
228 if arr.array.len() == 2 {
229 match (&arr.array[0], &arr.array[1]) {
230 (
231 IdlArrayElementSnapshot::TypeName(t),
232 IdlArrayElementSnapshot::Size(size),
233 ) => {
234 format!("[{}; {}]", Self::map_simple_type(t), size)
235 }
236 (
237 IdlArrayElementSnapshot::Type(nested),
238 IdlArrayElementSnapshot::Size(size),
239 ) => {
240 format!("[{}; {}]", nested.to_rust_type_string(), size)
241 }
242 _ => "Vec<u8>".to_string(),
243 }
244 } else {
245 "Vec<u8>".to_string()
246 }
247 }
248 IdlTypeSnapshot::Option(opt) => {
249 format!("Option<{}>", opt.option.to_rust_type_string())
250 }
251 IdlTypeSnapshot::Vec(vec) => {
252 format!("Vec<{}>", vec.vec.to_rust_type_string())
253 }
254 IdlTypeSnapshot::Defined(def) => match &def.defined {
255 IdlDefinedInnerSnapshot::Named { name } => name.clone(),
256 IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
257 },
258 }
259 }
260
261 fn map_simple_type(idl_type: &str) -> String {
262 match idl_type {
263 "u8" => "u8".to_string(),
264 "u16" => "u16".to_string(),
265 "u32" => "u32".to_string(),
266 "u64" => "u64".to_string(),
267 "u128" => "u128".to_string(),
268 "i8" => "i8".to_string(),
269 "i16" => "i16".to_string(),
270 "i32" => "i32".to_string(),
271 "i64" => "i64".to_string(),
272 "i128" => "i128".to_string(),
273 "bool" => "bool".to_string(),
274 "string" => "String".to_string(),
275 "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
276 "bytes" => "Vec<u8>".to_string(),
277 _ => idl_type.to_string(),
278 }
279 }
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
283pub struct FieldPath {
284 pub segments: Vec<String>,
285 pub offsets: Option<Vec<usize>>,
286}
287
288impl FieldPath {
289 pub fn new(segments: &[&str]) -> Self {
290 FieldPath {
291 segments: segments.iter().map(|s| s.to_string()).collect(),
292 offsets: None,
293 }
294 }
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
298pub enum Transformation {
299 HexEncode,
300 HexDecode,
301 Base58Encode,
302 Base58Decode,
303 ToString,
304 ToNumber,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub enum PopulationStrategy {
309 SetOnce,
310 LastWrite,
311 Append,
312 Merge,
313 Max,
314 Sum,
316 Count,
318 Min,
320 UniqueCount,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct ComputedFieldSpec {
332 pub target_path: String,
334 pub expression: ComputedExpr,
336 pub result_type: String,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
350pub enum ComputedExpr {
351 FieldRef {
354 path: String,
355 },
356
357 UnwrapOr {
359 expr: Box<ComputedExpr>,
360 default: serde_json::Value,
361 },
362
363 Binary {
365 op: BinaryOp,
366 left: Box<ComputedExpr>,
367 right: Box<ComputedExpr>,
368 },
369
370 Cast {
372 expr: Box<ComputedExpr>,
373 to_type: String,
374 },
375
376 MethodCall {
378 expr: Box<ComputedExpr>,
379 method: String,
380 args: Vec<ComputedExpr>,
381 },
382
383 Literal {
385 value: serde_json::Value,
386 },
387
388 Paren {
390 expr: Box<ComputedExpr>,
391 },
392
393 Var {
395 name: String,
396 },
397
398 Let {
400 name: String,
401 value: Box<ComputedExpr>,
402 body: Box<ComputedExpr>,
403 },
404
405 If {
407 condition: Box<ComputedExpr>,
408 then_branch: Box<ComputedExpr>,
409 else_branch: Box<ComputedExpr>,
410 },
411
412 None,
414 Some {
415 value: Box<ComputedExpr>,
416 },
417
418 Slice {
420 expr: Box<ComputedExpr>,
421 start: usize,
422 end: usize,
423 },
424 Index {
425 expr: Box<ComputedExpr>,
426 index: usize,
427 },
428
429 U64FromLeBytes {
431 bytes: Box<ComputedExpr>,
432 },
433 U64FromBeBytes {
434 bytes: Box<ComputedExpr>,
435 },
436
437 ByteArray {
439 bytes: Vec<u8>,
440 },
441
442 Closure {
444 param: String,
445 body: Box<ComputedExpr>,
446 },
447
448 Unary {
450 op: UnaryOp,
451 expr: Box<ComputedExpr>,
452 },
453
454 JsonToBytes {
456 expr: Box<ComputedExpr>,
457 },
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
462pub enum BinaryOp {
463 Add,
465 Sub,
466 Mul,
467 Div,
468 Mod,
469 Gt,
471 Lt,
472 Gte,
473 Lte,
474 Eq,
475 Ne,
476 And,
478 Or,
479 Xor,
481 BitAnd,
482 BitOr,
483 Shl,
484 Shr,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
489pub enum UnaryOp {
490 Not,
491 ReverseBits,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct SerializableStreamSpec {
497 pub state_name: String,
498 #[serde(default)]
500 pub program_id: Option<String>,
501 #[serde(default)]
503 pub idl: Option<IdlSnapshot>,
504 pub identity: IdentitySpec,
505 pub handlers: Vec<SerializableHandlerSpec>,
506 pub sections: Vec<EntitySection>,
507 pub field_mappings: BTreeMap<String, FieldTypeInfo>,
508 pub resolver_hooks: Vec<ResolverHook>,
509 pub instruction_hooks: Vec<InstructionHook>,
510 #[serde(default)]
512 pub computed_fields: Vec<String>,
513 #[serde(default)]
515 pub computed_field_specs: Vec<ComputedFieldSpec>,
516 #[serde(default, skip_serializing_if = "Option::is_none")]
519 pub content_hash: Option<String>,
520}
521
522#[derive(Debug, Clone)]
523pub struct TypedStreamSpec<S> {
524 pub state_name: String,
525 pub identity: IdentitySpec,
526 pub handlers: Vec<TypedHandlerSpec<S>>,
527 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>,
533}
534
535impl<S> TypedStreamSpec<S> {
536 pub fn new(
537 state_name: String,
538 identity: IdentitySpec,
539 handlers: Vec<TypedHandlerSpec<S>>,
540 ) -> Self {
541 TypedStreamSpec {
542 state_name,
543 identity,
544 handlers,
545 sections: Vec::new(),
546 field_mappings: BTreeMap::new(),
547 resolver_hooks: Vec::new(),
548 instruction_hooks: Vec::new(),
549 computed_fields: Vec::new(),
550 _phantom: PhantomData,
551 }
552 }
553
554 pub fn with_type_info(
556 state_name: String,
557 identity: IdentitySpec,
558 handlers: Vec<TypedHandlerSpec<S>>,
559 sections: Vec<EntitySection>,
560 field_mappings: BTreeMap<String, FieldTypeInfo>,
561 ) -> Self {
562 TypedStreamSpec {
563 state_name,
564 identity,
565 handlers,
566 sections,
567 field_mappings,
568 resolver_hooks: Vec::new(),
569 instruction_hooks: Vec::new(),
570 computed_fields: Vec::new(),
571 _phantom: PhantomData,
572 }
573 }
574
575 pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
577 self.field_mappings.get(path)
578 }
579
580 pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
582 self.sections
583 .iter()
584 .find(|s| s.name == section_name)
585 .map(|s| &s.fields)
586 }
587
588 pub fn get_section_names(&self) -> Vec<&String> {
590 self.sections.iter().map(|s| &s.name).collect()
591 }
592
593 pub fn to_serializable(&self) -> SerializableStreamSpec {
595 let mut spec = SerializableStreamSpec {
596 state_name: self.state_name.clone(),
597 program_id: None, idl: None, identity: self.identity.clone(),
600 handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
601 sections: self.sections.clone(),
602 field_mappings: self.field_mappings.clone(),
603 resolver_hooks: self.resolver_hooks.clone(),
604 instruction_hooks: self.instruction_hooks.clone(),
605 computed_fields: self.computed_fields.clone(),
606 computed_field_specs: Vec::new(), content_hash: None,
608 };
609 spec.content_hash = Some(spec.compute_content_hash());
611 spec
612 }
613
614 pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
616 TypedStreamSpec {
617 state_name: spec.state_name,
618 identity: spec.identity,
619 handlers: spec
620 .handlers
621 .into_iter()
622 .map(|h| TypedHandlerSpec::from_serializable(h))
623 .collect(),
624 sections: spec.sections,
625 field_mappings: spec.field_mappings,
626 resolver_hooks: spec.resolver_hooks,
627 instruction_hooks: spec.instruction_hooks,
628 computed_fields: spec.computed_fields,
629 _phantom: PhantomData,
630 }
631 }
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize)]
635pub struct IdentitySpec {
636 pub primary_keys: Vec<String>,
637 pub lookup_indexes: Vec<LookupIndexSpec>,
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct LookupIndexSpec {
642 pub field_name: String,
643 pub temporal_field: Option<String>,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
652pub struct ResolverHook {
653 pub account_type: String,
655
656 pub strategy: ResolverStrategy,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize)]
661pub enum ResolverStrategy {
662 PdaReverseLookup {
664 lookup_name: String,
665 queue_discriminators: Vec<Vec<u8>>,
667 },
668
669 DirectField { field_path: FieldPath },
671}
672
673#[derive(Debug, Clone, Serialize, Deserialize)]
675pub struct InstructionHook {
676 pub instruction_type: String,
678
679 pub actions: Vec<HookAction>,
681
682 pub lookup_by: Option<FieldPath>,
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize)]
687pub enum HookAction {
688 RegisterPdaMapping {
690 pda_field: FieldPath,
691 seed_field: FieldPath,
692 lookup_name: String,
693 },
694
695 SetField {
697 target_field: String,
698 source: MappingSource,
699 condition: Option<ConditionExpr>,
700 },
701
702 IncrementField {
704 target_field: String,
705 increment_by: i64,
706 condition: Option<ConditionExpr>,
707 },
708}
709
710#[derive(Debug, Clone, Serialize, Deserialize)]
712pub struct ConditionExpr {
713 pub expression: String,
715
716 pub parsed: Option<ParsedCondition>,
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize)]
721pub enum ParsedCondition {
722 Comparison {
724 field: FieldPath,
725 op: ComparisonOp,
726 value: serde_json::Value,
727 },
728
729 Logical {
731 op: LogicalOp,
732 conditions: Vec<ParsedCondition>,
733 },
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize)]
737pub enum ComparisonOp {
738 Equal,
739 NotEqual,
740 GreaterThan,
741 GreaterThanOrEqual,
742 LessThan,
743 LessThanOrEqual,
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize)]
747pub enum LogicalOp {
748 And,
749 Or,
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize)]
754pub struct SerializableHandlerSpec {
755 pub source: SourceSpec,
756 pub key_resolution: KeyResolutionStrategy,
757 pub mappings: Vec<SerializableFieldMapping>,
758 pub conditions: Vec<Condition>,
759 pub emit: bool,
760}
761
762#[derive(Debug, Clone)]
763pub struct TypedHandlerSpec<S> {
764 pub source: SourceSpec,
765 pub key_resolution: KeyResolutionStrategy,
766 pub mappings: Vec<TypedFieldMapping<S>>,
767 pub conditions: Vec<Condition>,
768 pub emit: bool,
769 _phantom: PhantomData<S>,
770}
771
772impl<S> TypedHandlerSpec<S> {
773 pub fn new(
774 source: SourceSpec,
775 key_resolution: KeyResolutionStrategy,
776 mappings: Vec<TypedFieldMapping<S>>,
777 emit: bool,
778 ) -> Self {
779 TypedHandlerSpec {
780 source,
781 key_resolution,
782 mappings,
783 conditions: vec![],
784 emit,
785 _phantom: PhantomData,
786 }
787 }
788
789 pub fn to_serializable(&self) -> SerializableHandlerSpec {
791 SerializableHandlerSpec {
792 source: self.source.clone(),
793 key_resolution: self.key_resolution.clone(),
794 mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
795 conditions: self.conditions.clone(),
796 emit: self.emit,
797 }
798 }
799
800 pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
802 TypedHandlerSpec {
803 source: spec.source,
804 key_resolution: spec.key_resolution,
805 mappings: spec
806 .mappings
807 .into_iter()
808 .map(|m| TypedFieldMapping::from_serializable(m))
809 .collect(),
810 conditions: spec.conditions,
811 emit: spec.emit,
812 _phantom: PhantomData,
813 }
814 }
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
818pub enum KeyResolutionStrategy {
819 Embedded {
820 primary_field: FieldPath,
821 },
822 Lookup {
823 primary_field: FieldPath,
824 },
825 Computed {
826 primary_field: FieldPath,
827 compute_partition: ComputeFunction,
828 },
829 TemporalLookup {
830 lookup_field: FieldPath,
831 timestamp_field: FieldPath,
832 index_name: String,
833 },
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize)]
837pub enum SourceSpec {
838 Source {
839 program_id: Option<String>,
840 discriminator: Option<Vec<u8>>,
841 type_name: String,
842 },
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize)]
847pub struct SerializableFieldMapping {
848 pub target_path: String,
849 pub source: MappingSource,
850 pub transform: Option<Transformation>,
851 pub population: PopulationStrategy,
852}
853
854#[derive(Debug, Clone)]
855pub struct TypedFieldMapping<S> {
856 pub target_path: String,
857 pub source: MappingSource,
858 pub transform: Option<Transformation>,
859 pub population: PopulationStrategy,
860 _phantom: PhantomData<S>,
861}
862
863impl<S> TypedFieldMapping<S> {
864 pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
865 TypedFieldMapping {
866 target_path,
867 source,
868 transform: None,
869 population,
870 _phantom: PhantomData,
871 }
872 }
873
874 pub fn with_transform(mut self, transform: Transformation) -> Self {
875 self.transform = Some(transform);
876 self
877 }
878
879 pub fn to_serializable(&self) -> SerializableFieldMapping {
881 SerializableFieldMapping {
882 target_path: self.target_path.clone(),
883 source: self.source.clone(),
884 transform: self.transform.clone(),
885 population: self.population.clone(),
886 }
887 }
888
889 pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
891 TypedFieldMapping {
892 target_path: mapping.target_path,
893 source: mapping.source,
894 transform: mapping.transform,
895 population: mapping.population,
896 _phantom: PhantomData,
897 }
898 }
899}
900
901#[derive(Debug, Clone, Serialize, Deserialize)]
902pub enum MappingSource {
903 FromSource {
904 path: FieldPath,
905 default: Option<Value>,
906 transform: Option<Transformation>,
907 },
908 Constant(Value),
909 Computed {
910 inputs: Vec<FieldPath>,
911 function: ComputeFunction,
912 },
913 FromState {
914 path: String,
915 },
916 AsEvent {
917 fields: Vec<Box<MappingSource>>,
918 },
919 WholeSource,
920 AsCapture {
923 field_transforms: BTreeMap<String, Transformation>,
924 },
925 FromContext {
928 field: String,
929 },
930}
931
932impl MappingSource {
933 pub fn with_transform(self, transform: Transformation) -> Self {
934 match self {
935 MappingSource::FromSource {
936 path,
937 default,
938 transform: _,
939 } => MappingSource::FromSource {
940 path,
941 default,
942 transform: Some(transform),
943 },
944 other => other,
945 }
946 }
947}
948
949#[derive(Debug, Clone, Serialize, Deserialize)]
950pub enum ComputeFunction {
951 Sum,
952 Concat,
953 Format(String),
954 Custom(String),
955}
956
957#[derive(Debug, Clone, Serialize, Deserialize)]
958pub struct Condition {
959 pub field: FieldPath,
960 pub operator: ConditionOp,
961 pub value: Value,
962}
963
964#[derive(Debug, Clone, Serialize, Deserialize)]
965pub enum ConditionOp {
966 Equals,
967 NotEquals,
968 GreaterThan,
969 LessThan,
970 Contains,
971 Exists,
972}
973
974#[derive(Debug, Clone, Serialize, Deserialize)]
976pub struct FieldTypeInfo {
977 pub field_name: String,
978 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)]
986 pub resolved_type: Option<ResolvedStructType>,
987}
988
989#[derive(Debug, Clone, Serialize, Deserialize)]
991pub struct ResolvedStructType {
992 pub type_name: String,
993 pub fields: Vec<ResolvedField>,
994 pub is_instruction: bool,
995 pub is_account: bool,
996 pub is_event: bool,
997 #[serde(default)]
999 pub is_enum: bool,
1000 #[serde(default)]
1002 pub enum_variants: Vec<String>,
1003}
1004
1005#[derive(Debug, Clone, Serialize, Deserialize)]
1007pub struct ResolvedField {
1008 pub field_name: String,
1009 pub field_type: String,
1010 pub base_type: BaseType,
1011 pub is_optional: bool,
1012 pub is_array: bool,
1013}
1014
1015#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1017pub enum BaseType {
1018 Integer, Float, String, Boolean, Object, Array, Binary, Timestamp, Pubkey, Any, }
1034
1035#[derive(Debug, Clone, Serialize, Deserialize)]
1037pub struct EntitySection {
1038 pub name: String,
1039 pub fields: Vec<FieldTypeInfo>,
1040 pub is_nested_struct: bool,
1041 pub parent_field: Option<String>, }
1043
1044impl FieldTypeInfo {
1045 pub fn new(field_name: String, rust_type_name: String) -> Self {
1046 let (base_type, is_optional, is_array, inner_type) =
1047 Self::analyze_rust_type(&rust_type_name);
1048
1049 FieldTypeInfo {
1050 field_name: field_name.clone(),
1051 rust_type_name,
1052 base_type: Self::infer_semantic_type(&field_name, base_type),
1053 is_optional,
1054 is_array,
1055 inner_type,
1056 source_path: None,
1057 resolved_type: None,
1058 }
1059 }
1060
1061 pub fn with_source_path(mut self, source_path: String) -> Self {
1062 self.source_path = Some(source_path);
1063 self
1064 }
1065
1066 fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
1068 let type_str = rust_type.trim();
1069
1070 if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1072 let (inner_base_type, _, inner_is_array, inner_inner_type) =
1073 Self::analyze_rust_type(&inner);
1074 return (
1075 inner_base_type,
1076 true,
1077 inner_is_array,
1078 inner_inner_type.or(Some(inner)),
1079 );
1080 }
1081
1082 if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1084 let (_inner_base_type, inner_is_optional, _, inner_inner_type) =
1085 Self::analyze_rust_type(&inner);
1086 return (
1087 BaseType::Array,
1088 inner_is_optional,
1089 true,
1090 inner_inner_type.or(Some(inner)),
1091 );
1092 }
1093
1094 let base_type = match type_str {
1096 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
1097 BaseType::Integer
1098 }
1099 "f32" | "f64" => BaseType::Float,
1100 "bool" => BaseType::Boolean,
1101 "String" | "&str" | "str" => BaseType::String,
1102 "Value" | "serde_json::Value" => BaseType::Any,
1103 "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1104 _ => {
1105 if type_str.contains("Bytes") || type_str.contains("bytes") {
1107 BaseType::Binary
1108 } else if type_str.contains("Pubkey") {
1109 BaseType::Pubkey
1110 } else {
1111 BaseType::Object
1112 }
1113 }
1114 };
1115
1116 (base_type, false, false, None)
1117 }
1118
1119 fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1121 let pattern = format!("{}<", generic_name);
1122 if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1123 let start = pattern.len();
1124 let end = type_str.len() - 1;
1125 if end > start {
1126 return Some(type_str[start..end].trim().to_string());
1127 }
1128 }
1129 None
1130 }
1131
1132 fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1134 let lower_name = field_name.to_lowercase();
1135
1136 if base_type == BaseType::Integer
1138 && (lower_name.ends_with("_at")
1139 || lower_name.ends_with("_time")
1140 || lower_name.contains("timestamp")
1141 || lower_name.contains("created")
1142 || lower_name.contains("settled")
1143 || lower_name.contains("activated"))
1144 {
1145 return BaseType::Timestamp;
1146 }
1147
1148 base_type
1149 }
1150}
1151
1152pub trait FieldAccessor<S> {
1153 fn path(&self) -> String;
1154}
1155
1156impl SerializableStreamSpec {
1161 pub fn compute_content_hash(&self) -> String {
1167 use sha2::{Digest, Sha256};
1168
1169 let mut spec_for_hash = self.clone();
1171 spec_for_hash.content_hash = None;
1172
1173 let json =
1175 serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1176
1177 let mut hasher = Sha256::new();
1179 hasher.update(json.as_bytes());
1180 let result = hasher.finalize();
1181
1182 hex::encode(result)
1184 }
1185
1186 pub fn verify_content_hash(&self) -> bool {
1189 match &self.content_hash {
1190 Some(hash) => {
1191 let computed = self.compute_content_hash();
1192 hash == &computed
1193 }
1194 None => true, }
1196 }
1197
1198 pub fn with_content_hash(mut self) -> Self {
1200 self.content_hash = Some(self.compute_content_hash());
1201 self
1202 }
1203}
1204
1205#[macro_export]
1206macro_rules! define_accessor {
1207 ($name:ident, $state:ty, $path:expr) => {
1208 pub struct $name;
1209
1210 impl $crate::ast::FieldAccessor<$state> for $name {
1211 fn path(&self) -> String {
1212 $path.to_string()
1213 }
1214 }
1215 };
1216}