1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4use std::marker::PhantomData;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct IdlSnapshot {
14 pub name: String,
16 pub version: String,
18 pub accounts: Vec<IdlAccountSnapshot>,
20 pub instructions: Vec<IdlInstructionSnapshot>,
22 #[serde(default)]
24 pub types: Vec<IdlTypeDefSnapshot>,
25 #[serde(default)]
27 pub events: Vec<IdlEventSnapshot>,
28 #[serde(default)]
30 pub errors: Vec<IdlErrorSnapshot>,
31 #[serde(default = "default_discriminant_size")]
34 pub discriminant_size: usize,
35}
36
37fn default_discriminant_size() -> usize {
38 8
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct IdlAccountSnapshot {
44 pub name: String,
46 pub discriminator: Vec<u8>,
48 #[serde(default)]
50 pub docs: Vec<String>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct IdlInstructionSnapshot {
56 pub name: String,
58 pub discriminator: Vec<u8>,
60 #[serde(default)]
62 pub docs: Vec<String>,
63 pub accounts: Vec<IdlInstructionAccountSnapshot>,
65 pub args: Vec<IdlFieldSnapshot>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct IdlInstructionAccountSnapshot {
72 pub name: String,
74 #[serde(default)]
76 pub writable: bool,
77 #[serde(default)]
79 pub signer: bool,
80 #[serde(default)]
82 pub optional: bool,
83 #[serde(default)]
85 pub address: Option<String>,
86 #[serde(default)]
88 pub docs: Vec<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct IdlFieldSnapshot {
94 pub name: String,
96 #[serde(rename = "type")]
98 pub type_: IdlTypeSnapshot,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(untagged)]
104pub enum IdlTypeSnapshot {
105 Simple(String),
107 Array(IdlArrayTypeSnapshot),
109 Option(IdlOptionTypeSnapshot),
111 Vec(IdlVecTypeSnapshot),
113 Defined(IdlDefinedTypeSnapshot),
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct IdlArrayTypeSnapshot {
120 pub array: Vec<IdlArrayElementSnapshot>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(untagged)]
127pub enum IdlArrayElementSnapshot {
128 Type(IdlTypeSnapshot),
130 TypeName(String),
132 Size(u32),
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct IdlOptionTypeSnapshot {
139 pub option: Box<IdlTypeSnapshot>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct IdlVecTypeSnapshot {
145 pub vec: Box<IdlTypeSnapshot>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct IdlDefinedTypeSnapshot {
151 pub defined: IdlDefinedInnerSnapshot,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(untagged)]
157pub enum IdlDefinedInnerSnapshot {
158 Named { name: String },
160 Simple(String),
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct IdlTypeDefSnapshot {
167 pub name: String,
169 #[serde(default)]
171 pub docs: Vec<String>,
172 #[serde(rename = "type")]
174 pub type_def: IdlTypeDefKindSnapshot,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(untagged)]
180pub enum IdlTypeDefKindSnapshot {
181 Struct {
183 kind: String,
184 fields: Vec<IdlFieldSnapshot>,
185 },
186 TupleStruct {
188 kind: String,
189 fields: Vec<IdlTypeSnapshot>,
190 },
191 Enum {
193 kind: String,
194 variants: Vec<IdlEnumVariantSnapshot>,
195 },
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct IdlEnumVariantSnapshot {
201 pub name: String,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct IdlEventSnapshot {
207 pub name: String,
209 pub discriminator: Vec<u8>,
211 #[serde(default)]
213 pub docs: Vec<String>,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct IdlErrorSnapshot {
219 pub code: u32,
221 pub name: String,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub msg: Option<String>,
226}
227
228impl IdlTypeSnapshot {
229 pub fn to_rust_type_string(&self) -> String {
231 match self {
232 IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
233 IdlTypeSnapshot::Array(arr) => {
234 if arr.array.len() == 2 {
235 match (&arr.array[0], &arr.array[1]) {
236 (
237 IdlArrayElementSnapshot::TypeName(t),
238 IdlArrayElementSnapshot::Size(size),
239 ) => {
240 format!("[{}; {}]", Self::map_simple_type(t), size)
241 }
242 (
243 IdlArrayElementSnapshot::Type(nested),
244 IdlArrayElementSnapshot::Size(size),
245 ) => {
246 format!("[{}; {}]", nested.to_rust_type_string(), size)
247 }
248 _ => "Vec<u8>".to_string(),
249 }
250 } else {
251 "Vec<u8>".to_string()
252 }
253 }
254 IdlTypeSnapshot::Option(opt) => {
255 format!("Option<{}>", opt.option.to_rust_type_string())
256 }
257 IdlTypeSnapshot::Vec(vec) => {
258 format!("Vec<{}>", vec.vec.to_rust_type_string())
259 }
260 IdlTypeSnapshot::Defined(def) => match &def.defined {
261 IdlDefinedInnerSnapshot::Named { name } => name.clone(),
262 IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
263 },
264 }
265 }
266
267 fn map_simple_type(idl_type: &str) -> String {
268 match idl_type {
269 "u8" => "u8".to_string(),
270 "u16" => "u16".to_string(),
271 "u32" => "u32".to_string(),
272 "u64" => "u64".to_string(),
273 "u128" => "u128".to_string(),
274 "i8" => "i8".to_string(),
275 "i16" => "i16".to_string(),
276 "i32" => "i32".to_string(),
277 "i64" => "i64".to_string(),
278 "i128" => "i128".to_string(),
279 "bool" => "bool".to_string(),
280 "string" => "String".to_string(),
281 "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
282 "bytes" => "Vec<u8>".to_string(),
283 _ => idl_type.to_string(),
284 }
285 }
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
289pub struct FieldPath {
290 pub segments: Vec<String>,
291 pub offsets: Option<Vec<usize>>,
292}
293
294impl FieldPath {
295 pub fn new(segments: &[&str]) -> Self {
296 FieldPath {
297 segments: segments.iter().map(|s| s.to_string()).collect(),
298 offsets: None,
299 }
300 }
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
304pub enum Transformation {
305 HexEncode,
306 HexDecode,
307 Base58Encode,
308 Base58Decode,
309 ToString,
310 ToNumber,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub enum PopulationStrategy {
315 SetOnce,
316 LastWrite,
317 Append,
318 Merge,
319 Max,
320 Sum,
322 Count,
324 Min,
326 UniqueCount,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct ComputedFieldSpec {
338 pub target_path: String,
340 pub expression: ComputedExpr,
342 pub result_type: String,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
356pub enum ComputedExpr {
357 FieldRef {
360 path: String,
361 },
362
363 UnwrapOr {
365 expr: Box<ComputedExpr>,
366 default: serde_json::Value,
367 },
368
369 Binary {
371 op: BinaryOp,
372 left: Box<ComputedExpr>,
373 right: Box<ComputedExpr>,
374 },
375
376 Cast {
378 expr: Box<ComputedExpr>,
379 to_type: String,
380 },
381
382 MethodCall {
384 expr: Box<ComputedExpr>,
385 method: String,
386 args: Vec<ComputedExpr>,
387 },
388
389 Literal {
391 value: serde_json::Value,
392 },
393
394 Paren {
396 expr: Box<ComputedExpr>,
397 },
398
399 Var {
401 name: String,
402 },
403
404 Let {
406 name: String,
407 value: Box<ComputedExpr>,
408 body: Box<ComputedExpr>,
409 },
410
411 If {
413 condition: Box<ComputedExpr>,
414 then_branch: Box<ComputedExpr>,
415 else_branch: Box<ComputedExpr>,
416 },
417
418 None,
420 Some {
421 value: Box<ComputedExpr>,
422 },
423
424 Slice {
426 expr: Box<ComputedExpr>,
427 start: usize,
428 end: usize,
429 },
430 Index {
431 expr: Box<ComputedExpr>,
432 index: usize,
433 },
434
435 U64FromLeBytes {
437 bytes: Box<ComputedExpr>,
438 },
439 U64FromBeBytes {
440 bytes: Box<ComputedExpr>,
441 },
442
443 ByteArray {
445 bytes: Vec<u8>,
446 },
447
448 Closure {
450 param: String,
451 body: Box<ComputedExpr>,
452 },
453
454 Unary {
456 op: UnaryOp,
457 expr: Box<ComputedExpr>,
458 },
459
460 JsonToBytes {
462 expr: Box<ComputedExpr>,
463 },
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub enum BinaryOp {
469 Add,
471 Sub,
472 Mul,
473 Div,
474 Mod,
475 Gt,
477 Lt,
478 Gte,
479 Lte,
480 Eq,
481 Ne,
482 And,
484 Or,
485 Xor,
487 BitAnd,
488 BitOr,
489 Shl,
490 Shr,
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize)]
495pub enum UnaryOp {
496 Not,
497 ReverseBits,
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct SerializableStreamSpec {
503 pub state_name: String,
504 #[serde(default)]
506 pub program_id: Option<String>,
507 #[serde(default)]
509 pub idl: Option<IdlSnapshot>,
510 pub identity: IdentitySpec,
511 pub handlers: Vec<SerializableHandlerSpec>,
512 pub sections: Vec<EntitySection>,
513 pub field_mappings: BTreeMap<String, FieldTypeInfo>,
514 pub resolver_hooks: Vec<ResolverHook>,
515 pub instruction_hooks: Vec<InstructionHook>,
516 #[serde(default)]
518 pub computed_fields: Vec<String>,
519 #[serde(default)]
521 pub computed_field_specs: Vec<ComputedFieldSpec>,
522 #[serde(default, skip_serializing_if = "Option::is_none")]
525 pub content_hash: Option<String>,
526 #[serde(default)]
528 pub views: Vec<ViewDef>,
529}
530
531#[derive(Debug, Clone)]
532pub struct TypedStreamSpec<S> {
533 pub state_name: String,
534 pub identity: IdentitySpec,
535 pub handlers: Vec<TypedHandlerSpec<S>>,
536 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>,
542}
543
544impl<S> TypedStreamSpec<S> {
545 pub fn new(
546 state_name: String,
547 identity: IdentitySpec,
548 handlers: Vec<TypedHandlerSpec<S>>,
549 ) -> Self {
550 TypedStreamSpec {
551 state_name,
552 identity,
553 handlers,
554 sections: Vec::new(),
555 field_mappings: BTreeMap::new(),
556 resolver_hooks: Vec::new(),
557 instruction_hooks: Vec::new(),
558 computed_fields: Vec::new(),
559 _phantom: PhantomData,
560 }
561 }
562
563 pub fn with_type_info(
565 state_name: String,
566 identity: IdentitySpec,
567 handlers: Vec<TypedHandlerSpec<S>>,
568 sections: Vec<EntitySection>,
569 field_mappings: BTreeMap<String, FieldTypeInfo>,
570 ) -> Self {
571 TypedStreamSpec {
572 state_name,
573 identity,
574 handlers,
575 sections,
576 field_mappings,
577 resolver_hooks: Vec::new(),
578 instruction_hooks: Vec::new(),
579 computed_fields: Vec::new(),
580 _phantom: PhantomData,
581 }
582 }
583
584 pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
586 self.field_mappings.get(path)
587 }
588
589 pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
591 self.sections
592 .iter()
593 .find(|s| s.name == section_name)
594 .map(|s| &s.fields)
595 }
596
597 pub fn get_section_names(&self) -> Vec<&String> {
599 self.sections.iter().map(|s| &s.name).collect()
600 }
601
602 pub fn to_serializable(&self) -> SerializableStreamSpec {
604 let mut spec = SerializableStreamSpec {
605 state_name: self.state_name.clone(),
606 program_id: None,
607 idl: None,
608 identity: self.identity.clone(),
609 handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
610 sections: self.sections.clone(),
611 field_mappings: self.field_mappings.clone(),
612 resolver_hooks: self.resolver_hooks.clone(),
613 instruction_hooks: self.instruction_hooks.clone(),
614 computed_fields: self.computed_fields.clone(),
615 computed_field_specs: Vec::new(),
616 content_hash: None,
617 views: Vec::new(),
618 };
619 spec.content_hash = Some(spec.compute_content_hash());
620 spec
621 }
622
623 pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
625 TypedStreamSpec {
626 state_name: spec.state_name,
627 identity: spec.identity,
628 handlers: spec
629 .handlers
630 .into_iter()
631 .map(|h| TypedHandlerSpec::from_serializable(h))
632 .collect(),
633 sections: spec.sections,
634 field_mappings: spec.field_mappings,
635 resolver_hooks: spec.resolver_hooks,
636 instruction_hooks: spec.instruction_hooks,
637 computed_fields: spec.computed_fields,
638 _phantom: PhantomData,
639 }
640 }
641}
642
643#[derive(Debug, Clone, Serialize, Deserialize)]
644pub struct IdentitySpec {
645 pub primary_keys: Vec<String>,
646 pub lookup_indexes: Vec<LookupIndexSpec>,
647}
648
649#[derive(Debug, Clone, Serialize, Deserialize)]
650pub struct LookupIndexSpec {
651 pub field_name: String,
652 pub temporal_field: Option<String>,
653}
654
655#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct ResolverHook {
662 pub account_type: String,
664
665 pub strategy: ResolverStrategy,
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize)]
670pub enum ResolverStrategy {
671 PdaReverseLookup {
673 lookup_name: String,
674 queue_discriminators: Vec<Vec<u8>>,
676 },
677
678 DirectField { field_path: FieldPath },
680}
681
682#[derive(Debug, Clone, Serialize, Deserialize)]
684pub struct InstructionHook {
685 pub instruction_type: String,
687
688 pub actions: Vec<HookAction>,
690
691 pub lookup_by: Option<FieldPath>,
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize)]
696pub enum HookAction {
697 RegisterPdaMapping {
699 pda_field: FieldPath,
700 seed_field: FieldPath,
701 lookup_name: String,
702 },
703
704 SetField {
706 target_field: String,
707 source: MappingSource,
708 condition: Option<ConditionExpr>,
709 },
710
711 IncrementField {
713 target_field: String,
714 increment_by: i64,
715 condition: Option<ConditionExpr>,
716 },
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize)]
721pub struct ConditionExpr {
722 pub expression: String,
724
725 pub parsed: Option<ParsedCondition>,
727}
728
729#[derive(Debug, Clone, Serialize, Deserialize)]
730pub enum ParsedCondition {
731 Comparison {
733 field: FieldPath,
734 op: ComparisonOp,
735 value: serde_json::Value,
736 },
737
738 Logical {
740 op: LogicalOp,
741 conditions: Vec<ParsedCondition>,
742 },
743}
744
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub enum ComparisonOp {
747 Equal,
748 NotEqual,
749 GreaterThan,
750 GreaterThanOrEqual,
751 LessThan,
752 LessThanOrEqual,
753}
754
755#[derive(Debug, Clone, Serialize, Deserialize)]
756pub enum LogicalOp {
757 And,
758 Or,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct SerializableHandlerSpec {
764 pub source: SourceSpec,
765 pub key_resolution: KeyResolutionStrategy,
766 pub mappings: Vec<SerializableFieldMapping>,
767 pub conditions: Vec<Condition>,
768 pub emit: bool,
769}
770
771#[derive(Debug, Clone)]
772pub struct TypedHandlerSpec<S> {
773 pub source: SourceSpec,
774 pub key_resolution: KeyResolutionStrategy,
775 pub mappings: Vec<TypedFieldMapping<S>>,
776 pub conditions: Vec<Condition>,
777 pub emit: bool,
778 _phantom: PhantomData<S>,
779}
780
781impl<S> TypedHandlerSpec<S> {
782 pub fn new(
783 source: SourceSpec,
784 key_resolution: KeyResolutionStrategy,
785 mappings: Vec<TypedFieldMapping<S>>,
786 emit: bool,
787 ) -> Self {
788 TypedHandlerSpec {
789 source,
790 key_resolution,
791 mappings,
792 conditions: vec![],
793 emit,
794 _phantom: PhantomData,
795 }
796 }
797
798 pub fn to_serializable(&self) -> SerializableHandlerSpec {
800 SerializableHandlerSpec {
801 source: self.source.clone(),
802 key_resolution: self.key_resolution.clone(),
803 mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
804 conditions: self.conditions.clone(),
805 emit: self.emit,
806 }
807 }
808
809 pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
811 TypedHandlerSpec {
812 source: spec.source,
813 key_resolution: spec.key_resolution,
814 mappings: spec
815 .mappings
816 .into_iter()
817 .map(|m| TypedFieldMapping::from_serializable(m))
818 .collect(),
819 conditions: spec.conditions,
820 emit: spec.emit,
821 _phantom: PhantomData,
822 }
823 }
824}
825
826#[derive(Debug, Clone, Serialize, Deserialize)]
827pub enum KeyResolutionStrategy {
828 Embedded {
829 primary_field: FieldPath,
830 },
831 Lookup {
832 primary_field: FieldPath,
833 },
834 Computed {
835 primary_field: FieldPath,
836 compute_partition: ComputeFunction,
837 },
838 TemporalLookup {
839 lookup_field: FieldPath,
840 timestamp_field: FieldPath,
841 index_name: String,
842 },
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize)]
846pub enum SourceSpec {
847 Source {
848 program_id: Option<String>,
849 discriminator: Option<Vec<u8>>,
850 type_name: String,
851 },
852}
853
854#[derive(Debug, Clone, Serialize, Deserialize)]
856pub struct SerializableFieldMapping {
857 pub target_path: String,
858 pub source: MappingSource,
859 pub transform: Option<Transformation>,
860 pub population: PopulationStrategy,
861}
862
863#[derive(Debug, Clone)]
864pub struct TypedFieldMapping<S> {
865 pub target_path: String,
866 pub source: MappingSource,
867 pub transform: Option<Transformation>,
868 pub population: PopulationStrategy,
869 _phantom: PhantomData<S>,
870}
871
872impl<S> TypedFieldMapping<S> {
873 pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
874 TypedFieldMapping {
875 target_path,
876 source,
877 transform: None,
878 population,
879 _phantom: PhantomData,
880 }
881 }
882
883 pub fn with_transform(mut self, transform: Transformation) -> Self {
884 self.transform = Some(transform);
885 self
886 }
887
888 pub fn to_serializable(&self) -> SerializableFieldMapping {
890 SerializableFieldMapping {
891 target_path: self.target_path.clone(),
892 source: self.source.clone(),
893 transform: self.transform.clone(),
894 population: self.population.clone(),
895 }
896 }
897
898 pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
900 TypedFieldMapping {
901 target_path: mapping.target_path,
902 source: mapping.source,
903 transform: mapping.transform,
904 population: mapping.population,
905 _phantom: PhantomData,
906 }
907 }
908}
909
910#[derive(Debug, Clone, Serialize, Deserialize)]
911pub enum MappingSource {
912 FromSource {
913 path: FieldPath,
914 default: Option<Value>,
915 transform: Option<Transformation>,
916 },
917 Constant(Value),
918 Computed {
919 inputs: Vec<FieldPath>,
920 function: ComputeFunction,
921 },
922 FromState {
923 path: String,
924 },
925 AsEvent {
926 fields: Vec<Box<MappingSource>>,
927 },
928 WholeSource,
929 AsCapture {
932 field_transforms: BTreeMap<String, Transformation>,
933 },
934 FromContext {
937 field: String,
938 },
939}
940
941impl MappingSource {
942 pub fn with_transform(self, transform: Transformation) -> Self {
943 match self {
944 MappingSource::FromSource {
945 path,
946 default,
947 transform: _,
948 } => MappingSource::FromSource {
949 path,
950 default,
951 transform: Some(transform),
952 },
953 other => other,
954 }
955 }
956}
957
958#[derive(Debug, Clone, Serialize, Deserialize)]
959pub enum ComputeFunction {
960 Sum,
961 Concat,
962 Format(String),
963 Custom(String),
964}
965
966#[derive(Debug, Clone, Serialize, Deserialize)]
967pub struct Condition {
968 pub field: FieldPath,
969 pub operator: ConditionOp,
970 pub value: Value,
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize)]
974pub enum ConditionOp {
975 Equals,
976 NotEquals,
977 GreaterThan,
978 LessThan,
979 Contains,
980 Exists,
981}
982
983#[derive(Debug, Clone, Serialize, Deserialize)]
985pub struct FieldTypeInfo {
986 pub field_name: String,
987 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)]
995 pub resolved_type: Option<ResolvedStructType>,
996}
997
998#[derive(Debug, Clone, Serialize, Deserialize)]
1000pub struct ResolvedStructType {
1001 pub type_name: String,
1002 pub fields: Vec<ResolvedField>,
1003 pub is_instruction: bool,
1004 pub is_account: bool,
1005 pub is_event: bool,
1006 #[serde(default)]
1008 pub is_enum: bool,
1009 #[serde(default)]
1011 pub enum_variants: Vec<String>,
1012}
1013
1014#[derive(Debug, Clone, Serialize, Deserialize)]
1016pub struct ResolvedField {
1017 pub field_name: String,
1018 pub field_type: String,
1019 pub base_type: BaseType,
1020 pub is_optional: bool,
1021 pub is_array: bool,
1022}
1023
1024#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1026pub enum BaseType {
1027 Integer, Float, String, Boolean, Object, Array, Binary, Timestamp, Pubkey, Any, }
1043
1044#[derive(Debug, Clone, Serialize, Deserialize)]
1046pub struct EntitySection {
1047 pub name: String,
1048 pub fields: Vec<FieldTypeInfo>,
1049 pub is_nested_struct: bool,
1050 pub parent_field: Option<String>, }
1052
1053impl FieldTypeInfo {
1054 pub fn new(field_name: String, rust_type_name: String) -> Self {
1055 let (base_type, is_optional, is_array, inner_type) =
1056 Self::analyze_rust_type(&rust_type_name);
1057
1058 FieldTypeInfo {
1059 field_name: field_name.clone(),
1060 rust_type_name,
1061 base_type: Self::infer_semantic_type(&field_name, base_type),
1062 is_optional,
1063 is_array,
1064 inner_type,
1065 source_path: None,
1066 resolved_type: None,
1067 }
1068 }
1069
1070 pub fn with_source_path(mut self, source_path: String) -> Self {
1071 self.source_path = Some(source_path);
1072 self
1073 }
1074
1075 fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
1077 let type_str = rust_type.trim();
1078
1079 if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1081 let (inner_base_type, _, inner_is_array, inner_inner_type) =
1082 Self::analyze_rust_type(&inner);
1083 return (
1084 inner_base_type,
1085 true,
1086 inner_is_array,
1087 inner_inner_type.or(Some(inner)),
1088 );
1089 }
1090
1091 if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1093 let (_inner_base_type, inner_is_optional, _, inner_inner_type) =
1094 Self::analyze_rust_type(&inner);
1095 return (
1096 BaseType::Array,
1097 inner_is_optional,
1098 true,
1099 inner_inner_type.or(Some(inner)),
1100 );
1101 }
1102
1103 let base_type = match type_str {
1105 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
1106 BaseType::Integer
1107 }
1108 "f32" | "f64" => BaseType::Float,
1109 "bool" => BaseType::Boolean,
1110 "String" | "&str" | "str" => BaseType::String,
1111 "Value" | "serde_json::Value" => BaseType::Any,
1112 "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1113 _ => {
1114 if type_str.contains("Bytes") || type_str.contains("bytes") {
1116 BaseType::Binary
1117 } else if type_str.contains("Pubkey") {
1118 BaseType::Pubkey
1119 } else {
1120 BaseType::Object
1121 }
1122 }
1123 };
1124
1125 (base_type, false, false, None)
1126 }
1127
1128 fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1130 let pattern = format!("{}<", generic_name);
1131 if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1132 let start = pattern.len();
1133 let end = type_str.len() - 1;
1134 if end > start {
1135 return Some(type_str[start..end].trim().to_string());
1136 }
1137 }
1138 None
1139 }
1140
1141 fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1143 let lower_name = field_name.to_lowercase();
1144
1145 if base_type == BaseType::Integer
1147 && (lower_name.ends_with("_at")
1148 || lower_name.ends_with("_time")
1149 || lower_name.contains("timestamp")
1150 || lower_name.contains("created")
1151 || lower_name.contains("settled")
1152 || lower_name.contains("activated"))
1153 {
1154 return BaseType::Timestamp;
1155 }
1156
1157 base_type
1158 }
1159}
1160
1161pub trait FieldAccessor<S> {
1162 fn path(&self) -> String;
1163}
1164
1165impl SerializableStreamSpec {
1170 pub fn compute_content_hash(&self) -> String {
1176 use sha2::{Digest, Sha256};
1177
1178 let mut spec_for_hash = self.clone();
1180 spec_for_hash.content_hash = None;
1181
1182 let json =
1184 serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1185
1186 let mut hasher = Sha256::new();
1188 hasher.update(json.as_bytes());
1189 let result = hasher.finalize();
1190
1191 hex::encode(result)
1193 }
1194
1195 pub fn verify_content_hash(&self) -> bool {
1198 match &self.content_hash {
1199 Some(hash) => {
1200 let computed = self.compute_content_hash();
1201 hash == &computed
1202 }
1203 None => true, }
1205 }
1206
1207 pub fn with_content_hash(mut self) -> Self {
1209 self.content_hash = Some(self.compute_content_hash());
1210 self
1211 }
1212}
1213
1214#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1220#[serde(rename_all = "lowercase")]
1221pub enum SortOrder {
1222 #[default]
1223 Asc,
1224 Desc,
1225}
1226
1227#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1229pub enum CompareOp {
1230 Eq,
1231 Ne,
1232 Gt,
1233 Gte,
1234 Lt,
1235 Lte,
1236}
1237
1238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1240pub enum PredicateValue {
1241 Literal(serde_json::Value),
1243 Dynamic(String),
1245 Field(FieldPath),
1247}
1248
1249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1251pub enum Predicate {
1252 Compare {
1254 field: FieldPath,
1255 op: CompareOp,
1256 value: PredicateValue,
1257 },
1258 And(Vec<Predicate>),
1260 Or(Vec<Predicate>),
1262 Not(Box<Predicate>),
1264 Exists { field: FieldPath },
1266}
1267
1268#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1270pub enum ViewTransform {
1271 Filter { predicate: Predicate },
1273
1274 Sort {
1276 key: FieldPath,
1277 #[serde(default)]
1278 order: SortOrder,
1279 },
1280
1281 Take { count: usize },
1283
1284 Skip { count: usize },
1286
1287 First,
1289
1290 Last,
1292
1293 MaxBy { key: FieldPath },
1295
1296 MinBy { key: FieldPath },
1298}
1299
1300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1302pub enum ViewSource {
1303 Entity { name: String },
1305 View { id: String },
1307}
1308
1309#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1311pub enum ViewOutput {
1312 #[default]
1314 Collection,
1315 Single,
1317 Keyed { key_field: FieldPath },
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1323pub struct ViewDef {
1324 pub id: String,
1326
1327 pub source: ViewSource,
1329
1330 #[serde(default)]
1332 pub pipeline: Vec<ViewTransform>,
1333
1334 #[serde(default)]
1336 pub output: ViewOutput,
1337}
1338
1339impl ViewDef {
1340 pub fn list(entity_name: &str) -> Self {
1342 ViewDef {
1343 id: format!("{}/list", entity_name),
1344 source: ViewSource::Entity {
1345 name: entity_name.to_string(),
1346 },
1347 pipeline: vec![],
1348 output: ViewOutput::Collection,
1349 }
1350 }
1351
1352 pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1354 ViewDef {
1355 id: format!("{}/state", entity_name),
1356 source: ViewSource::Entity {
1357 name: entity_name.to_string(),
1358 },
1359 pipeline: vec![],
1360 output: ViewOutput::Keyed {
1361 key_field: FieldPath::new(key_field),
1362 },
1363 }
1364 }
1365
1366 pub fn is_single(&self) -> bool {
1368 matches!(self.output, ViewOutput::Single)
1369 }
1370
1371 pub fn has_single_transform(&self) -> bool {
1373 self.pipeline.iter().any(|t| {
1374 matches!(
1375 t,
1376 ViewTransform::First
1377 | ViewTransform::Last
1378 | ViewTransform::MaxBy { .. }
1379 | ViewTransform::MinBy { .. }
1380 )
1381 })
1382 }
1383}
1384
1385#[macro_export]
1386macro_rules! define_accessor {
1387 ($name:ident, $state:ty, $path:expr) => {
1388 pub struct $name;
1389
1390 impl $crate::ast::FieldAccessor<$state> for $name {
1391 fn path(&self) -> String {
1392 $path.to_string()
1393 }
1394 }
1395 };
1396}