diff --git a/src/effects/effects_for_operation_request.rs b/src/effects/effects_for_operation_request.rs new file mode 100644 index 0000000..957bbf5 --- /dev/null +++ b/src/effects/effects_for_operation_request.rs @@ -0,0 +1,164 @@ +use crate::{ + models::{Order, Request}, + BuildQueryParametersExt, +}; + +/// Represents the request to fetch the effects for a specific operation from the Horizon API. +/// +/// `EffectsForOperationRequest` 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 `EffectsForOperationRequest` 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. +/// +/// # Fields +/// * `operation_id` - The operation id to filter effects. +/// * `cursor` - A pointer to a specific location in a collection of responses, derived from the +/// * `limit` - Specifies the maximum number of records to be returned in a single response. +/// * `order` - Determines the [`Order`] of the records in the response. Valid options are [`Order::Asc`] (ascending) +/// +/// # Example +/// ```rust +/// use stellar_rs::effects::effects_for_operation_request::EffectsForOperationRequest; +/// use stellar_rs::models::*; +/// +/// let request = EffectsForOperationRequest::new() +/// .set_operation_id("123") +/// .set_cursor(1).unwrap() +/// .set_limit(10).unwrap() +/// .set_order(Order::Asc); +/// +/// // The request can now be used with a Horizon client to fetch effects. +/// ``` +/// + +#[derive(Default)] +pub struct EffectsForOperationRequest { + /// The operation id to filter effects. + operation_id: 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 EffectsForOperationRequest { + pub fn new() -> EffectsForOperationRequest { + EffectsForOperationRequest::default() + } + + pub fn set_operation_id(self, operation_id: &str) -> EffectsForOperationRequest { + EffectsForOperationRequest { + operation_id: Some(operation_id.to_string()), + ..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(EffectsForOperationRequest { + 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(EffectsForOperationRequest { + 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) -> EffectsForOperationRequest { + EffectsForOperationRequest { + order: Some(order), + ..self + } + } +} + +impl Request for EffectsForOperationRequest { + fn get_query_parameters(&self) -> String { + vec![ + self.operation_id + .as_ref() + .map(|l| format!("operation_id={}", 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::*; + + #[test] + fn test_get_query_parameters() { + let request = EffectsForOperationRequest::new() + .set_operation_id("123") + .set_cursor(1) + .unwrap() + .set_limit(10) + .unwrap() + .set_order(Order::Asc); + + let query_parameters = request.get_query_parameters(); + assert_eq!( + query_parameters, + "?operation_id=123&cursor=1&limit=10&order=asc" + ); + } + + #[test] + fn test_build_url() { + let request = EffectsForOperationRequest::new(); + let base_url = "https://horizon-testnet.stellar.org"; + let url = request.build_url(base_url); + assert_eq!(url, "https://horizon-testnet.stellar.org/effects"); + } +} \ No newline at end of file diff --git a/src/effects/effects_for_transaction_request.rs b/src/effects/effects_for_transaction_request.rs new file mode 100644 index 0000000..56fd186 --- /dev/null +++ b/src/effects/effects_for_transaction_request.rs @@ -0,0 +1,164 @@ +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"); + } +} \ No newline at end of file diff --git a/src/effects/mod.rs b/src/effects/mod.rs index 6f8bab5..22b484b 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -2,6 +2,8 @@ pub mod all_effects_request; pub mod effects_for_account_request; pub mod effects_for_ledger_request; pub mod effects_for_liquidity_pools_request; +pub mod effects_for_operation_request; +pub mod effects_for_transaction_request; pub mod response; static EFFECTS_PATH: &str = "effects"; @@ -11,6 +13,8 @@ pub mod prelude { pub use super::effects_for_account_request::*; pub use super::effects_for_ledger_request::*; pub use super::effects_for_liquidity_pools_request::*; + pub use super::effects_for_operation_request::*; + pub use super::effects_for_transaction_request::*; pub use super::response::*; } @@ -180,4 +184,84 @@ mod tests { "account_debited" ); } + + #[tokio::test] + async fn test_get_effects_for_operation() { + const OPERATION_ID: &str = "459561504769"; + 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_operation_request = EffectsForOperationRequest::new() + .set_operation_id(OPERATION_ID) + .set_limit(2) + .unwrap(); + let effects_for_operation_response = horizon_client + .get_effects_for_operation(&effects_for_operation_request) + .await; + + assert!(effects_for_operation_response.is_ok()); + + let binding = effects_for_operation_response.clone().unwrap(); + let record = &binding.embedded().records()[0]; + + assert_eq!(record.id(), ID); + assert_eq!(record.paging_token(), PAGING_TOKEN); + assert_eq!(record.account(), ACCOUNT); + assert_eq!(record.effect_type(), RECORD_TYPE); + assert_eq!(record.type_i(), &TYPE_I); + assert_eq!(record.created_at(), CREATED_AT); + assert_eq!( + record.starting_balance().as_ref().unwrap(), + &STARTING_BALANCE + ); + } + + #[tokio::test] + async fn test_get_effects_for_transaction() { + const TRANSACTION_HASH: &str = + "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020"; + 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_transaction_request = EffectForTransactionRequest::new() + .set_transaction_hash(TRANSACTION_HASH.to_string()) + .set_limit(2) + .unwrap(); + + let effects_for_transaction_response = horizon_client + .get_effects_for_transaction(&effects_for_transaction_request) + .await; + + assert!(effects_for_transaction_response.is_ok()); + + let binding = effects_for_transaction_response.clone().unwrap(); + let record = &binding.embedded().records()[0]; + + assert_eq!(record.id(), ID); + assert_eq!(record.paging_token(), PAGING_TOKEN); + assert_eq!(record.account(), ACCOUNT); + assert_eq!(record.effect_type(), RECORD_TYPE); + assert_eq!(record.type_i(), &TYPE_I); + assert_eq!(record.created_at(), CREATED_AT); + assert_eq!( + record.starting_balance().as_ref().unwrap(), + &STARTING_BALANCE + ); + } } diff --git a/src/horizon_client.rs b/src/horizon_client.rs index 0d11526..ccece98 100644 --- a/src/horizon_client.rs +++ b/src/horizon_client.rs @@ -415,6 +415,108 @@ impl HorizonClient { self.get::(request).await } + /// Retrieves a list of effects for a specific operation from the Horizon server. + /// + /// This asynchronous method fetches a list of effects for a specific operation from the Horizon server. + /// It requires an [`EffectsForOperationRequest`] to specify the operation ID and optional query parameters. + /// + /// # Arguments + /// * `request` - A reference to an [`EffectsForOperationRequest`] instance, containing the operation ID + /// 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 [`EffectsForOperationRequest`] and set the operation ID 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 = EffectsForOperationRequest::new() + /// .set_operation_id("123"); + /// + /// let response = horizon_client.get_effects_for_operation(&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_operation( + &self, + request: &EffectsForOperationRequest, + ) -> Result { + 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("transaction_hash".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