use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SolanaQuery {
pub from_slot: u64,
#[serde(default)]
pub to_slot: Option<u64>,
#[serde(default)]
pub instructions: Vec<InstructionSelection>,
#[serde(default)]
pub transactions: Vec<TransactionSelection>,
#[serde(default)]
pub include_all_blocks: bool,
#[serde(default)]
pub field_selection: crate::field_selection::SolanaFieldSelection,
#[serde(default)]
pub max_num_instructions: Option<usize>,
#[serde(default)]
pub max_num_transactions: Option<usize>,
#[serde(default)]
pub logs: Vec<LogSelection>,
#[serde(default)]
pub max_num_blocks: Option<usize>,
#[serde(default)]
pub max_num_logs: Option<usize>,
#[serde(default)]
pub balances: Vec<BalanceSelection>,
#[serde(default)]
pub token_balances: Vec<TokenBalanceSelection>,
#[serde(default)]
pub include_balances: bool,
#[serde(default)]
pub include_token_balances: bool,
#[serde(default)]
pub max_num_balances: Option<usize>,
#[serde(default)]
pub max_num_token_balances: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InstructionSelection {
#[serde(default)]
pub program_id: Vec<String>,
#[serde(default)]
pub d1: Vec<String>,
#[serde(default)]
pub d2: Vec<String>,
#[serde(default)]
pub d4: Vec<String>,
#[serde(default)]
pub d8: Vec<String>,
#[serde(default)]
pub a0: Vec<String>,
#[serde(default)]
pub a1: Vec<String>,
#[serde(default)]
pub a2: Vec<String>,
#[serde(default)]
pub a3: Vec<String>,
#[serde(default)]
pub a4: Vec<String>,
#[serde(default)]
pub a5: Vec<String>,
#[serde(default)]
pub a6: Vec<String>,
#[serde(default)]
pub a7: Vec<String>,
#[serde(default)]
pub a8: Vec<String>,
#[serde(default)]
pub a9: Vec<String>,
#[serde(default)]
pub is_inner: Option<bool>,
}
impl InstructionSelection {
pub fn is_empty(&self) -> bool {
self.program_id.is_empty()
&& self.d1.is_empty()
&& self.d2.is_empty()
&& self.d4.is_empty()
&& self.d8.is_empty()
&& self.a0.is_empty()
&& self.a1.is_empty()
&& self.a2.is_empty()
&& self.a3.is_empty()
&& self.a4.is_empty()
&& self.a5.is_empty()
&& self.a6.is_empty()
&& self.a7.is_empty()
&& self.a8.is_empty()
&& self.a9.is_empty()
&& self.is_inner.is_none()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TransactionSelection {
#[serde(default)]
pub fee_payer: Vec<String>,
#[serde(default)]
pub success: Option<bool>,
}
impl TransactionSelection {
pub fn is_empty(&self) -> bool {
self.fee_payer.is_empty() && self.success.is_none()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LogSelection {
#[serde(default)]
pub program_id: Vec<String>,
#[serde(default)]
pub kind: Vec<String>,
}
impl LogSelection {
pub fn is_empty(&self) -> bool {
self.program_id.is_empty() && self.kind.is_empty()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BalanceSelection {
#[serde(default)]
pub account: Vec<String>,
}
impl BalanceSelection {
pub fn is_empty(&self) -> bool {
self.account.is_empty()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenBalanceSelection {
#[serde(default)]
pub account: Vec<String>,
#[serde(default)]
pub mint: Vec<String>,
#[serde(default)]
pub owner: Vec<String>,
#[serde(default)]
pub program_id: Vec<String>,
}
impl TokenBalanceSelection {
pub fn is_empty(&self) -> bool {
self.account.is_empty()
&& self.mint.is_empty()
&& self.owner.is_empty()
&& self.program_id.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field_selection::BlockField;
#[test]
fn field_selection_canonical_key_deserializes() {
let q: SolanaQuery =
serde_json::from_str(r#"{"from_slot":0,"field_selection":{"block":["slot"]}}"#)
.unwrap();
assert_eq!(q.field_selection.block, vec![BlockField::Slot]);
}
#[test]
fn field_selection_serializes_with_new_key() {
let q = SolanaQuery {
field_selection: crate::field_selection::SolanaFieldSelection {
block: vec![BlockField::Slot],
..Default::default()
},
..Default::default()
};
let json = serde_json::to_string(&q).unwrap();
assert!(json.contains("field_selection"));
assert!(!json.contains("\"fields\""));
}
#[test]
fn legacy_include_flags_are_ignored() {
let q: SolanaQuery = serde_json::from_str(
r#"{"from_slot":0,"instructions":[{"program_id":["p"],"include_transaction":true,"include_logs":true}]}"#,
)
.unwrap();
assert_eq!(q.instructions[0].program_id, vec!["p".to_string()]);
}
}