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 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub serialization: Option<IdlSerializationSnapshot>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "lowercase")]
57pub enum IdlSerializationSnapshot {
58 Borsh,
59 Bytemuck,
60 #[serde(alias = "bytemuckunsafe")]
61 BytemuckUnsafe,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct IdlInstructionSnapshot {
67 pub name: String,
69 pub discriminator: Vec<u8>,
71 #[serde(default)]
73 pub docs: Vec<String>,
74 pub accounts: Vec<IdlInstructionAccountSnapshot>,
76 pub args: Vec<IdlFieldSnapshot>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct IdlInstructionAccountSnapshot {
83 pub name: String,
85 #[serde(default)]
87 pub writable: bool,
88 #[serde(default)]
90 pub signer: bool,
91 #[serde(default)]
93 pub optional: bool,
94 #[serde(default)]
96 pub address: Option<String>,
97 #[serde(default)]
99 pub docs: Vec<String>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct IdlFieldSnapshot {
105 pub name: String,
107 #[serde(rename = "type")]
109 pub type_: IdlTypeSnapshot,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(untagged)]
115pub enum IdlTypeSnapshot {
116 Simple(String),
118 Array(IdlArrayTypeSnapshot),
120 Option(IdlOptionTypeSnapshot),
122 Vec(IdlVecTypeSnapshot),
124 Defined(IdlDefinedTypeSnapshot),
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct IdlArrayTypeSnapshot {
131 pub array: Vec<IdlArrayElementSnapshot>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(untagged)]
138pub enum IdlArrayElementSnapshot {
139 Type(IdlTypeSnapshot),
141 TypeName(String),
143 Size(u32),
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct IdlOptionTypeSnapshot {
150 pub option: Box<IdlTypeSnapshot>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct IdlVecTypeSnapshot {
156 pub vec: Box<IdlTypeSnapshot>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct IdlDefinedTypeSnapshot {
162 pub defined: IdlDefinedInnerSnapshot,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(untagged)]
168pub enum IdlDefinedInnerSnapshot {
169 Named { name: String },
171 Simple(String),
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct IdlTypeDefSnapshot {
178 pub name: String,
180 #[serde(default)]
182 pub docs: Vec<String>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub serialization: Option<IdlSerializationSnapshot>,
185 #[serde(rename = "type")]
187 pub type_def: IdlTypeDefKindSnapshot,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(untagged)]
193pub enum IdlTypeDefKindSnapshot {
194 Struct {
196 kind: String,
197 fields: Vec<IdlFieldSnapshot>,
198 },
199 TupleStruct {
201 kind: String,
202 fields: Vec<IdlTypeSnapshot>,
203 },
204 Enum {
206 kind: String,
207 variants: Vec<IdlEnumVariantSnapshot>,
208 },
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct IdlEnumVariantSnapshot {
214 pub name: String,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct IdlEventSnapshot {
220 pub name: String,
222 pub discriminator: Vec<u8>,
224 #[serde(default)]
226 pub docs: Vec<String>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct IdlErrorSnapshot {
232 pub code: u32,
234 pub name: String,
236 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub msg: Option<String>,
239}
240
241impl IdlTypeSnapshot {
242 pub fn to_rust_type_string(&self) -> String {
244 match self {
245 IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
246 IdlTypeSnapshot::Array(arr) => {
247 if arr.array.len() == 2 {
248 match (&arr.array[0], &arr.array[1]) {
249 (
250 IdlArrayElementSnapshot::TypeName(t),
251 IdlArrayElementSnapshot::Size(size),
252 ) => {
253 format!("[{}; {}]", Self::map_simple_type(t), size)
254 }
255 (
256 IdlArrayElementSnapshot::Type(nested),
257 IdlArrayElementSnapshot::Size(size),
258 ) => {
259 format!("[{}; {}]", nested.to_rust_type_string(), size)
260 }
261 _ => "Vec<u8>".to_string(),
262 }
263 } else {
264 "Vec<u8>".to_string()
265 }
266 }
267 IdlTypeSnapshot::Option(opt) => {
268 format!("Option<{}>", opt.option.to_rust_type_string())
269 }
270 IdlTypeSnapshot::Vec(vec) => {
271 format!("Vec<{}>", vec.vec.to_rust_type_string())
272 }
273 IdlTypeSnapshot::Defined(def) => match &def.defined {
274 IdlDefinedInnerSnapshot::Named { name } => name.clone(),
275 IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
276 },
277 }
278 }
279
280 fn map_simple_type(idl_type: &str) -> String {
281 match idl_type {
282 "u8" => "u8".to_string(),
283 "u16" => "u16".to_string(),
284 "u32" => "u32".to_string(),
285 "u64" => "u64".to_string(),
286 "u128" => "u128".to_string(),
287 "i8" => "i8".to_string(),
288 "i16" => "i16".to_string(),
289 "i32" => "i32".to_string(),
290 "i64" => "i64".to_string(),
291 "i128" => "i128".to_string(),
292 "bool" => "bool".to_string(),
293 "string" => "String".to_string(),
294 "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
295 "bytes" => "Vec<u8>".to_string(),
296 _ => idl_type.to_string(),
297 }
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
302pub struct FieldPath {
303 pub segments: Vec<String>,
304 pub offsets: Option<Vec<usize>>,
305}
306
307impl FieldPath {
308 pub fn new(segments: &[&str]) -> Self {
309 FieldPath {
310 segments: segments.iter().map(|s| s.to_string()).collect(),
311 offsets: None,
312 }
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
317pub enum Transformation {
318 HexEncode,
319 HexDecode,
320 Base58Encode,
321 Base58Decode,
322 ToString,
323 ToNumber,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub enum PopulationStrategy {
328 SetOnce,
329 LastWrite,
330 Append,
331 Merge,
332 Max,
333 Sum,
335 Count,
337 Min,
339 UniqueCount,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct ComputedFieldSpec {
351 pub target_path: String,
353 pub expression: ComputedExpr,
355 pub result_type: String,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
369pub enum ComputedExpr {
370 FieldRef {
373 path: String,
374 },
375
376 UnwrapOr {
378 expr: Box<ComputedExpr>,
379 default: serde_json::Value,
380 },
381
382 Binary {
384 op: BinaryOp,
385 left: Box<ComputedExpr>,
386 right: Box<ComputedExpr>,
387 },
388
389 Cast {
391 expr: Box<ComputedExpr>,
392 to_type: String,
393 },
394
395 MethodCall {
397 expr: Box<ComputedExpr>,
398 method: String,
399 args: Vec<ComputedExpr>,
400 },
401
402 Literal {
404 value: serde_json::Value,
405 },
406
407 Paren {
409 expr: Box<ComputedExpr>,
410 },
411
412 Var {
414 name: String,
415 },
416
417 Let {
419 name: String,
420 value: Box<ComputedExpr>,
421 body: Box<ComputedExpr>,
422 },
423
424 If {
426 condition: Box<ComputedExpr>,
427 then_branch: Box<ComputedExpr>,
428 else_branch: Box<ComputedExpr>,
429 },
430
431 None,
433 Some {
434 value: Box<ComputedExpr>,
435 },
436
437 Slice {
439 expr: Box<ComputedExpr>,
440 start: usize,
441 end: usize,
442 },
443 Index {
444 expr: Box<ComputedExpr>,
445 index: usize,
446 },
447
448 U64FromLeBytes {
450 bytes: Box<ComputedExpr>,
451 },
452 U64FromBeBytes {
453 bytes: Box<ComputedExpr>,
454 },
455
456 ByteArray {
458 bytes: Vec<u8>,
459 },
460
461 Closure {
463 param: String,
464 body: Box<ComputedExpr>,
465 },
466
467 Unary {
469 op: UnaryOp,
470 expr: Box<ComputedExpr>,
471 },
472
473 JsonToBytes {
475 expr: Box<ComputedExpr>,
476 },
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub enum BinaryOp {
482 Add,
484 Sub,
485 Mul,
486 Div,
487 Mod,
488 Gt,
490 Lt,
491 Gte,
492 Lte,
493 Eq,
494 Ne,
495 And,
497 Or,
498 Xor,
500 BitAnd,
501 BitOr,
502 Shl,
503 Shr,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
508pub enum UnaryOp {
509 Not,
510 ReverseBits,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct SerializableStreamSpec {
516 pub state_name: String,
517 #[serde(default)]
519 pub program_id: Option<String>,
520 #[serde(default)]
522 pub idl: Option<IdlSnapshot>,
523 pub identity: IdentitySpec,
524 pub handlers: Vec<SerializableHandlerSpec>,
525 pub sections: Vec<EntitySection>,
526 pub field_mappings: BTreeMap<String, FieldTypeInfo>,
527 pub resolver_hooks: Vec<ResolverHook>,
528 pub instruction_hooks: Vec<InstructionHook>,
529 #[serde(default)]
531 pub computed_fields: Vec<String>,
532 #[serde(default)]
534 pub computed_field_specs: Vec<ComputedFieldSpec>,
535 #[serde(default, skip_serializing_if = "Option::is_none")]
538 pub content_hash: Option<String>,
539 #[serde(default)]
541 pub views: Vec<ViewDef>,
542}
543
544#[derive(Debug, Clone)]
545pub struct TypedStreamSpec<S> {
546 pub state_name: String,
547 pub identity: IdentitySpec,
548 pub handlers: Vec<TypedHandlerSpec<S>>,
549 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>,
555}
556
557impl<S> TypedStreamSpec<S> {
558 pub fn new(
559 state_name: String,
560 identity: IdentitySpec,
561 handlers: Vec<TypedHandlerSpec<S>>,
562 ) -> Self {
563 TypedStreamSpec {
564 state_name,
565 identity,
566 handlers,
567 sections: Vec::new(),
568 field_mappings: BTreeMap::new(),
569 resolver_hooks: Vec::new(),
570 instruction_hooks: Vec::new(),
571 computed_fields: Vec::new(),
572 _phantom: PhantomData,
573 }
574 }
575
576 pub fn with_type_info(
578 state_name: String,
579 identity: IdentitySpec,
580 handlers: Vec<TypedHandlerSpec<S>>,
581 sections: Vec<EntitySection>,
582 field_mappings: BTreeMap<String, FieldTypeInfo>,
583 ) -> Self {
584 TypedStreamSpec {
585 state_name,
586 identity,
587 handlers,
588 sections,
589 field_mappings,
590 resolver_hooks: Vec::new(),
591 instruction_hooks: Vec::new(),
592 computed_fields: Vec::new(),
593 _phantom: PhantomData,
594 }
595 }
596
597 pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
599 self.field_mappings.get(path)
600 }
601
602 pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
604 self.sections
605 .iter()
606 .find(|s| s.name == section_name)
607 .map(|s| &s.fields)
608 }
609
610 pub fn get_section_names(&self) -> Vec<&String> {
612 self.sections.iter().map(|s| &s.name).collect()
613 }
614
615 pub fn to_serializable(&self) -> SerializableStreamSpec {
617 let mut spec = SerializableStreamSpec {
618 state_name: self.state_name.clone(),
619 program_id: None,
620 idl: None,
621 identity: self.identity.clone(),
622 handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
623 sections: self.sections.clone(),
624 field_mappings: self.field_mappings.clone(),
625 resolver_hooks: self.resolver_hooks.clone(),
626 instruction_hooks: self.instruction_hooks.clone(),
627 computed_fields: self.computed_fields.clone(),
628 computed_field_specs: Vec::new(),
629 content_hash: None,
630 views: Vec::new(),
631 };
632 spec.content_hash = Some(spec.compute_content_hash());
633 spec
634 }
635
636 pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
638 TypedStreamSpec {
639 state_name: spec.state_name,
640 identity: spec.identity,
641 handlers: spec
642 .handlers
643 .into_iter()
644 .map(|h| TypedHandlerSpec::from_serializable(h))
645 .collect(),
646 sections: spec.sections,
647 field_mappings: spec.field_mappings,
648 resolver_hooks: spec.resolver_hooks,
649 instruction_hooks: spec.instruction_hooks,
650 computed_fields: spec.computed_fields,
651 _phantom: PhantomData,
652 }
653 }
654}
655
656#[derive(Debug, Clone, Serialize, Deserialize)]
657pub struct IdentitySpec {
658 pub primary_keys: Vec<String>,
659 pub lookup_indexes: Vec<LookupIndexSpec>,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize)]
663pub struct LookupIndexSpec {
664 pub field_name: String,
665 pub temporal_field: Option<String>,
666}
667
668#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct ResolverHook {
675 pub account_type: String,
677
678 pub strategy: ResolverStrategy,
680}
681
682#[derive(Debug, Clone, Serialize, Deserialize)]
683pub enum ResolverStrategy {
684 PdaReverseLookup {
686 lookup_name: String,
687 queue_discriminators: Vec<Vec<u8>>,
689 },
690
691 DirectField { field_path: FieldPath },
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize)]
697pub struct InstructionHook {
698 pub instruction_type: String,
700
701 pub actions: Vec<HookAction>,
703
704 pub lookup_by: Option<FieldPath>,
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize)]
709pub enum HookAction {
710 RegisterPdaMapping {
712 pda_field: FieldPath,
713 seed_field: FieldPath,
714 lookup_name: String,
715 },
716
717 SetField {
719 target_field: String,
720 source: MappingSource,
721 condition: Option<ConditionExpr>,
722 },
723
724 IncrementField {
726 target_field: String,
727 increment_by: i64,
728 condition: Option<ConditionExpr>,
729 },
730}
731
732#[derive(Debug, Clone, Serialize, Deserialize)]
734pub struct ConditionExpr {
735 pub expression: String,
737
738 pub parsed: Option<ParsedCondition>,
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize)]
743pub enum ParsedCondition {
744 Comparison {
746 field: FieldPath,
747 op: ComparisonOp,
748 value: serde_json::Value,
749 },
750
751 Logical {
753 op: LogicalOp,
754 conditions: Vec<ParsedCondition>,
755 },
756}
757
758#[derive(Debug, Clone, Serialize, Deserialize)]
759pub enum ComparisonOp {
760 Equal,
761 NotEqual,
762 GreaterThan,
763 GreaterThanOrEqual,
764 LessThan,
765 LessThanOrEqual,
766}
767
768#[derive(Debug, Clone, Serialize, Deserialize)]
769pub enum LogicalOp {
770 And,
771 Or,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
776pub struct SerializableHandlerSpec {
777 pub source: SourceSpec,
778 pub key_resolution: KeyResolutionStrategy,
779 pub mappings: Vec<SerializableFieldMapping>,
780 pub conditions: Vec<Condition>,
781 pub emit: bool,
782}
783
784#[derive(Debug, Clone)]
785pub struct TypedHandlerSpec<S> {
786 pub source: SourceSpec,
787 pub key_resolution: KeyResolutionStrategy,
788 pub mappings: Vec<TypedFieldMapping<S>>,
789 pub conditions: Vec<Condition>,
790 pub emit: bool,
791 _phantom: PhantomData<S>,
792}
793
794impl<S> TypedHandlerSpec<S> {
795 pub fn new(
796 source: SourceSpec,
797 key_resolution: KeyResolutionStrategy,
798 mappings: Vec<TypedFieldMapping<S>>,
799 emit: bool,
800 ) -> Self {
801 TypedHandlerSpec {
802 source,
803 key_resolution,
804 mappings,
805 conditions: vec![],
806 emit,
807 _phantom: PhantomData,
808 }
809 }
810
811 pub fn to_serializable(&self) -> SerializableHandlerSpec {
813 SerializableHandlerSpec {
814 source: self.source.clone(),
815 key_resolution: self.key_resolution.clone(),
816 mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
817 conditions: self.conditions.clone(),
818 emit: self.emit,
819 }
820 }
821
822 pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
824 TypedHandlerSpec {
825 source: spec.source,
826 key_resolution: spec.key_resolution,
827 mappings: spec
828 .mappings
829 .into_iter()
830 .map(|m| TypedFieldMapping::from_serializable(m))
831 .collect(),
832 conditions: spec.conditions,
833 emit: spec.emit,
834 _phantom: PhantomData,
835 }
836 }
837}
838
839#[derive(Debug, Clone, Serialize, Deserialize)]
840pub enum KeyResolutionStrategy {
841 Embedded {
842 primary_field: FieldPath,
843 },
844 Lookup {
845 primary_field: FieldPath,
846 },
847 Computed {
848 primary_field: FieldPath,
849 compute_partition: ComputeFunction,
850 },
851 TemporalLookup {
852 lookup_field: FieldPath,
853 timestamp_field: FieldPath,
854 index_name: String,
855 },
856}
857
858#[derive(Debug, Clone, Serialize, Deserialize)]
859pub enum SourceSpec {
860 Source {
861 program_id: Option<String>,
862 discriminator: Option<Vec<u8>>,
863 type_name: String,
864 #[serde(default, skip_serializing_if = "Option::is_none")]
865 serialization: Option<IdlSerializationSnapshot>,
866 },
867}
868
869#[derive(Debug, Clone, Serialize, Deserialize)]
871pub struct SerializableFieldMapping {
872 pub target_path: String,
873 pub source: MappingSource,
874 pub transform: Option<Transformation>,
875 pub population: PopulationStrategy,
876}
877
878#[derive(Debug, Clone)]
879pub struct TypedFieldMapping<S> {
880 pub target_path: String,
881 pub source: MappingSource,
882 pub transform: Option<Transformation>,
883 pub population: PopulationStrategy,
884 _phantom: PhantomData<S>,
885}
886
887impl<S> TypedFieldMapping<S> {
888 pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
889 TypedFieldMapping {
890 target_path,
891 source,
892 transform: None,
893 population,
894 _phantom: PhantomData,
895 }
896 }
897
898 pub fn with_transform(mut self, transform: Transformation) -> Self {
899 self.transform = Some(transform);
900 self
901 }
902
903 pub fn to_serializable(&self) -> SerializableFieldMapping {
905 SerializableFieldMapping {
906 target_path: self.target_path.clone(),
907 source: self.source.clone(),
908 transform: self.transform.clone(),
909 population: self.population.clone(),
910 }
911 }
912
913 pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
915 TypedFieldMapping {
916 target_path: mapping.target_path,
917 source: mapping.source,
918 transform: mapping.transform,
919 population: mapping.population,
920 _phantom: PhantomData,
921 }
922 }
923}
924
925#[derive(Debug, Clone, Serialize, Deserialize)]
926pub enum MappingSource {
927 FromSource {
928 path: FieldPath,
929 default: Option<Value>,
930 transform: Option<Transformation>,
931 },
932 Constant(Value),
933 Computed {
934 inputs: Vec<FieldPath>,
935 function: ComputeFunction,
936 },
937 FromState {
938 path: String,
939 },
940 AsEvent {
941 fields: Vec<Box<MappingSource>>,
942 },
943 WholeSource,
944 AsCapture {
947 field_transforms: BTreeMap<String, Transformation>,
948 },
949 FromContext {
952 field: String,
953 },
954}
955
956impl MappingSource {
957 pub fn with_transform(self, transform: Transformation) -> Self {
958 match self {
959 MappingSource::FromSource {
960 path,
961 default,
962 transform: _,
963 } => MappingSource::FromSource {
964 path,
965 default,
966 transform: Some(transform),
967 },
968 other => other,
969 }
970 }
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize)]
974pub enum ComputeFunction {
975 Sum,
976 Concat,
977 Format(String),
978 Custom(String),
979}
980
981#[derive(Debug, Clone, Serialize, Deserialize)]
982pub struct Condition {
983 pub field: FieldPath,
984 pub operator: ConditionOp,
985 pub value: Value,
986}
987
988#[derive(Debug, Clone, Serialize, Deserialize)]
989pub enum ConditionOp {
990 Equals,
991 NotEquals,
992 GreaterThan,
993 LessThan,
994 Contains,
995 Exists,
996}
997
998#[derive(Debug, Clone, Serialize, Deserialize)]
1000pub struct FieldTypeInfo {
1001 pub field_name: String,
1002 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)]
1010 pub resolved_type: Option<ResolvedStructType>,
1011}
1012
1013#[derive(Debug, Clone, Serialize, Deserialize)]
1015pub struct ResolvedStructType {
1016 pub type_name: String,
1017 pub fields: Vec<ResolvedField>,
1018 pub is_instruction: bool,
1019 pub is_account: bool,
1020 pub is_event: bool,
1021 #[serde(default)]
1023 pub is_enum: bool,
1024 #[serde(default)]
1026 pub enum_variants: Vec<String>,
1027}
1028
1029#[derive(Debug, Clone, Serialize, Deserialize)]
1031pub struct ResolvedField {
1032 pub field_name: String,
1033 pub field_type: String,
1034 pub base_type: BaseType,
1035 pub is_optional: bool,
1036 pub is_array: bool,
1037}
1038
1039#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041pub enum BaseType {
1042 Integer, Float, String, Boolean, Object, Array, Binary, Timestamp, Pubkey, Any, }
1058
1059#[derive(Debug, Clone, Serialize, Deserialize)]
1061pub struct EntitySection {
1062 pub name: String,
1063 pub fields: Vec<FieldTypeInfo>,
1064 pub is_nested_struct: bool,
1065 pub parent_field: Option<String>, }
1067
1068impl FieldTypeInfo {
1069 pub fn new(field_name: String, rust_type_name: String) -> Self {
1070 let (base_type, is_optional, is_array, inner_type) =
1071 Self::analyze_rust_type(&rust_type_name);
1072
1073 FieldTypeInfo {
1074 field_name: field_name.clone(),
1075 rust_type_name,
1076 base_type: Self::infer_semantic_type(&field_name, base_type),
1077 is_optional,
1078 is_array,
1079 inner_type,
1080 source_path: None,
1081 resolved_type: None,
1082 }
1083 }
1084
1085 pub fn with_source_path(mut self, source_path: String) -> Self {
1086 self.source_path = Some(source_path);
1087 self
1088 }
1089
1090 fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
1092 let type_str = rust_type.trim();
1093
1094 if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1096 let (inner_base_type, _, inner_is_array, inner_inner_type) =
1097 Self::analyze_rust_type(&inner);
1098 return (
1099 inner_base_type,
1100 true,
1101 inner_is_array,
1102 inner_inner_type.or(Some(inner)),
1103 );
1104 }
1105
1106 if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1108 let (_inner_base_type, inner_is_optional, _, inner_inner_type) =
1109 Self::analyze_rust_type(&inner);
1110 return (
1111 BaseType::Array,
1112 inner_is_optional,
1113 true,
1114 inner_inner_type.or(Some(inner)),
1115 );
1116 }
1117
1118 let base_type = match type_str {
1120 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
1121 BaseType::Integer
1122 }
1123 "f32" | "f64" => BaseType::Float,
1124 "bool" => BaseType::Boolean,
1125 "String" | "&str" | "str" => BaseType::String,
1126 "Value" | "serde_json::Value" => BaseType::Any,
1127 "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1128 _ => {
1129 if type_str.contains("Bytes") || type_str.contains("bytes") {
1131 BaseType::Binary
1132 } else if type_str.contains("Pubkey") {
1133 BaseType::Pubkey
1134 } else {
1135 BaseType::Object
1136 }
1137 }
1138 };
1139
1140 (base_type, false, false, None)
1141 }
1142
1143 fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1145 let pattern = format!("{}<", generic_name);
1146 if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1147 let start = pattern.len();
1148 let end = type_str.len() - 1;
1149 if end > start {
1150 return Some(type_str[start..end].trim().to_string());
1151 }
1152 }
1153 None
1154 }
1155
1156 fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1158 let lower_name = field_name.to_lowercase();
1159
1160 if base_type == BaseType::Integer
1162 && (lower_name.ends_with("_at")
1163 || lower_name.ends_with("_time")
1164 || lower_name.contains("timestamp")
1165 || lower_name.contains("created")
1166 || lower_name.contains("settled")
1167 || lower_name.contains("activated"))
1168 {
1169 return BaseType::Timestamp;
1170 }
1171
1172 base_type
1173 }
1174}
1175
1176pub trait FieldAccessor<S> {
1177 fn path(&self) -> String;
1178}
1179
1180impl SerializableStreamSpec {
1185 pub fn compute_content_hash(&self) -> String {
1191 use sha2::{Digest, Sha256};
1192
1193 let mut spec_for_hash = self.clone();
1195 spec_for_hash.content_hash = None;
1196
1197 let json =
1199 serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1200
1201 let mut hasher = Sha256::new();
1203 hasher.update(json.as_bytes());
1204 let result = hasher.finalize();
1205
1206 hex::encode(result)
1208 }
1209
1210 pub fn verify_content_hash(&self) -> bool {
1213 match &self.content_hash {
1214 Some(hash) => {
1215 let computed = self.compute_content_hash();
1216 hash == &computed
1217 }
1218 None => true, }
1220 }
1221
1222 pub fn with_content_hash(mut self) -> Self {
1224 self.content_hash = Some(self.compute_content_hash());
1225 self
1226 }
1227}
1228
1229#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1235#[serde(rename_all = "lowercase")]
1236pub enum SortOrder {
1237 #[default]
1238 Asc,
1239 Desc,
1240}
1241
1242#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1244pub enum CompareOp {
1245 Eq,
1246 Ne,
1247 Gt,
1248 Gte,
1249 Lt,
1250 Lte,
1251}
1252
1253#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1255pub enum PredicateValue {
1256 Literal(serde_json::Value),
1258 Dynamic(String),
1260 Field(FieldPath),
1262}
1263
1264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1266pub enum Predicate {
1267 Compare {
1269 field: FieldPath,
1270 op: CompareOp,
1271 value: PredicateValue,
1272 },
1273 And(Vec<Predicate>),
1275 Or(Vec<Predicate>),
1277 Not(Box<Predicate>),
1279 Exists { field: FieldPath },
1281}
1282
1283#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1285pub enum ViewTransform {
1286 Filter { predicate: Predicate },
1288
1289 Sort {
1291 key: FieldPath,
1292 #[serde(default)]
1293 order: SortOrder,
1294 },
1295
1296 Take { count: usize },
1298
1299 Skip { count: usize },
1301
1302 First,
1304
1305 Last,
1307
1308 MaxBy { key: FieldPath },
1310
1311 MinBy { key: FieldPath },
1313}
1314
1315#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1317pub enum ViewSource {
1318 Entity { name: String },
1320 View { id: String },
1322}
1323
1324#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1326pub enum ViewOutput {
1327 #[default]
1329 Collection,
1330 Single,
1332 Keyed { key_field: FieldPath },
1334}
1335
1336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1338pub struct ViewDef {
1339 pub id: String,
1341
1342 pub source: ViewSource,
1344
1345 #[serde(default)]
1347 pub pipeline: Vec<ViewTransform>,
1348
1349 #[serde(default)]
1351 pub output: ViewOutput,
1352}
1353
1354impl ViewDef {
1355 pub fn list(entity_name: &str) -> Self {
1357 ViewDef {
1358 id: format!("{}/list", entity_name),
1359 source: ViewSource::Entity {
1360 name: entity_name.to_string(),
1361 },
1362 pipeline: vec![],
1363 output: ViewOutput::Collection,
1364 }
1365 }
1366
1367 pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1369 ViewDef {
1370 id: format!("{}/state", entity_name),
1371 source: ViewSource::Entity {
1372 name: entity_name.to_string(),
1373 },
1374 pipeline: vec![],
1375 output: ViewOutput::Keyed {
1376 key_field: FieldPath::new(key_field),
1377 },
1378 }
1379 }
1380
1381 pub fn is_single(&self) -> bool {
1383 matches!(self.output, ViewOutput::Single)
1384 }
1385
1386 pub fn has_single_transform(&self) -> bool {
1388 self.pipeline.iter().any(|t| {
1389 matches!(
1390 t,
1391 ViewTransform::First
1392 | ViewTransform::Last
1393 | ViewTransform::MaxBy { .. }
1394 | ViewTransform::MinBy { .. }
1395 )
1396 })
1397 }
1398}
1399
1400#[macro_export]
1401macro_rules! define_accessor {
1402 ($name:ident, $state:ty, $path:expr) => {
1403 pub struct $name;
1404
1405 impl $crate::ast::FieldAccessor<$state> for $name {
1406 fn path(&self) -> String {
1407 $path.to_string()
1408 }
1409 }
1410 };
1411}