use crate::client::RequestParts;
use crate::ids::{AccountID, TransactionID};
use crate::models::{Transaction, TransactionFilter};
use crate::validation::Validate;
use crate::{Client, Error, Result, Timestamp};
use reqwest::Method;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListTransactionsQuery {
#[serde(rename = "from", default, skip_serializing_if = "Option::is_none")]
pub from: Option<Timestamp>,
#[serde(rename = "to", default, skip_serializing_if = "Option::is_none")]
pub to: Option<Timestamp>,
#[serde(rename = "pageSize", default, skip_serializing_if = "Option::is_none")]
pub page_size: Option<i64>,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<Vec<TransactionFilter>>,
}
impl Validate for ListTransactionsQuery {
fn validate(&self) -> Result<()> {
if self
.page_size
.is_some_and(|size| !(1..=1000).contains(&size))
{
return Err(Error::InvalidInput("pageSize must be 1..=1000".into()));
}
if let (Some(from), Some(to)) = (self.from, self.to)
&& (to < from
|| to.into_inner().signed_duration_since(from.into_inner())
> chrono::Duration::days(365))
{
return Err(Error::InvalidInput(
"transaction range must be ordered and at most 365 days".into(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListTransactionsResponse {
#[serde(rename = "from", default, skip_serializing_if = "Option::is_none")]
pub from: Option<Timestamp>,
#[serde(rename = "to", default, skip_serializing_if = "Option::is_none")]
pub to: Option<Timestamp>,
#[serde(rename = "pageSize", default, skip_serializing_if = "Option::is_none")]
pub page_size: Option<i64>,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<Vec<TransactionFilter>>,
#[serde(rename = "count", default, skip_serializing_if = "Option::is_none")]
pub count: Option<i64>,
#[serde(rename = "pages", default, skip_serializing_if = "Option::is_none")]
pub pages: Option<Vec<String>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn list_transactions(
&self,
account_id: &AccountID,
query: Option<&ListTransactionsQuery>,
) -> std::result::Result<
crate::ApiResponse<ListTransactionsResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let path = format!("/v3/accounts/{account_id_encoded}/transactions");
let request = RequestParts::new(Method::GET, &path);
let request = match query {
Some(query) => request.query(query)?,
None => request,
};
self.execute(request).await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionDetailsResponse {
#[serde(
rename = "transaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub transaction: Option<Transaction>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn transaction_details(
&self,
account_id: &AccountID,
transaction_id: &TransactionID,
) -> std::result::Result<
crate::ApiResponse<TransactionDetailsResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let transaction_id_encoded = crate::client::segment(transaction_id.as_str());
let path =
format!("/v3/accounts/{account_id_encoded}/transactions/{transaction_id_encoded}");
self.execute(RequestParts::new(Method::GET, &path)).await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsByIdRangeQuery {
#[serde(rename = "from")]
pub from: TransactionID,
#[serde(rename = "to")]
pub to: TransactionID,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<Vec<TransactionFilter>>,
}
impl Validate for TransactionsByIdRangeQuery {
fn validate(&self) -> Result<()> {
let from = self.from.as_str().parse::<u64>();
let to = self.to.as_str().parse::<u64>();
if !matches!((from, to), (Ok(from), Ok(to)) if from <= to) {
return Err(Error::InvalidInput("invalid transaction ID range".into()));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsByIdRangeResponse {
#[serde(
rename = "transactions",
default,
skip_serializing_if = "Option::is_none"
)]
pub transactions: Option<Vec<Transaction>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn transactions_by_id_range(
&self,
account_id: &AccountID,
query: &TransactionsByIdRangeQuery,
) -> std::result::Result<
crate::ApiResponse<TransactionsByIdRangeResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let path = format!("/v3/accounts/{account_id_encoded}/transactions/idrange");
self.execute(RequestParts::new(Method::GET, &path).query(query)?)
.await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsSinceIdQuery {
#[serde(rename = "id")]
pub id: TransactionID,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<Vec<TransactionFilter>>,
}
impl Validate for TransactionsSinceIdQuery {
fn validate(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsSinceIdResponse {
#[serde(
rename = "transactions",
default,
skip_serializing_if = "Option::is_none"
)]
pub transactions: Option<Vec<Transaction>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn transactions_since_id(
&self,
account_id: &AccountID,
query: &TransactionsSinceIdQuery,
) -> std::result::Result<
crate::ApiResponse<TransactionsSinceIdResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let path = format!("/v3/accounts/{account_id_encoded}/transactions/sinceid");
self.execute(RequestParts::new(Method::GET, &path).query(query)?)
.await
}
}