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)]
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)]
372pub enum ComputedExpr {
373 FieldRef {
376 path: String,
377 },
378
379 UnwrapOr {
381 expr: Box<ComputedExpr>,
382 default: serde_json::Value,
383 },
384
385 Binary {
387 op: BinaryOp,
388 left: Box<ComputedExpr>,
389 right: Box<ComputedExpr>,
390 },
391
392 Cast {
394 expr: Box<ComputedExpr>,
395 to_type: String,
396 },
397
398 MethodCall {
400 expr: Box<ComputedExpr>,
401 method: String,
402 args: Vec<ComputedExpr>,
403 },
404
405 Literal {
407 value: serde_json::Value,
408 },
409
410 Paren {
412 expr: Box<ComputedExpr>,
413 },
414
415 Var {
417 name: String,
418 },
419
420 Let {
422 name: String,
423 value: Box<ComputedExpr>,
424 body: Box<ComputedExpr>,
425 },
426
427 If {
429 condition: Box<ComputedExpr>,
430 then_branch: Box<ComputedExpr>,
431 else_branch: Box<ComputedExpr>,
432 },
433
434 None,
436 Some {
437 value: Box<ComputedExpr>,
438 },
439
440 Slice {
442 expr: Box<ComputedExpr>,
443 start: usize,
444 end: usize,
445 },
446 Index {
447 expr: Box<ComputedExpr>,
448 index: usize,
449 },
450
451 U64FromLeBytes {
453 bytes: Box<ComputedExpr>,
454 },
455 U64FromBeBytes {
456 bytes: Box<ComputedExpr>,
457 },
458
459 ByteArray {
461 bytes: Vec<u8>,
462 },
463
464 Closure {
466 param: String,
467 body: Box<ComputedExpr>,
468 },
469
470 Unary {
472 op: UnaryOp,
473 expr: Box<ComputedExpr>,
474 },
475
476 JsonToBytes {
478 expr: Box<ComputedExpr>,
479 },
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize)]
484pub enum BinaryOp {
485 Add,
487 Sub,
488 Mul,
489 Div,
490 Mod,
491 Gt,
493 Lt,
494 Gte,
495 Lte,
496 Eq,
497 Ne,
498 And,
500 Or,
501 Xor,
503 BitAnd,
504 BitOr,
505 Shl,
506 Shr,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
511pub enum UnaryOp {
512 Not,
513 ReverseBits,
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct SerializableStreamSpec {
519 pub state_name: String,
520 #[serde(default)]
522 pub program_id: Option<String>,
523 #[serde(default)]
525 pub idl: Option<IdlSnapshot>,
526 pub identity: IdentitySpec,
527 pub handlers: Vec<SerializableHandlerSpec>,
528 pub sections: Vec<EntitySection>,
529 pub field_mappings: BTreeMap<String, FieldTypeInfo>,
530 pub resolver_hooks: Vec<ResolverHook>,
531 pub instruction_hooks: Vec<InstructionHook>,
532 #[serde(default)]
534 pub computed_fields: Vec<String>,
535 #[serde(default)]
537 pub computed_field_specs: Vec<ComputedFieldSpec>,
538 #[serde(default, skip_serializing_if = "Option::is_none")]
541 pub content_hash: Option<String>,
542 #[serde(default)]
544 pub views: Vec<ViewDef>,
545}
546
547#[derive(Debug, Clone)]
548pub struct TypedStreamSpec<S> {
549 pub state_name: String,
550 pub identity: IdentitySpec,
551 pub handlers: Vec<TypedHandlerSpec<S>>,
552 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>,
558}
559
560impl<S> TypedStreamSpec<S> {
561 pub fn new(
562 state_name: String,
563 identity: IdentitySpec,
564 handlers: Vec<TypedHandlerSpec<S>>,
565 ) -> Self {
566 TypedStreamSpec {
567 state_name,
568 identity,
569 handlers,
570 sections: Vec::new(),
571 field_mappings: BTreeMap::new(),
572 resolver_hooks: Vec::new(),
573 instruction_hooks: Vec::new(),
574 computed_fields: Vec::new(),
575 _phantom: PhantomData,
576 }
577 }
578
579 pub fn with_type_info(
581 state_name: String,
582 identity: IdentitySpec,
583 handlers: Vec<TypedHandlerSpec<S>>,
584 sections: Vec<EntitySection>,
585 field_mappings: BTreeMap<String, FieldTypeInfo>,
586 ) -> Self {
587 TypedStreamSpec {
588 state_name,
589 identity,
590 handlers,
591 sections,
592 field_mappings,
593 resolver_hooks: Vec::new(),
594 instruction_hooks: Vec::new(),
595 computed_fields: Vec::new(),
596 _phantom: PhantomData,
597 }
598 }
599
600 pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
602 self.field_mappings.get(path)
603 }
604
605 pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
607 self.sections
608 .iter()
609 .find(|s| s.name == section_name)
610 .map(|s| &s.fields)
611 }
612
613 pub fn get_section_names(&self) -> Vec<&String> {
615 self.sections.iter().map(|s| &s.name).collect()
616 }
617
618 pub fn to_serializable(&self) -> SerializableStreamSpec {
620 let mut spec = SerializableStreamSpec {
621 state_name: self.state_name.clone(),
622 program_id: None,
623 idl: None,
624 identity: self.identity.clone(),
625 handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
626 sections: self.sections.clone(),
627 field_mappings: self.field_mappings.clone(),
628 resolver_hooks: self.resolver_hooks.clone(),
629 instruction_hooks: self.instruction_hooks.clone(),
630 computed_fields: self.computed_fields.clone(),
631 computed_field_specs: Vec::new(),
632 content_hash: None,
633 views: Vec::new(),
634 };
635 spec.content_hash = Some(spec.compute_content_hash());
636 spec
637 }
638
639 pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
641 TypedStreamSpec {
642 state_name: spec.state_name,
643 identity: spec.identity,
644 handlers: spec
645 .handlers
646 .into_iter()
647 .map(|h| TypedHandlerSpec::from_serializable(h))
648 .collect(),
649 sections: spec.sections,
650 field_mappings: spec.field_mappings,
651 resolver_hooks: spec.resolver_hooks,
652 instruction_hooks: spec.instruction_hooks,
653 computed_fields: spec.computed_fields,
654 _phantom: PhantomData,
655 }
656 }
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize)]
660pub struct IdentitySpec {
661 pub primary_keys: Vec<String>,
662 pub lookup_indexes: Vec<LookupIndexSpec>,
663}
664
665#[derive(Debug, Clone, Serialize, Deserialize)]
666pub struct LookupIndexSpec {
667 pub field_name: String,
668 pub temporal_field: Option<String>,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize)]
677pub struct ResolverHook {
678 pub account_type: String,
680
681 pub strategy: ResolverStrategy,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize)]
686pub enum ResolverStrategy {
687 PdaReverseLookup {
689 lookup_name: String,
690 queue_discriminators: Vec<Vec<u8>>,
692 },
693
694 DirectField { field_path: FieldPath },
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct InstructionHook {
701 pub instruction_type: String,
703
704 pub actions: Vec<HookAction>,
706
707 pub lookup_by: Option<FieldPath>,
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize)]
712pub enum HookAction {
713 RegisterPdaMapping {
715 pda_field: FieldPath,
716 seed_field: FieldPath,
717 lookup_name: String,
718 },
719
720 SetField {
722 target_field: String,
723 source: MappingSource,
724 condition: Option<ConditionExpr>,
725 },
726
727 IncrementField {
729 target_field: String,
730 increment_by: i64,
731 condition: Option<ConditionExpr>,
732 },
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize)]
737pub struct ConditionExpr {
738 pub expression: String,
740
741 pub parsed: Option<ParsedCondition>,
743}
744
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub enum ParsedCondition {
747 Comparison {
749 field: FieldPath,
750 op: ComparisonOp,
751 value: serde_json::Value,
752 },
753
754 Logical {
756 op: LogicalOp,
757 conditions: Vec<ParsedCondition>,
758 },
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
762pub enum ComparisonOp {
763 Equal,
764 NotEqual,
765 GreaterThan,
766 GreaterThanOrEqual,
767 LessThan,
768 LessThanOrEqual,
769}
770
771#[derive(Debug, Clone, Serialize, Deserialize)]
772pub enum LogicalOp {
773 And,
774 Or,
775}
776
777#[derive(Debug, Clone, Serialize, Deserialize)]
779pub struct SerializableHandlerSpec {
780 pub source: SourceSpec,
781 pub key_resolution: KeyResolutionStrategy,
782 pub mappings: Vec<SerializableFieldMapping>,
783 pub conditions: Vec<Condition>,
784 pub emit: bool,
785}
786
787#[derive(Debug, Clone)]
788pub struct TypedHandlerSpec<S> {
789 pub source: SourceSpec,
790 pub key_resolution: KeyResolutionStrategy,
791 pub mappings: Vec<TypedFieldMapping<S>>,
792 pub conditions: Vec<Condition>,
793 pub emit: bool,
794 _phantom: PhantomData<S>,
795}
796
797impl<S> TypedHandlerSpec<S> {
798 pub fn new(
799 source: SourceSpec,
800 key_resolution: KeyResolutionStrategy,
801 mappings: Vec<TypedFieldMapping<S>>,
802 emit: bool,
803 ) -> Self {
804 TypedHandlerSpec {
805 source,
806 key_resolution,
807 mappings,
808 conditions: vec![],
809 emit,
810 _phantom: PhantomData,
811 }
812 }
813
814 pub fn to_serializable(&self) -> SerializableHandlerSpec {
816 SerializableHandlerSpec {
817 source: self.source.clone(),
818 key_resolution: self.key_resolution.clone(),
819 mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
820 conditions: self.conditions.clone(),
821 emit: self.emit,
822 }
823 }
824
825 pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
827 TypedHandlerSpec {
828 source: spec.source,
829 key_resolution: spec.key_resolution,
830 mappings: spec
831 .mappings
832 .into_iter()
833 .map(|m| TypedFieldMapping::from_serializable(m))
834 .collect(),
835 conditions: spec.conditions,
836 emit: spec.emit,
837 _phantom: PhantomData,
838 }
839 }
840}
841
842#[derive(Debug, Clone, Serialize, Deserialize)]
843pub enum KeyResolutionStrategy {
844 Embedded {
845 primary_field: FieldPath,
846 },
847 Lookup {
848 primary_field: FieldPath,
849 },
850 Computed {
851 primary_field: FieldPath,
852 compute_partition: ComputeFunction,
853 },
854 TemporalLookup {
855 lookup_field: FieldPath,
856 timestamp_field: FieldPath,
857 index_name: String,
858 },
859}
860
861#[derive(Debug, Clone, Serialize, Deserialize)]
862pub enum SourceSpec {
863 Source {
864 program_id: Option<String>,
865 discriminator: Option<Vec<u8>>,
866 type_name: String,
867 #[serde(default, skip_serializing_if = "Option::is_none")]
868 serialization: Option<IdlSerializationSnapshot>,
869 },
870}
871
872#[derive(Debug, Clone, Serialize, Deserialize)]
874pub struct SerializableFieldMapping {
875 pub target_path: String,
876 pub source: MappingSource,
877 pub transform: Option<Transformation>,
878 pub population: PopulationStrategy,
879 #[serde(default, skip_serializing_if = "Option::is_none")]
880 pub condition: Option<ConditionExpr>,
881 #[serde(default, skip_serializing_if = "Option::is_none")]
882 pub when: Option<String>,
883 #[serde(default = "default_emit", skip_serializing_if = "is_true")]
884 pub emit: bool,
885}
886
887fn default_emit() -> bool {
888 true
889}
890
891fn is_true(value: &bool) -> bool {
892 *value
893}
894
895#[derive(Debug, Clone)]
896pub struct TypedFieldMapping<S> {
897 pub target_path: String,
898 pub source: MappingSource,
899 pub transform: Option<Transformation>,
900 pub population: PopulationStrategy,
901 pub condition: Option<ConditionExpr>,
902 pub when: Option<String>,
903 pub emit: bool,
904 _phantom: PhantomData<S>,
905}
906
907impl<S> TypedFieldMapping<S> {
908 pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
909 TypedFieldMapping {
910 target_path,
911 source,
912 transform: None,
913 population,
914 condition: None,
915 when: None,
916 emit: true,
917 _phantom: PhantomData,
918 }
919 }
920
921 pub fn with_transform(mut self, transform: Transformation) -> Self {
922 self.transform = Some(transform);
923 self
924 }
925
926 pub fn with_condition(mut self, condition: ConditionExpr) -> Self {
927 self.condition = Some(condition);
928 self
929 }
930
931 pub fn with_when(mut self, when: String) -> Self {
932 self.when = Some(when);
933 self
934 }
935
936 pub fn with_emit(mut self, emit: bool) -> Self {
937 self.emit = emit;
938 self
939 }
940
941 pub fn to_serializable(&self) -> SerializableFieldMapping {
943 SerializableFieldMapping {
944 target_path: self.target_path.clone(),
945 source: self.source.clone(),
946 transform: self.transform.clone(),
947 population: self.population.clone(),
948 condition: self.condition.clone(),
949 when: self.when.clone(),
950 emit: self.emit,
951 }
952 }
953
954 pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
956 TypedFieldMapping {
957 target_path: mapping.target_path,
958 source: mapping.source,
959 transform: mapping.transform,
960 population: mapping.population,
961 condition: mapping.condition,
962 when: mapping.when,
963 emit: mapping.emit,
964 _phantom: PhantomData,
965 }
966 }
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize)]
970pub enum MappingSource {
971 FromSource {
972 path: FieldPath,
973 default: Option<Value>,
974 transform: Option<Transformation>,
975 },
976 Constant(Value),
977 Computed {
978 inputs: Vec<FieldPath>,
979 function: ComputeFunction,
980 },
981 FromState {
982 path: String,
983 },
984 AsEvent {
985 fields: Vec<Box<MappingSource>>,
986 },
987 WholeSource,
988 AsCapture {
991 field_transforms: BTreeMap<String, Transformation>,
992 },
993 FromContext {
996 field: String,
997 },
998}
999
1000impl MappingSource {
1001 pub fn with_transform(self, transform: Transformation) -> Self {
1002 match self {
1003 MappingSource::FromSource {
1004 path,
1005 default,
1006 transform: _,
1007 } => MappingSource::FromSource {
1008 path,
1009 default,
1010 transform: Some(transform),
1011 },
1012 other => other,
1013 }
1014 }
1015}
1016
1017#[derive(Debug, Clone, Serialize, Deserialize)]
1018pub enum ComputeFunction {
1019 Sum,
1020 Concat,
1021 Format(String),
1022 Custom(String),
1023}
1024
1025#[derive(Debug, Clone, Serialize, Deserialize)]
1026pub struct Condition {
1027 pub field: FieldPath,
1028 pub operator: ConditionOp,
1029 pub value: Value,
1030}
1031
1032#[derive(Debug, Clone, Serialize, Deserialize)]
1033pub enum ConditionOp {
1034 Equals,
1035 NotEquals,
1036 GreaterThan,
1037 LessThan,
1038 Contains,
1039 Exists,
1040}
1041
1042#[derive(Debug, Clone, Serialize, Deserialize)]
1044pub struct FieldTypeInfo {
1045 pub field_name: String,
1046 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)]
1054 pub resolved_type: Option<ResolvedStructType>,
1055 #[serde(default = "default_emit", skip_serializing_if = "is_true")]
1056 pub emit: bool,
1057}
1058
1059#[derive(Debug, Clone, Serialize, Deserialize)]
1061pub struct ResolvedStructType {
1062 pub type_name: String,
1063 pub fields: Vec<ResolvedField>,
1064 pub is_instruction: bool,
1065 pub is_account: bool,
1066 pub is_event: bool,
1067 #[serde(default)]
1069 pub is_enum: bool,
1070 #[serde(default)]
1072 pub enum_variants: Vec<String>,
1073}
1074
1075#[derive(Debug, Clone, Serialize, Deserialize)]
1077pub struct ResolvedField {
1078 pub field_name: String,
1079 pub field_type: String,
1080 pub base_type: BaseType,
1081 pub is_optional: bool,
1082 pub is_array: bool,
1083}
1084
1085#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1087pub enum BaseType {
1088 Integer, Float, String, Boolean, Object, Array, Binary, Timestamp, Pubkey, Any, }
1104
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub struct EntitySection {
1108 pub name: String,
1109 pub fields: Vec<FieldTypeInfo>,
1110 pub is_nested_struct: bool,
1111 pub parent_field: Option<String>, }
1113
1114impl FieldTypeInfo {
1115 pub fn new(field_name: String, rust_type_name: String) -> Self {
1116 let (base_type, is_optional, is_array, inner_type) =
1117 Self::analyze_rust_type(&rust_type_name);
1118
1119 FieldTypeInfo {
1120 field_name: field_name.clone(),
1121 rust_type_name,
1122 base_type: Self::infer_semantic_type(&field_name, base_type),
1123 is_optional,
1124 is_array,
1125 inner_type,
1126 source_path: None,
1127 resolved_type: None,
1128 emit: true,
1129 }
1130 }
1131
1132 pub fn with_source_path(mut self, source_path: String) -> Self {
1133 self.source_path = Some(source_path);
1134 self
1135 }
1136
1137 fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
1139 let type_str = rust_type.trim();
1140
1141 if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1143 let (inner_base_type, _, inner_is_array, inner_inner_type) =
1144 Self::analyze_rust_type(&inner);
1145 return (
1146 inner_base_type,
1147 true,
1148 inner_is_array,
1149 inner_inner_type.or(Some(inner)),
1150 );
1151 }
1152
1153 if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1155 let (_inner_base_type, inner_is_optional, _, inner_inner_type) =
1156 Self::analyze_rust_type(&inner);
1157 return (
1158 BaseType::Array,
1159 inner_is_optional,
1160 true,
1161 inner_inner_type.or(Some(inner)),
1162 );
1163 }
1164
1165 let base_type = match type_str {
1167 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
1168 BaseType::Integer
1169 }
1170 "f32" | "f64" => BaseType::Float,
1171 "bool" => BaseType::Boolean,
1172 "String" | "&str" | "str" => BaseType::String,
1173 "Value" | "serde_json::Value" => BaseType::Any,
1174 "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1175 _ => {
1176 if type_str.contains("Bytes") || type_str.contains("bytes") {
1178 BaseType::Binary
1179 } else if type_str.contains("Pubkey") {
1180 BaseType::Pubkey
1181 } else {
1182 BaseType::Object
1183 }
1184 }
1185 };
1186
1187 (base_type, false, false, None)
1188 }
1189
1190 fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1192 let pattern = format!("{}<", generic_name);
1193 if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1194 let start = pattern.len();
1195 let end = type_str.len() - 1;
1196 if end > start {
1197 return Some(type_str[start..end].trim().to_string());
1198 }
1199 }
1200 None
1201 }
1202
1203 fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1205 let lower_name = field_name.to_lowercase();
1206
1207 if base_type == BaseType::Integer
1209 && (lower_name.ends_with("_at")
1210 || lower_name.ends_with("_time")
1211 || lower_name.contains("timestamp")
1212 || lower_name.contains("created")
1213 || lower_name.contains("settled")
1214 || lower_name.contains("activated"))
1215 {
1216 return BaseType::Timestamp;
1217 }
1218
1219 base_type
1220 }
1221}
1222
1223pub trait FieldAccessor<S> {
1224 fn path(&self) -> String;
1225}
1226
1227impl SerializableStreamSpec {
1232 pub fn compute_content_hash(&self) -> String {
1238 use sha2::{Digest, Sha256};
1239
1240 let mut spec_for_hash = self.clone();
1242 spec_for_hash.content_hash = None;
1243
1244 let json =
1246 serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1247
1248 let mut hasher = Sha256::new();
1250 hasher.update(json.as_bytes());
1251 let result = hasher.finalize();
1252
1253 hex::encode(result)
1255 }
1256
1257 pub fn verify_content_hash(&self) -> bool {
1260 match &self.content_hash {
1261 Some(hash) => {
1262 let computed = self.compute_content_hash();
1263 hash == &computed
1264 }
1265 None => true, }
1267 }
1268
1269 pub fn with_content_hash(mut self) -> Self {
1271 self.content_hash = Some(self.compute_content_hash());
1272 self
1273 }
1274}
1275
1276#[derive(Debug, Clone, Serialize, Deserialize)]
1283pub struct SerializableStackSpec {
1284 pub stack_name: String,
1286 #[serde(default)]
1288 pub program_ids: Vec<String>,
1289 #[serde(default)]
1291 pub idls: Vec<IdlSnapshot>,
1292 pub entities: Vec<SerializableStreamSpec>,
1294 #[serde(default, skip_serializing_if = "Option::is_none")]
1296 pub content_hash: Option<String>,
1297}
1298
1299impl SerializableStackSpec {
1300 pub fn compute_content_hash(&self) -> String {
1302 use sha2::{Digest, Sha256};
1303 let mut spec_for_hash = self.clone();
1304 spec_for_hash.content_hash = None;
1305 let json = serde_json::to_string(&spec_for_hash)
1306 .expect("Failed to serialize stack spec for hashing");
1307 let mut hasher = Sha256::new();
1308 hasher.update(json.as_bytes());
1309 hex::encode(hasher.finalize())
1310 }
1311
1312 pub fn with_content_hash(mut self) -> Self {
1313 self.content_hash = Some(self.compute_content_hash());
1314 self
1315 }
1316}
1317
1318#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1324#[serde(rename_all = "lowercase")]
1325pub enum SortOrder {
1326 #[default]
1327 Asc,
1328 Desc,
1329}
1330
1331#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1333pub enum CompareOp {
1334 Eq,
1335 Ne,
1336 Gt,
1337 Gte,
1338 Lt,
1339 Lte,
1340}
1341
1342#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1344pub enum PredicateValue {
1345 Literal(serde_json::Value),
1347 Dynamic(String),
1349 Field(FieldPath),
1351}
1352
1353#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1355pub enum Predicate {
1356 Compare {
1358 field: FieldPath,
1359 op: CompareOp,
1360 value: PredicateValue,
1361 },
1362 And(Vec<Predicate>),
1364 Or(Vec<Predicate>),
1366 Not(Box<Predicate>),
1368 Exists { field: FieldPath },
1370}
1371
1372#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1374pub enum ViewTransform {
1375 Filter { predicate: Predicate },
1377
1378 Sort {
1380 key: FieldPath,
1381 #[serde(default)]
1382 order: SortOrder,
1383 },
1384
1385 Take { count: usize },
1387
1388 Skip { count: usize },
1390
1391 First,
1393
1394 Last,
1396
1397 MaxBy { key: FieldPath },
1399
1400 MinBy { key: FieldPath },
1402}
1403
1404#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1406pub enum ViewSource {
1407 Entity { name: String },
1409 View { id: String },
1411}
1412
1413#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1415pub enum ViewOutput {
1416 #[default]
1418 Collection,
1419 Single,
1421 Keyed { key_field: FieldPath },
1423}
1424
1425#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1427pub struct ViewDef {
1428 pub id: String,
1430
1431 pub source: ViewSource,
1433
1434 #[serde(default)]
1436 pub pipeline: Vec<ViewTransform>,
1437
1438 #[serde(default)]
1440 pub output: ViewOutput,
1441}
1442
1443impl ViewDef {
1444 pub fn list(entity_name: &str) -> Self {
1446 ViewDef {
1447 id: format!("{}/list", entity_name),
1448 source: ViewSource::Entity {
1449 name: entity_name.to_string(),
1450 },
1451 pipeline: vec![],
1452 output: ViewOutput::Collection,
1453 }
1454 }
1455
1456 pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1458 ViewDef {
1459 id: format!("{}/state", entity_name),
1460 source: ViewSource::Entity {
1461 name: entity_name.to_string(),
1462 },
1463 pipeline: vec![],
1464 output: ViewOutput::Keyed {
1465 key_field: FieldPath::new(key_field),
1466 },
1467 }
1468 }
1469
1470 pub fn is_single(&self) -> bool {
1472 matches!(self.output, ViewOutput::Single)
1473 }
1474
1475 pub fn has_single_transform(&self) -> bool {
1477 self.pipeline.iter().any(|t| {
1478 matches!(
1479 t,
1480 ViewTransform::First
1481 | ViewTransform::Last
1482 | ViewTransform::MaxBy { .. }
1483 | ViewTransform::MinBy { .. }
1484 )
1485 })
1486 }
1487}
1488
1489#[macro_export]
1490macro_rules! define_accessor {
1491 ($name:ident, $state:ty, $path:expr) => {
1492 pub struct $name;
1493
1494 impl $crate::ast::FieldAccessor<$state> for $name {
1495 fn path(&self) -> String {
1496 $path.to_string()
1497 }
1498 }
1499 };
1500}