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 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub program_id: Option<String>,
19 pub version: String,
21 pub accounts: Vec<IdlAccountSnapshot>,
23 pub instructions: Vec<IdlInstructionSnapshot>,
25 #[serde(default)]
27 pub types: Vec<IdlTypeDefSnapshot>,
28 #[serde(default)]
30 pub events: Vec<IdlEventSnapshot>,
31 #[serde(default)]
33 pub errors: Vec<IdlErrorSnapshot>,
34 #[serde(default = "default_discriminant_size")]
37 pub discriminant_size: usize,
38}
39
40fn default_discriminant_size() -> usize {
41 8
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct IdlAccountSnapshot {
47 pub name: String,
49 pub discriminator: Vec<u8>,
51 #[serde(default)]
53 pub docs: Vec<String>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub serialization: Option<IdlSerializationSnapshot>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "lowercase")]
60pub enum IdlSerializationSnapshot {
61 Borsh,
62 Bytemuck,
63 #[serde(alias = "bytemuckunsafe")]
64 BytemuckUnsafe,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct IdlInstructionSnapshot {
70 pub name: String,
72 pub discriminator: Vec<u8>,
74 #[serde(default)]
76 pub docs: Vec<String>,
77 pub accounts: Vec<IdlInstructionAccountSnapshot>,
79 pub args: Vec<IdlFieldSnapshot>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct IdlInstructionAccountSnapshot {
86 pub name: String,
88 #[serde(default)]
90 pub writable: bool,
91 #[serde(default)]
93 pub signer: bool,
94 #[serde(default)]
96 pub optional: bool,
97 #[serde(default)]
99 pub address: Option<String>,
100 #[serde(default)]
102 pub docs: Vec<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct IdlFieldSnapshot {
108 pub name: String,
110 #[serde(rename = "type")]
112 pub type_: IdlTypeSnapshot,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(untagged)]
118pub enum IdlTypeSnapshot {
119 Simple(String),
121 Array(IdlArrayTypeSnapshot),
123 Option(IdlOptionTypeSnapshot),
125 Vec(IdlVecTypeSnapshot),
127 Defined(IdlDefinedTypeSnapshot),
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct IdlArrayTypeSnapshot {
134 pub array: Vec<IdlArrayElementSnapshot>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(untagged)]
141pub enum IdlArrayElementSnapshot {
142 Type(IdlTypeSnapshot),
144 TypeName(String),
146 Size(u32),
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct IdlOptionTypeSnapshot {
153 pub option: Box<IdlTypeSnapshot>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct IdlVecTypeSnapshot {
159 pub vec: Box<IdlTypeSnapshot>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct IdlDefinedTypeSnapshot {
165 pub defined: IdlDefinedInnerSnapshot,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(untagged)]
171pub enum IdlDefinedInnerSnapshot {
172 Named { name: String },
174 Simple(String),
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct IdlTypeDefSnapshot {
181 pub name: String,
183 #[serde(default)]
185 pub docs: Vec<String>,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub serialization: Option<IdlSerializationSnapshot>,
188 #[serde(rename = "type")]
190 pub type_def: IdlTypeDefKindSnapshot,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(untagged)]
196pub enum IdlTypeDefKindSnapshot {
197 Struct {
199 kind: String,
200 fields: Vec<IdlFieldSnapshot>,
201 },
202 TupleStruct {
204 kind: String,
205 fields: Vec<IdlTypeSnapshot>,
206 },
207 Enum {
209 kind: String,
210 variants: Vec<IdlEnumVariantSnapshot>,
211 },
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct IdlEnumVariantSnapshot {
217 pub name: String,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct IdlEventSnapshot {
223 pub name: String,
225 pub discriminator: Vec<u8>,
227 #[serde(default)]
229 pub docs: Vec<String>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234pub struct IdlErrorSnapshot {
235 pub code: u32,
237 pub name: String,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
241 pub msg: Option<String>,
242}
243
244impl IdlTypeSnapshot {
245 pub fn to_rust_type_string(&self) -> String {
247 match self {
248 IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
249 IdlTypeSnapshot::Array(arr) => {
250 if arr.array.len() == 2 {
251 match (&arr.array[0], &arr.array[1]) {
252 (
253 IdlArrayElementSnapshot::TypeName(t),
254 IdlArrayElementSnapshot::Size(size),
255 ) => {
256 format!("[{}; {}]", Self::map_simple_type(t), size)
257 }
258 (
259 IdlArrayElementSnapshot::Type(nested),
260 IdlArrayElementSnapshot::Size(size),
261 ) => {
262 format!("[{}; {}]", nested.to_rust_type_string(), size)
263 }
264 _ => "Vec<u8>".to_string(),
265 }
266 } else {
267 "Vec<u8>".to_string()
268 }
269 }
270 IdlTypeSnapshot::Option(opt) => {
271 format!("Option<{}>", opt.option.to_rust_type_string())
272 }
273 IdlTypeSnapshot::Vec(vec) => {
274 format!("Vec<{}>", vec.vec.to_rust_type_string())
275 }
276 IdlTypeSnapshot::Defined(def) => match &def.defined {
277 IdlDefinedInnerSnapshot::Named { name } => name.clone(),
278 IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
279 },
280 }
281 }
282
283 fn map_simple_type(idl_type: &str) -> String {
284 match idl_type {
285 "u8" => "u8".to_string(),
286 "u16" => "u16".to_string(),
287 "u32" => "u32".to_string(),
288 "u64" => "u64".to_string(),
289 "u128" => "u128".to_string(),
290 "i8" => "i8".to_string(),
291 "i16" => "i16".to_string(),
292 "i32" => "i32".to_string(),
293 "i64" => "i64".to_string(),
294 "i128" => "i128".to_string(),
295 "bool" => "bool".to_string(),
296 "string" => "String".to_string(),
297 "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
298 "bytes" => "Vec<u8>".to_string(),
299 _ => idl_type.to_string(),
300 }
301 }
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
305pub struct FieldPath {
306 pub segments: Vec<String>,
307 pub offsets: Option<Vec<usize>>,
308}
309
310impl FieldPath {
311 pub fn new(segments: &[&str]) -> Self {
312 FieldPath {
313 segments: segments.iter().map(|s| s.to_string()).collect(),
314 offsets: None,
315 }
316 }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
320pub enum Transformation {
321 HexEncode,
322 HexDecode,
323 Base58Encode,
324 Base58Decode,
325 ToString,
326 ToNumber,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub enum PopulationStrategy {
331 SetOnce,
332 LastWrite,
333 Append,
334 Merge,
335 Max,
336 Sum,
338 Count,
340 Min,
342 UniqueCount,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct ComputedFieldSpec {
354 pub target_path: String,
356 pub expression: ComputedExpr,
358 pub result_type: String,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
367#[serde(rename_all = "lowercase")]
368pub enum ResolverType {
369 Token,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct ResolverExtractSpec {
374 pub target_path: String,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub source_path: Option<String>,
377 #[serde(default, skip_serializing_if = "Option::is_none")]
378 pub transform: Option<Transformation>,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
382pub enum ResolveStrategy {
383 #[default]
384 SetOnce,
385 LastWrite,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct ResolverSpec {
390 pub resolver: ResolverType,
391 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub input_path: Option<String>,
393 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub input_value: Option<Value>,
395 #[serde(default)]
396 pub strategy: ResolveStrategy,
397 pub extracts: Vec<ResolverExtractSpec>,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
410pub enum ComputedExpr {
411 FieldRef {
414 path: String,
415 },
416
417 UnwrapOr {
419 expr: Box<ComputedExpr>,
420 default: serde_json::Value,
421 },
422
423 Binary {
425 op: BinaryOp,
426 left: Box<ComputedExpr>,
427 right: Box<ComputedExpr>,
428 },
429
430 Cast {
432 expr: Box<ComputedExpr>,
433 to_type: String,
434 },
435
436 MethodCall {
438 expr: Box<ComputedExpr>,
439 method: String,
440 args: Vec<ComputedExpr>,
441 },
442
443 ResolverComputed {
445 resolver: String,
446 method: String,
447 args: Vec<ComputedExpr>,
448 },
449
450 Literal {
452 value: serde_json::Value,
453 },
454
455 Paren {
457 expr: Box<ComputedExpr>,
458 },
459
460 Var {
462 name: String,
463 },
464
465 Let {
467 name: String,
468 value: Box<ComputedExpr>,
469 body: Box<ComputedExpr>,
470 },
471
472 If {
474 condition: Box<ComputedExpr>,
475 then_branch: Box<ComputedExpr>,
476 else_branch: Box<ComputedExpr>,
477 },
478
479 None,
481 Some {
482 value: Box<ComputedExpr>,
483 },
484
485 Slice {
487 expr: Box<ComputedExpr>,
488 start: usize,
489 end: usize,
490 },
491 Index {
492 expr: Box<ComputedExpr>,
493 index: usize,
494 },
495
496 U64FromLeBytes {
498 bytes: Box<ComputedExpr>,
499 },
500 U64FromBeBytes {
501 bytes: Box<ComputedExpr>,
502 },
503
504 ByteArray {
506 bytes: Vec<u8>,
507 },
508
509 Closure {
511 param: String,
512 body: Box<ComputedExpr>,
513 },
514
515 Unary {
517 op: UnaryOp,
518 expr: Box<ComputedExpr>,
519 },
520
521 JsonToBytes {
523 expr: Box<ComputedExpr>,
524 },
525
526 ContextSlot,
529 ContextTimestamp,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize)]
535pub enum BinaryOp {
536 Add,
538 Sub,
539 Mul,
540 Div,
541 Mod,
542 Gt,
544 Lt,
545 Gte,
546 Lte,
547 Eq,
548 Ne,
549 And,
551 Or,
552 Xor,
554 BitAnd,
555 BitOr,
556 Shl,
557 Shr,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
562pub enum UnaryOp {
563 Not,
564 ReverseBits,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct SerializableStreamSpec {
570 pub state_name: String,
571 #[serde(default)]
573 pub program_id: Option<String>,
574 #[serde(default)]
576 pub idl: Option<IdlSnapshot>,
577 pub identity: IdentitySpec,
578 pub handlers: Vec<SerializableHandlerSpec>,
579 pub sections: Vec<EntitySection>,
580 pub field_mappings: BTreeMap<String, FieldTypeInfo>,
581 pub resolver_hooks: Vec<ResolverHook>,
582 pub instruction_hooks: Vec<InstructionHook>,
583 #[serde(default)]
584 pub resolver_specs: Vec<ResolverSpec>,
585 #[serde(default)]
587 pub computed_fields: Vec<String>,
588 #[serde(default)]
590 pub computed_field_specs: Vec<ComputedFieldSpec>,
591 #[serde(default, skip_serializing_if = "Option::is_none")]
594 pub content_hash: Option<String>,
595 #[serde(default)]
597 pub views: Vec<ViewDef>,
598}
599
600#[derive(Debug, Clone)]
601pub struct TypedStreamSpec<S> {
602 pub state_name: String,
603 pub identity: IdentitySpec,
604 pub handlers: Vec<TypedHandlerSpec<S>>,
605 pub sections: Vec<EntitySection>, pub field_mappings: BTreeMap<String, FieldTypeInfo>, pub resolver_hooks: Vec<ResolverHook>, pub instruction_hooks: Vec<InstructionHook>, pub resolver_specs: Vec<ResolverSpec>,
610 pub computed_fields: Vec<String>, _phantom: PhantomData<S>,
612}
613
614impl<S> TypedStreamSpec<S> {
615 pub fn new(
616 state_name: String,
617 identity: IdentitySpec,
618 handlers: Vec<TypedHandlerSpec<S>>,
619 ) -> Self {
620 TypedStreamSpec {
621 state_name,
622 identity,
623 handlers,
624 sections: Vec::new(),
625 field_mappings: BTreeMap::new(),
626 resolver_hooks: Vec::new(),
627 instruction_hooks: Vec::new(),
628 resolver_specs: Vec::new(),
629 computed_fields: Vec::new(),
630 _phantom: PhantomData,
631 }
632 }
633
634 pub fn with_type_info(
636 state_name: String,
637 identity: IdentitySpec,
638 handlers: Vec<TypedHandlerSpec<S>>,
639 sections: Vec<EntitySection>,
640 field_mappings: BTreeMap<String, FieldTypeInfo>,
641 ) -> Self {
642 TypedStreamSpec {
643 state_name,
644 identity,
645 handlers,
646 sections,
647 field_mappings,
648 resolver_hooks: Vec::new(),
649 instruction_hooks: Vec::new(),
650 resolver_specs: Vec::new(),
651 computed_fields: Vec::new(),
652 _phantom: PhantomData,
653 }
654 }
655
656 pub fn with_resolver_specs(mut self, resolver_specs: Vec<ResolverSpec>) -> Self {
657 self.resolver_specs = resolver_specs;
658 self
659 }
660
661 pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
663 self.field_mappings.get(path)
664 }
665
666 pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
668 self.sections
669 .iter()
670 .find(|s| s.name == section_name)
671 .map(|s| &s.fields)
672 }
673
674 pub fn get_section_names(&self) -> Vec<&String> {
676 self.sections.iter().map(|s| &s.name).collect()
677 }
678
679 pub fn to_serializable(&self) -> SerializableStreamSpec {
681 let mut spec = SerializableStreamSpec {
682 state_name: self.state_name.clone(),
683 program_id: None,
684 idl: None,
685 identity: self.identity.clone(),
686 handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
687 sections: self.sections.clone(),
688 field_mappings: self.field_mappings.clone(),
689 resolver_hooks: self.resolver_hooks.clone(),
690 instruction_hooks: self.instruction_hooks.clone(),
691 resolver_specs: self.resolver_specs.clone(),
692 computed_fields: self.computed_fields.clone(),
693 computed_field_specs: Vec::new(),
694 content_hash: None,
695 views: Vec::new(),
696 };
697 spec.content_hash = Some(spec.compute_content_hash());
698 spec
699 }
700
701 pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
703 TypedStreamSpec {
704 state_name: spec.state_name,
705 identity: spec.identity,
706 handlers: spec
707 .handlers
708 .into_iter()
709 .map(|h| TypedHandlerSpec::from_serializable(h))
710 .collect(),
711 sections: spec.sections,
712 field_mappings: spec.field_mappings,
713 resolver_hooks: spec.resolver_hooks,
714 instruction_hooks: spec.instruction_hooks,
715 resolver_specs: spec.resolver_specs,
716 computed_fields: spec.computed_fields,
717 _phantom: PhantomData,
718 }
719 }
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
723pub struct IdentitySpec {
724 pub primary_keys: Vec<String>,
725 pub lookup_indexes: Vec<LookupIndexSpec>,
726}
727
728#[derive(Debug, Clone, Serialize, Deserialize)]
729pub struct LookupIndexSpec {
730 pub field_name: String,
731 pub temporal_field: Option<String>,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct ResolverHook {
741 pub account_type: String,
743
744 pub strategy: ResolverStrategy,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
749pub enum ResolverStrategy {
750 PdaReverseLookup {
752 lookup_name: String,
753 queue_discriminators: Vec<Vec<u8>>,
755 },
756
757 DirectField { field_path: FieldPath },
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct InstructionHook {
764 pub instruction_type: String,
766
767 pub actions: Vec<HookAction>,
769
770 pub lookup_by: Option<FieldPath>,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
775pub enum HookAction {
776 RegisterPdaMapping {
778 pda_field: FieldPath,
779 seed_field: FieldPath,
780 lookup_name: String,
781 },
782
783 SetField {
785 target_field: String,
786 source: MappingSource,
787 condition: Option<ConditionExpr>,
788 },
789
790 IncrementField {
792 target_field: String,
793 increment_by: i64,
794 condition: Option<ConditionExpr>,
795 },
796}
797
798#[derive(Debug, Clone, Serialize, Deserialize)]
800pub struct ConditionExpr {
801 pub expression: String,
803
804 pub parsed: Option<ParsedCondition>,
806}
807
808#[derive(Debug, Clone, Serialize, Deserialize)]
809pub enum ParsedCondition {
810 Comparison {
812 field: FieldPath,
813 op: ComparisonOp,
814 value: serde_json::Value,
815 },
816
817 Logical {
819 op: LogicalOp,
820 conditions: Vec<ParsedCondition>,
821 },
822}
823
824#[derive(Debug, Clone, Serialize, Deserialize)]
825pub enum ComparisonOp {
826 Equal,
827 NotEqual,
828 GreaterThan,
829 GreaterThanOrEqual,
830 LessThan,
831 LessThanOrEqual,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub enum LogicalOp {
836 And,
837 Or,
838}
839
840#[derive(Debug, Clone, Serialize, Deserialize)]
842pub struct SerializableHandlerSpec {
843 pub source: SourceSpec,
844 pub key_resolution: KeyResolutionStrategy,
845 pub mappings: Vec<SerializableFieldMapping>,
846 pub conditions: Vec<Condition>,
847 pub emit: bool,
848}
849
850#[derive(Debug, Clone)]
851pub struct TypedHandlerSpec<S> {
852 pub source: SourceSpec,
853 pub key_resolution: KeyResolutionStrategy,
854 pub mappings: Vec<TypedFieldMapping<S>>,
855 pub conditions: Vec<Condition>,
856 pub emit: bool,
857 _phantom: PhantomData<S>,
858}
859
860impl<S> TypedHandlerSpec<S> {
861 pub fn new(
862 source: SourceSpec,
863 key_resolution: KeyResolutionStrategy,
864 mappings: Vec<TypedFieldMapping<S>>,
865 emit: bool,
866 ) -> Self {
867 TypedHandlerSpec {
868 source,
869 key_resolution,
870 mappings,
871 conditions: vec![],
872 emit,
873 _phantom: PhantomData,
874 }
875 }
876
877 pub fn to_serializable(&self) -> SerializableHandlerSpec {
879 SerializableHandlerSpec {
880 source: self.source.clone(),
881 key_resolution: self.key_resolution.clone(),
882 mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
883 conditions: self.conditions.clone(),
884 emit: self.emit,
885 }
886 }
887
888 pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
890 TypedHandlerSpec {
891 source: spec.source,
892 key_resolution: spec.key_resolution,
893 mappings: spec
894 .mappings
895 .into_iter()
896 .map(|m| TypedFieldMapping::from_serializable(m))
897 .collect(),
898 conditions: spec.conditions,
899 emit: spec.emit,
900 _phantom: PhantomData,
901 }
902 }
903}
904
905#[derive(Debug, Clone, Serialize, Deserialize)]
906pub enum KeyResolutionStrategy {
907 Embedded {
908 primary_field: FieldPath,
909 },
910 Lookup {
911 primary_field: FieldPath,
912 },
913 Computed {
914 primary_field: FieldPath,
915 compute_partition: ComputeFunction,
916 },
917 TemporalLookup {
918 lookup_field: FieldPath,
919 timestamp_field: FieldPath,
920 index_name: String,
921 },
922}
923
924#[derive(Debug, Clone, Serialize, Deserialize)]
925pub enum SourceSpec {
926 Source {
927 program_id: Option<String>,
928 discriminator: Option<Vec<u8>>,
929 type_name: String,
930 #[serde(default, skip_serializing_if = "Option::is_none")]
931 serialization: Option<IdlSerializationSnapshot>,
932 },
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct SerializableFieldMapping {
938 pub target_path: String,
939 pub source: MappingSource,
940 pub transform: Option<Transformation>,
941 pub population: PopulationStrategy,
942 #[serde(default, skip_serializing_if = "Option::is_none")]
943 pub condition: Option<ConditionExpr>,
944 #[serde(default, skip_serializing_if = "Option::is_none")]
945 pub when: Option<String>,
946 #[serde(default, skip_serializing_if = "Option::is_none")]
947 pub stop: Option<String>,
948 #[serde(default = "default_emit", skip_serializing_if = "is_true")]
949 pub emit: bool,
950}
951
952fn default_emit() -> bool {
953 true
954}
955
956fn is_true(value: &bool) -> bool {
957 *value
958}
959
960#[derive(Debug, Clone)]
961pub struct TypedFieldMapping<S> {
962 pub target_path: String,
963 pub source: MappingSource,
964 pub transform: Option<Transformation>,
965 pub population: PopulationStrategy,
966 pub condition: Option<ConditionExpr>,
967 pub when: Option<String>,
968 pub stop: Option<String>,
969 pub emit: bool,
970 _phantom: PhantomData<S>,
971}
972
973impl<S> TypedFieldMapping<S> {
974 pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
975 TypedFieldMapping {
976 target_path,
977 source,
978 transform: None,
979 population,
980 condition: None,
981 when: None,
982 stop: None,
983 emit: true,
984 _phantom: PhantomData,
985 }
986 }
987
988 pub fn with_transform(mut self, transform: Transformation) -> Self {
989 self.transform = Some(transform);
990 self
991 }
992
993 pub fn with_condition(mut self, condition: ConditionExpr) -> Self {
994 self.condition = Some(condition);
995 self
996 }
997
998 pub fn with_when(mut self, when: String) -> Self {
999 self.when = Some(when);
1000 self
1001 }
1002
1003 pub fn with_stop(mut self, stop: String) -> Self {
1004 self.stop = Some(stop);
1005 self
1006 }
1007
1008 pub fn with_emit(mut self, emit: bool) -> Self {
1009 self.emit = emit;
1010 self
1011 }
1012
1013 pub fn to_serializable(&self) -> SerializableFieldMapping {
1015 SerializableFieldMapping {
1016 target_path: self.target_path.clone(),
1017 source: self.source.clone(),
1018 transform: self.transform.clone(),
1019 population: self.population.clone(),
1020 condition: self.condition.clone(),
1021 when: self.when.clone(),
1022 stop: self.stop.clone(),
1023 emit: self.emit,
1024 }
1025 }
1026
1027 pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
1029 TypedFieldMapping {
1030 target_path: mapping.target_path,
1031 source: mapping.source,
1032 transform: mapping.transform,
1033 population: mapping.population,
1034 condition: mapping.condition,
1035 when: mapping.when,
1036 stop: mapping.stop,
1037 emit: mapping.emit,
1038 _phantom: PhantomData,
1039 }
1040 }
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044pub enum MappingSource {
1045 FromSource {
1046 path: FieldPath,
1047 default: Option<Value>,
1048 transform: Option<Transformation>,
1049 },
1050 Constant(Value),
1051 Computed {
1052 inputs: Vec<FieldPath>,
1053 function: ComputeFunction,
1054 },
1055 FromState {
1056 path: String,
1057 },
1058 AsEvent {
1059 fields: Vec<Box<MappingSource>>,
1060 },
1061 WholeSource,
1062 AsCapture {
1065 field_transforms: BTreeMap<String, Transformation>,
1066 },
1067 FromContext {
1070 field: String,
1071 },
1072}
1073
1074impl MappingSource {
1075 pub fn with_transform(self, transform: Transformation) -> Self {
1076 match self {
1077 MappingSource::FromSource {
1078 path,
1079 default,
1080 transform: _,
1081 } => MappingSource::FromSource {
1082 path,
1083 default,
1084 transform: Some(transform),
1085 },
1086 other => other,
1087 }
1088 }
1089}
1090
1091#[derive(Debug, Clone, Serialize, Deserialize)]
1092pub enum ComputeFunction {
1093 Sum,
1094 Concat,
1095 Format(String),
1096 Custom(String),
1097}
1098
1099#[derive(Debug, Clone, Serialize, Deserialize)]
1100pub struct Condition {
1101 pub field: FieldPath,
1102 pub operator: ConditionOp,
1103 pub value: Value,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub enum ConditionOp {
1108 Equals,
1109 NotEquals,
1110 GreaterThan,
1111 LessThan,
1112 Contains,
1113 Exists,
1114}
1115
1116#[derive(Debug, Clone, Serialize, Deserialize)]
1118pub struct FieldTypeInfo {
1119 pub field_name: String,
1120 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)]
1128 pub resolved_type: Option<ResolvedStructType>,
1129 #[serde(default = "default_emit", skip_serializing_if = "is_true")]
1130 pub emit: bool,
1131}
1132
1133#[derive(Debug, Clone, Serialize, Deserialize)]
1135pub struct ResolvedStructType {
1136 pub type_name: String,
1137 pub fields: Vec<ResolvedField>,
1138 pub is_instruction: bool,
1139 pub is_account: bool,
1140 pub is_event: bool,
1141 #[serde(default)]
1143 pub is_enum: bool,
1144 #[serde(default)]
1146 pub enum_variants: Vec<String>,
1147}
1148
1149#[derive(Debug, Clone, Serialize, Deserialize)]
1151pub struct ResolvedField {
1152 pub field_name: String,
1153 pub field_type: String,
1154 pub base_type: BaseType,
1155 pub is_optional: bool,
1156 pub is_array: bool,
1157}
1158
1159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1161pub enum BaseType {
1162 Integer, Float, String, Boolean, Object, Array, Binary, Timestamp, Pubkey, Any, }
1178
1179#[derive(Debug, Clone, Serialize, Deserialize)]
1181pub struct EntitySection {
1182 pub name: String,
1183 pub fields: Vec<FieldTypeInfo>,
1184 pub is_nested_struct: bool,
1185 pub parent_field: Option<String>, }
1187
1188impl FieldTypeInfo {
1189 pub fn new(field_name: String, rust_type_name: String) -> Self {
1190 let (base_type, is_optional, is_array, inner_type) =
1191 Self::analyze_rust_type(&rust_type_name);
1192
1193 FieldTypeInfo {
1194 field_name: field_name.clone(),
1195 rust_type_name,
1196 base_type: Self::infer_semantic_type(&field_name, base_type),
1197 is_optional,
1198 is_array,
1199 inner_type,
1200 source_path: None,
1201 resolved_type: None,
1202 emit: true,
1203 }
1204 }
1205
1206 pub fn with_source_path(mut self, source_path: String) -> Self {
1207 self.source_path = Some(source_path);
1208 self
1209 }
1210
1211 fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
1213 let type_str = rust_type.trim();
1214
1215 if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1217 let (inner_base_type, _, inner_is_array, inner_inner_type) =
1218 Self::analyze_rust_type(&inner);
1219 return (
1220 inner_base_type,
1221 true,
1222 inner_is_array,
1223 inner_inner_type.or(Some(inner)),
1224 );
1225 }
1226
1227 if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1229 let (_inner_base_type, inner_is_optional, _, inner_inner_type) =
1230 Self::analyze_rust_type(&inner);
1231 return (
1232 BaseType::Array,
1233 inner_is_optional,
1234 true,
1235 inner_inner_type.or(Some(inner)),
1236 );
1237 }
1238
1239 let base_type = match type_str {
1241 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
1242 BaseType::Integer
1243 }
1244 "f32" | "f64" => BaseType::Float,
1245 "bool" => BaseType::Boolean,
1246 "String" | "&str" | "str" => BaseType::String,
1247 "Value" | "serde_json::Value" => BaseType::Any,
1248 "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1249 _ => {
1250 if type_str.contains("Bytes") || type_str.contains("bytes") {
1252 BaseType::Binary
1253 } else if type_str.contains("Pubkey") {
1254 BaseType::Pubkey
1255 } else {
1256 BaseType::Object
1257 }
1258 }
1259 };
1260
1261 (base_type, false, false, None)
1262 }
1263
1264 fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1266 let pattern = format!("{}<", generic_name);
1267 if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1268 let start = pattern.len();
1269 let end = type_str.len() - 1;
1270 if end > start {
1271 return Some(type_str[start..end].trim().to_string());
1272 }
1273 }
1274 None
1275 }
1276
1277 fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1279 let lower_name = field_name.to_lowercase();
1280
1281 if base_type == BaseType::Integer
1283 && (lower_name.ends_with("_at")
1284 || lower_name.ends_with("_time")
1285 || lower_name.contains("timestamp")
1286 || lower_name.contains("created")
1287 || lower_name.contains("settled")
1288 || lower_name.contains("activated"))
1289 {
1290 return BaseType::Timestamp;
1291 }
1292
1293 base_type
1294 }
1295}
1296
1297pub trait FieldAccessor<S> {
1298 fn path(&self) -> String;
1299}
1300
1301impl SerializableStreamSpec {
1306 pub fn compute_content_hash(&self) -> String {
1312 use sha2::{Digest, Sha256};
1313
1314 let mut spec_for_hash = self.clone();
1316 spec_for_hash.content_hash = None;
1317
1318 let json =
1320 serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1321
1322 let mut hasher = Sha256::new();
1324 hasher.update(json.as_bytes());
1325 let result = hasher.finalize();
1326
1327 hex::encode(result)
1329 }
1330
1331 pub fn verify_content_hash(&self) -> bool {
1334 match &self.content_hash {
1335 Some(hash) => {
1336 let computed = self.compute_content_hash();
1337 hash == &computed
1338 }
1339 None => true, }
1341 }
1342
1343 pub fn with_content_hash(mut self) -> Self {
1345 self.content_hash = Some(self.compute_content_hash());
1346 self
1347 }
1348}
1349
1350#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1357pub struct PdaDefinition {
1358 pub name: String,
1360
1361 pub seeds: Vec<PdaSeedDef>,
1363
1364 #[serde(default, skip_serializing_if = "Option::is_none")]
1367 pub program_id: Option<String>,
1368}
1369
1370#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1372#[serde(tag = "type", rename_all = "camelCase")]
1373pub enum PdaSeedDef {
1374 Literal { value: String },
1376
1377 Bytes { value: Vec<u8> },
1379
1380 ArgRef {
1382 arg_name: String,
1383 #[serde(default, skip_serializing_if = "Option::is_none")]
1385 arg_type: Option<String>,
1386 },
1387
1388 AccountRef { account_name: String },
1390}
1391
1392#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1394#[serde(tag = "category", rename_all = "camelCase")]
1395pub enum AccountResolution {
1396 Signer,
1398
1399 Known { address: String },
1401
1402 PdaRef { pda_name: String },
1404
1405 PdaInline {
1407 seeds: Vec<PdaSeedDef>,
1408 #[serde(default, skip_serializing_if = "Option::is_none")]
1409 program_id: Option<String>,
1410 },
1411
1412 UserProvided,
1414}
1415
1416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1418pub struct InstructionAccountDef {
1419 pub name: String,
1421
1422 #[serde(default)]
1424 pub is_signer: bool,
1425
1426 #[serde(default)]
1428 pub is_writable: bool,
1429
1430 pub resolution: AccountResolution,
1432
1433 #[serde(default)]
1435 pub is_optional: bool,
1436
1437 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1439 pub docs: Vec<String>,
1440}
1441
1442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1444pub struct InstructionArgDef {
1445 pub name: String,
1447
1448 #[serde(rename = "type")]
1450 pub arg_type: String,
1451
1452 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1454 pub docs: Vec<String>,
1455}
1456
1457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1459pub struct InstructionDef {
1460 pub name: String,
1462
1463 pub discriminator: Vec<u8>,
1465
1466 #[serde(default = "default_discriminant_size")]
1468 pub discriminator_size: usize,
1469
1470 pub accounts: Vec<InstructionAccountDef>,
1472
1473 pub args: Vec<InstructionArgDef>,
1475
1476 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1478 pub errors: Vec<IdlErrorSnapshot>,
1479
1480 #[serde(default, skip_serializing_if = "Option::is_none")]
1482 pub program_id: Option<String>,
1483
1484 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1486 pub docs: Vec<String>,
1487}
1488
1489#[derive(Debug, Clone, Serialize, Deserialize)]
1496pub struct SerializableStackSpec {
1497 pub stack_name: String,
1499 #[serde(default)]
1501 pub program_ids: Vec<String>,
1502 #[serde(default)]
1504 pub idls: Vec<IdlSnapshot>,
1505 pub entities: Vec<SerializableStreamSpec>,
1507 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1510 pub pdas: BTreeMap<String, BTreeMap<String, PdaDefinition>>,
1511 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1513 pub instructions: Vec<InstructionDef>,
1514 #[serde(default, skip_serializing_if = "Option::is_none")]
1516 pub content_hash: Option<String>,
1517}
1518
1519impl SerializableStackSpec {
1520 pub fn compute_content_hash(&self) -> String {
1522 use sha2::{Digest, Sha256};
1523 let mut spec_for_hash = self.clone();
1524 spec_for_hash.content_hash = None;
1525 let json = serde_json::to_string(&spec_for_hash)
1526 .expect("Failed to serialize stack spec for hashing");
1527 let mut hasher = Sha256::new();
1528 hasher.update(json.as_bytes());
1529 hex::encode(hasher.finalize())
1530 }
1531
1532 pub fn with_content_hash(mut self) -> Self {
1533 self.content_hash = Some(self.compute_content_hash());
1534 self
1535 }
1536}
1537
1538#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1544#[serde(rename_all = "lowercase")]
1545pub enum SortOrder {
1546 #[default]
1547 Asc,
1548 Desc,
1549}
1550
1551#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1553pub enum CompareOp {
1554 Eq,
1555 Ne,
1556 Gt,
1557 Gte,
1558 Lt,
1559 Lte,
1560}
1561
1562#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1564pub enum PredicateValue {
1565 Literal(serde_json::Value),
1567 Dynamic(String),
1569 Field(FieldPath),
1571}
1572
1573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1575pub enum Predicate {
1576 Compare {
1578 field: FieldPath,
1579 op: CompareOp,
1580 value: PredicateValue,
1581 },
1582 And(Vec<Predicate>),
1584 Or(Vec<Predicate>),
1586 Not(Box<Predicate>),
1588 Exists { field: FieldPath },
1590}
1591
1592#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1594pub enum ViewTransform {
1595 Filter { predicate: Predicate },
1597
1598 Sort {
1600 key: FieldPath,
1601 #[serde(default)]
1602 order: SortOrder,
1603 },
1604
1605 Take { count: usize },
1607
1608 Skip { count: usize },
1610
1611 First,
1613
1614 Last,
1616
1617 MaxBy { key: FieldPath },
1619
1620 MinBy { key: FieldPath },
1622}
1623
1624#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1626pub enum ViewSource {
1627 Entity { name: String },
1629 View { id: String },
1631}
1632
1633#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1635pub enum ViewOutput {
1636 #[default]
1638 Collection,
1639 Single,
1641 Keyed { key_field: FieldPath },
1643}
1644
1645#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1647pub struct ViewDef {
1648 pub id: String,
1650
1651 pub source: ViewSource,
1653
1654 #[serde(default)]
1656 pub pipeline: Vec<ViewTransform>,
1657
1658 #[serde(default)]
1660 pub output: ViewOutput,
1661}
1662
1663impl ViewDef {
1664 pub fn list(entity_name: &str) -> Self {
1666 ViewDef {
1667 id: format!("{}/list", entity_name),
1668 source: ViewSource::Entity {
1669 name: entity_name.to_string(),
1670 },
1671 pipeline: vec![],
1672 output: ViewOutput::Collection,
1673 }
1674 }
1675
1676 pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1678 ViewDef {
1679 id: format!("{}/state", entity_name),
1680 source: ViewSource::Entity {
1681 name: entity_name.to_string(),
1682 },
1683 pipeline: vec![],
1684 output: ViewOutput::Keyed {
1685 key_field: FieldPath::new(key_field),
1686 },
1687 }
1688 }
1689
1690 pub fn is_single(&self) -> bool {
1692 matches!(self.output, ViewOutput::Single)
1693 }
1694
1695 pub fn has_single_transform(&self) -> bool {
1697 self.pipeline.iter().any(|t| {
1698 matches!(
1699 t,
1700 ViewTransform::First
1701 | ViewTransform::Last
1702 | ViewTransform::MaxBy { .. }
1703 | ViewTransform::MinBy { .. }
1704 )
1705 })
1706 }
1707}
1708
1709#[macro_export]
1710macro_rules! define_accessor {
1711 ($name:ident, $state:ty, $path:expr) => {
1712 pub struct $name;
1713
1714 impl $crate::ast::FieldAccessor<$state> for $name {
1715 fn path(&self) -> String {
1716 $path.to_string()
1717 }
1718 }
1719 };
1720}