From 04e541df10e1cb135189d2d6f83ce13b1a431462 Mon Sep 17 00:00:00 2001 From: LeonardTibben Date: Mon, 26 Feb 2024 13:43:32 +0100 Subject: [PATCH] transaction request --- .../effects_for_transaction_request.rs | 166 ++++++++++++++++ src/effects/mod.rs | 2 + src/horizon_client.rs | 177 ++++++++++++++++-- 3 files changed, 329 insertions(+), 16 deletions(-) create mode 100644 src/effects/effects_for_transaction_request.rs diff --git a/src/effects/effects_for_transaction_request.rs b/src/effects/effects_for_transaction_request.rs new file mode 100644 index 0000000..567598d --- /dev/null +++ b/src/effects/effects_for_transaction_request.rs @@ -0,0 +1,166 @@ +use stellar_xdr::curr::SurveyRequestMessage; + +use crate::{models::{Order, Request}, BuildQueryParametersExt}; + +/// Represents a request to fetch effect data from the Stellar Horizon API. +/// +/// `EffectForTransactionRequest` is a struct used to construct queries for retrieving information about effects +/// from the Horizon server. It includes parameters that allow for pagination control and sorting +/// of the effect records. +/// +/// # Usage +/// Instances of `EffectForTransactionRequest` are created and optionally configured using the builder pattern. +/// Once the desired parameters are set, the request can be passed to the Horizon client to fetch +/// effect data. +/// +/// # Example +/// ```rust +/// use stellar_rs::effects::effects_for_transaction_request::EffectForTransactionRequest; +/// use stellar_rs::models::*; +/// +/// let request = EffectForTransactionRequest::new() +/// .set_transaction_hash("transaction_hash".to_string()) +/// .set_cursor(1234).unwrap() +/// .set_limit(20).unwrap() +/// .set_order(Order::Desc); +/// +/// // The request can now be used with a Horizon client to fetch effects. +/// ``` +#[derive(Default)] +pub struct EffectForTransactionRequest { + /// The transaction hash of the transaction of the effect + transaction_hash: Option, + + /// A pointer to a specific location in a collection of responses, derived from the + /// `paging_token` value of a record. Used for pagination control in the API response. + cursor: Option, + + /// Specifies the maximum number of records to be returned in a single response. + /// The range for this parameter is from 1 to 200. The default value is set to 10. + limit: Option, + + /// Determines the [`Order`] of the records in the response. Valid options are [`Order::Asc`] (ascending) + /// and [`Order::Desc`] (descending). If not specified, it defaults to ascending. + order: Option, +} + +impl EffectForTransactionRequest { + /// Creates a new `EffectForTransactionRequest` with default parameters. + pub fn new() -> Self { + EffectForTransactionRequest::default() + } + + /// Sets the liquidity pool id for the request. + /// + /// # Arguments + /// * `liquidity_pool_id` - A `String` value representing the liquidity pool id. + /// + pub fn set_transaction_hash( + self, + transaction_hash: String, + ) -> EffectForTransactionRequest { + EffectForTransactionRequest { + transaction_hash: Some(transaction_hash), + ..self + } + } + + /// Sets the cursor for pagination. + /// + /// # Arguments + /// * `cursor` - A `u32` value pointing to a specific location in a collection of responses. + /// + pub fn set_cursor(self, cursor: u32) -> Result { + if cursor < 1 { + return Err("cursor must be greater than or equal to 1".to_string()); + } + + Ok(EffectForTransactionRequest { + cursor: Some(cursor), + ..self + }) + } + + /// Sets the maximum number of records to return. + /// + /// # Arguments + /// * `limit` - A `u8` value specifying the maximum number of records. Range: 1 to 200. Defaults to 10. + /// + pub fn set_limit(self, limit: u8) -> Result { + if limit < 1 || limit > 200 { + return Err("limit must be between 1 and 200".to_string()); + } + + Ok(EffectForTransactionRequest { + limit: Some(limit), + ..self + }) + } + + /// Sets the order of the returned records. + /// + /// # Arguments + /// * `order` - An [`Order`] enum value specifying the order (ascending or descending). + /// + pub fn set_order(self, order: Order) -> EffectForTransactionRequest { + EffectForTransactionRequest { + order: Some(order), + ..self + } + } +} + +impl Request for EffectForTransactionRequest { + fn get_query_parameters(&self) -> String { + vec![ + self.transaction_hash + .as_ref() + .map(|l| format!("transaction_hash={}", l)), + self.cursor.as_ref().map(|c| format!("cursor={}", c)), + self.limit.as_ref().map(|l| format!("limit={}", l)), + self.order.as_ref().map(|o| format!("order={}", o)), + ] + .build_query_parameters() + } + + fn build_url(&self, base_url: &str) -> String { + format!( + "{}/{}{}", + base_url, + super::EFFECTS_PATH, + self.get_query_parameters() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::BuildQueryParametersExt; + + #[test] + fn test_effects_for_liquidity_pools_request() { + let request = EffectForTransactionRequest::new() + .set_transaction_hash("transaction_hash".to_string()) + .set_cursor(1) + .unwrap() + .set_limit(10) + .unwrap() + .set_order(Order::Asc); + + let url = request.build_url("https://horizon-testnet.stellar.org"); + let query_parameters = vec![ + Some("transaction_hash=transaction_hash".to_string()), + Some("cursor=1".to_string()), + Some("limit=10".to_string()), + Some("order=asc".to_string()), + ] + .build_query_parameters(); + + assert_eq!( + url, + "https://horizon-testnet.stellar.org/effects?transaction_hash=transaction_hash&cursor=1&limit=10&order=asc" + ); + assert_eq!(query_parameters, "?transaction_hash=transaction_hash&cursor=1&limit=10&order=asc"); + } +} diff --git a/src/effects/mod.rs b/src/effects/mod.rs index 298dd07..e707c43 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -3,6 +3,7 @@ pub mod effects_response; pub mod effects_for_account_request; pub mod effects_for_liquidity_pools_request; pub mod effects_for_ledger_request; +pub mod effects_for_transaction_request; static EFFECTS_PATH: &str = "effects"; @@ -12,6 +13,7 @@ pub mod prelude { pub use super::effects_for_account_request::*; pub use super::effects_for_liquidity_pools_request::*; pub use super::effects_for_ledger_request::*; + pub use super::effects_for_transaction_request::*; } #[cfg(test)] diff --git a/src/horizon_client.rs b/src/horizon_client.rs index c1983d1..f29dcfb 100644 --- a/src/horizon_client.rs +++ b/src/horizon_client.rs @@ -411,6 +411,57 @@ impl HorizonClient { self.get::(request).await } + /// Retrieves a list of effects for a specific transaction from the Horizon server. + /// + /// This asynchronous method fetches a list of effects for a specific transaction from the Horizon server. + /// It requires an [`EffectForTransactionRequest`] to specify the transaction hash and optional query parameters. + /// + /// # Arguments + /// * `request` - A reference to an [`EffectForTransactionRequest`] instance, containing the transaction hash + /// and optional query parameters for the effects request. + /// + /// # Returns + /// + /// On successful execution, returns a `Result` containing an [`EffectsResponse`], which includes + /// the list of effects obtained from the Horizon server. If the request fails, it returns an error within `Result`. + /// + /// # Example + /// To use this method, create an instance of [`EffectForTransactionRequest`] and set the transaction hash and any + /// desired query parameters. + /// + /// ``` + /// # use stellar_rs::effects::prelude::*; + /// # use stellar_rs::models::Request; + /// # use stellar_rs::horizon_client::HorizonClient; + /// + /// # async fn example() -> Result<(), Box> { + /// # let base_url = "https://horizon-testnet.stellar.org".to_string(); + /// # let horizon_client = HorizonClient::new(base_url) + /// # .expect("Failed to create Horizon Client"); + /// let request = EffectForTransactionRequest::new() + /// .set_transaction_hash("000000006520216af66d20d63a58534d6cbdf28ba9f2a9c1e03f8d9a756bb7d988b29bca".to_string()); + /// + /// let response = horizon_client.get_effects_for_transaction(&request).await; + /// + /// // Access the effects + /// if let Ok(effects_response) = response { + /// for effect in effects_response._embedded().records() { + /// println!("Effect ID: {}", effect.id()); + /// // Further processing... + /// } + /// } + /// + /// # Ok({}) + /// # } + /// ``` + /// + pub async fn get_effects_for_transaction( + &self, + request: &EffectForTransactionRequest, + ) -> Result { + self.get::(request).await + } + /// Retrieves a list of all ledgers. /// /// This asynchronous method is designed to fetch list of ledgers @@ -1911,41 +1962,135 @@ mod tests { } #[tokio::test] - async fn test_get_effects_for_ledger() { - // found by trial and error in the Stellar laboratory - let ledger_sequence = 125; + async fn get_effects_for_transactions() { + const ID: &str = "0000000459561504769-0000000001"; + const PAGING_TOKEN: &str = "459561504769-1"; + const ACCOUNT: &str = "GAIH3ULLFQ4DGSECF2AR555KZ4KNDGEKN4AFI4SU2M7B43MGK3QJZNSR"; + const RECORD_TYPE: &str = "account_created"; + const TYPE_I: u32 = 0; + const CREATED_AT: &str = "2024-02-06T17:42:48Z"; + const STARTING_BALANCE: &str = "10000000000.0000000"; let horizon_client = HorizonClient::new("https://horizon-testnet.stellar.org".to_string()).unwrap(); - let effects_for_ledger_request = - EffectsForLedgerRequest::new().set_sequence(ledger_sequence); - let _effects_for_ledger_response = horizon_client - .get_effects_for_ledger(&effects_for_ledger_request) + let effects_for_liquidity_pools_request = + EffectForTransactionRequest::new().set_limit(2).unwrap(); + + let effects_for_liquidity_pools_response = horizon_client + .get_effects_for_transaction(&effects_for_liquidity_pools_request) .await; - println!("{:#?}", _effects_for_ledger_response); + assert!(effects_for_liquidity_pools_response.clone().is_ok()); + + assert_eq!( + effects_for_liquidity_pools_response + .clone() + .unwrap() + ._embedded() + .records()[0] + .id(), + ID + ); + + assert_eq!( + effects_for_liquidity_pools_response + .clone() + .unwrap() + ._embedded() + .records()[0] + .paging_token(), + PAGING_TOKEN + ); - assert!(_effects_for_ledger_response.clone().is_ok()); + assert_eq!( + effects_for_liquidity_pools_response + .clone() + .unwrap() + ._embedded() + .records()[0] + .account(), + ACCOUNT + ); assert_eq!( - _effects_for_ledger_response + effects_for_liquidity_pools_response .clone() .unwrap() ._embedded() .records()[0] - .id, - "0000000536870916097-0000000001" + .effect_type(), + RECORD_TYPE ); assert_eq!( - _effects_for_ledger_response + effects_for_liquidity_pools_response .clone() .unwrap() ._embedded() - .records()[1] - .effect_type, - "account_debited" + .records()[0] + .type_i(), + &TYPE_I + ); + + assert_eq!( + effects_for_liquidity_pools_response + .clone() + .unwrap() + ._embedded() + .records()[0] + .created_at(), + CREATED_AT + ); + + assert_eq!( + effects_for_liquidity_pools_response + .clone() + .unwrap() + ._embedded() + .records()[0] + .starting_balance() + .as_ref() + .unwrap(), + &STARTING_BALANCE ); } } + +#[tokio::test] +async fn test_get_effects_for_ledger() { + // found by trial and error in the Stellar laboratory + let ledger_sequence = 125; + + let horizon_client = + HorizonClient::new("https://horizon-testnet.stellar.org".to_string()).unwrap(); + + let effects_for_ledger_request = EffectsForLedgerRequest::new().set_sequence(ledger_sequence); + let _effects_for_ledger_response = horizon_client + .get_effects_for_ledger(&effects_for_ledger_request) + .await; + + println!("{:#?}", _effects_for_ledger_response); + + assert!(_effects_for_ledger_response.clone().is_ok()); + + assert_eq!( + _effects_for_ledger_response + .clone() + .unwrap() + ._embedded() + .records()[0] + .id, + "0000000536870916097-0000000001" + ); + + assert_eq!( + _effects_for_ledger_response + .clone() + .unwrap() + ._embedded() + .records()[1] + .effect_type, + "account_debited" + ); +}