use serde::{Deserialize, Serialize};
use validator::Validate;
use super::rpc_response_context::RpcResponseContext;
use crate::validation::{
validate_encoding, validate_max_transaction_version, validate_signature_limit,
};
#[derive(Debug, Deserialize, Serialize, Clone, Validate)]
pub struct GetTransactionsRequest {
#[validate(nested)]
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<GetTransactionsConfig>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
#[validate(schema(function = validate_transactions_config))]
pub struct GetTransactionsConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding: Option<String>,
#[serde(rename = "maxSupportedTransactionVersion")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_supported_transaction_version: Option<u8>,
}
fn validate_transactions_config(
config: &GetTransactionsConfig,
) -> Result<(), validator::ValidationError> {
if let Some(limit) = config.limit {
validate_signature_limit(&limit)?;
}
if let Some(ref before) = config.before {
crate::validation::validate_signature(before)?;
}
if let Some(ref encoding) = config.encoding {
validate_encoding(encoding)?;
}
if let Some(ref max_version) = config.max_supported_transaction_version {
validate_max_transaction_version(max_version)?;
}
Ok(())
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetTransactionsResponse {
pub context: RpcResponseContext,
pub value: Vec<TransactionInfo>,
}
impl GetTransactionsResponse {
pub fn new(value: Vec<TransactionInfo>, slot: u64) -> Self {
Self {
context: RpcResponseContext::new(slot),
value,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionInfo {
pub signature: String,
pub slot: u64,
pub block_height: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_time: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub err: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
impl TransactionInfo {
pub fn new(signature: String, slot: u64) -> Self {
Self {
signature,
slot,
block_height: slot,
block_time: None,
err: None,
memo: None,
version: Some("legacy".to_string()),
}
}
pub fn with_block_time(mut self, block_time: i64) -> Self {
self.block_time = Some(block_time);
self
}
pub fn with_error(mut self, error: serde_json::Value) -> Self {
self.err = Some(error);
self
}
pub fn with_memo(mut self, memo: String) -> Self {
self.memo = Some(memo);
self
}
pub fn with_version(mut self, version: String) -> Self {
self.version = Some(version);
self
}
}
impl Default for GetTransactionsConfig {
fn default() -> Self {
Self {
limit: Some(100),
before: None,
encoding: Some("json".to_string()),
max_supported_transaction_version: Some(0),
}
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_str, to_string};
use super::*;
#[test]
fn test_get_transactions_request_serialization() {
let request = GetTransactionsRequest {
config: Some(GetTransactionsConfig {
limit: Some(10),
before: Some("5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW".to_string()),
encoding: Some("json".to_string()),
max_supported_transaction_version: Some(0),
}),
};
let json = to_string(&request).unwrap();
let deserialized: GetTransactionsRequest = from_str(&json).unwrap();
assert_eq!(
request.config.as_ref().unwrap().limit,
deserialized.config.as_ref().unwrap().limit
);
}
#[test]
fn test_transaction_info_creation() {
let signature = "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW".to_string();
let slot = 12345u64;
let info = TransactionInfo::new(signature.clone(), slot).with_block_time(1640995200000);
assert_eq!(info.signature, signature);
assert_eq!(info.slot, slot);
assert_eq!(info.block_height, slot); assert_eq!(info.block_time, Some(1640995200000)); }
#[test]
fn test_minimal_request() {
let request = GetTransactionsRequest { config: None };
let json = to_string(&request).unwrap();
let expected = r#"{}"#;
assert_eq!(json, expected);
}
#[test]
fn test_default_config() {
let config = GetTransactionsConfig::default();
assert_eq!(config.limit, Some(100));
assert_eq!(config.encoding, Some("json".to_string()));
assert_eq!(config.max_supported_transaction_version, Some(0));
assert!(config.before.is_none());
}
#[test]
fn test_config_validation_valid() {
use crate::validation::validate_request;
let config = GetTransactionsConfig {
limit: Some(500),
before: Some("5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW".to_string()),
encoding: Some("json".to_string()),
max_supported_transaction_version: Some(0),
};
let result = validate_request(config);
assert!(result.is_ok());
}
#[test]
fn test_config_validation_invalid_limit() {
use crate::validation::validate_request;
let config = GetTransactionsConfig {
limit: Some(1001), before: None,
encoding: Some("json".to_string()),
max_supported_transaction_version: Some(0),
};
let result = validate_request(config);
assert!(result.is_err());
}
#[test]
fn test_config_validation_invalid_encoding() {
use crate::validation::validate_request;
let config = GetTransactionsConfig {
limit: Some(100),
before: None,
encoding: Some("invalid".to_string()), max_supported_transaction_version: Some(0),
};
let result = validate_request(config);
assert!(result.is_err());
}
#[test]
fn test_response_creation() {
let transactions = vec![
TransactionInfo::new(
"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW".to_string(),
12345,
),
TransactionInfo::new(
"3Bxs9A1vAVi8n1hQrkVK7pn94BAmFWPKKDtkK5WwRXkacPZKqVm6gmnG6WtQReajvQZ9bdXLvEEsVJgS9zqjUBxG".to_string(),
12346,
),
];
let response = GetTransactionsResponse::new(transactions, 12500);
assert_eq!(response.value.len(), 2);
assert_eq!(response.context.slot, 12500);
assert_eq!(response.value[0].signature, "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW");
assert_eq!(response.value[1].signature, "3Bxs9A1vAVi8n1hQrkVK7pn94BAmFWPKKDtkK5WwRXkacPZKqVm6gmnG6WtQReajvQZ9bdXLvEEsVJgS9zqjUBxG");
}
}