diff --git a/src/horizon_client.rs b/src/horizon_client.rs index eaee254..c2b7601 100644 --- a/src/horizon_client.rs +++ b/src/horizon_client.rs @@ -11,36 +11,58 @@ use crate::{ ledgers::prelude::{ LedgersRequest, LedgersResponse, SingleLedgerRequest, SingleLedgerResponse, }, - models::{Request, Response}, + models::{Request, Response, self}, }; use reqwest; use url::Url; pub struct HorizonClient { - /// The base URL for the Horizon server + /// The URL of the Horizon API server base_url: String, } impl HorizonClient { - /// Creates a new Horizon client + /// Creates a new instance of the `HorizonClient`. + /// + /// This constructor method initializes a new `HorizonClient` with the specified base URL + /// for the Horizon API server. It performs validation on the provided URL to ensure it is + /// well-formed and appropriate for establishing a connection. + /// /// # Arguments - /// * `base_url` - The base URL for the Horizon server + /// * `base_url` - A `String` representing the base URL of the Horizon server. + /// /// # Returns - /// The Horizon client + /// If successful, this method returns a `Result` containing the initialized `HorizonClient` + /// instance. If the URL validation fails, it returns an error encapsulated within `Result`. + /// + /// # Example + /// ```rust + /// # use stellar_rust_sdk::horizon_client::HorizonClient; + /// let horizon_client = HorizonClient::new("https://horizon-testnet.stellar.org".to_string()) + /// .expect("Failed to create HorizonClient"); + /// ``` pub fn new(base_url: String) -> Result { url_validate(&base_url)?; Ok(Self { base_url }) } - /// Gets an account list from the server + /// Retrieves a list of accounts. + /// + /// This method retrieves a list of accounts by one of four filters: signer, asset, liquidity pool or sponsor, + /// based on the specified [`AccountsRequest`]. + /// + /// Adheres to the List All Accounts + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The accounts request + /// * `request` - A reference to an [`AccountsRequest`] instance, which contains the parameters + /// for the account list request. + /// /// # Returns - /// The accounts response - /// # Errors - /// Returns an error if the request fails - /// [GET /accounts](https://www.stellar.org/developers/horizon/reference/endpoints/accounts.html) + /// If successful, this method returns a `Result` containing an [`AccountsResponse`], + /// which encapsulates the list of accounts retrieved from the server. + /// In case of a failure in the request, it returns an error encapsulated within `Result`. + /// pub async fn get_account_list( &self, request: &AccountsRequest, @@ -48,15 +70,25 @@ impl HorizonClient { self.get::(request).await } - /// Gets a single account from the server + /// Retrieves a single account's information. + /// + /// This asynchronous method is designed to fetch detailed information for a specific + /// account from the Horizon server. It requires a [`SingleAccountRequest`] which + /// includes the account ID to be queried. + /// + /// Adheres to the Retrieve an Account + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The account request + /// * `request` - A reference to a [`SingleAccountRequest`] instance, containing the + /// account ID for which details are to be fetched. + /// /// # Returns - /// The account response - /// # Errors - /// Returns an error if the request fails - /// [GET /accounts/{account_id}](https://www.stellar.org/developers/horizon/reference/endpoints/accounts-single.html) + /// + /// On success, returns a `Result` wrapping a [`SingleAccountsResponse`], which includes the + /// detailed information of the requested account. If the request fails, it returns an error + /// encapsulated within `Result`. + /// pub async fn get_single_account( &self, request: &SingleAccountRequest, @@ -64,15 +96,26 @@ impl HorizonClient { self.get::(request).await } - /// Gets all assets from the server + + /// Retrieves a list of all assets from the Horizon server. + /// + /// This asynchronous method fetches a complete list of assets. + /// It requires a [`AllAssetsRequest`] to specify query parameters for the assets retrieval + /// such as filters by `asset_code` or `asset_issuer`. + /// + /// Adheres to the List all Assets + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The all assets request + /// * `request` - A reference to an [`AllAssetsRequest`] instance, containing the + /// parameters for the assets list request. + /// /// # Returns - /// The all assets response - /// # Errors - /// Returns an error if the request fails - /// [GET /assets](https://www.stellar.org/developers/horizon/reference/endpoints/assets-all.html) + /// + /// On success, this method returns a `Result` wrapping an [`AllAssetsResponse`], which includes + /// the comprehensive list of assets retrieved from the Horizon server. If the request + /// encounters an issue, an error is returned within `Result`. + /// pub async fn get_all_assets( &self, request: &AllAssetsRequest, @@ -80,15 +123,23 @@ impl HorizonClient { self.get::(request).await } - /// Gets all claimable balances from the server + /// Retrieves all claimable balances. + /// + /// This asynchronous method queries the Horizon server for all claimable balances. It + /// requires a [`AllClaimableBalancesRequest`] to specify the query parameters. + /// + /// Adheres to the List All Claimable Balances + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The all claimable balances request + /// * `request` - A reference to an [`AllClaimableBalancesRequest`] instance, which contains + /// the parameters for the claimable balances request. + /// /// # Returns - /// The all claimable balances response - /// # Errors - /// Returns an error if the request fails - /// [GET /claimable_balances/all](https://www.stellar.org/developers/horizon/reference/endpoints/claimable_balances-all.html) + /// + /// Returns a `Result` containing an [`AllClaimableBalancesResponse`] with the list of all + /// claimable balances if successful. In case of a failure, it returns an error within `Result`. + /// pub async fn get_all_claimable_balances( &self, request: &AllClaimableBalancesRequest, @@ -96,15 +147,25 @@ impl HorizonClient { self.get::(request).await } - /// Gets a single claimable balance from the server + /// Retrieves a specific claimable balance. + /// + /// This asynchronous method is used to fetch detailed information about a single claimable + /// balance from the Horizon server. It requires a [`SingleClaimableBalanceRequest`], which + /// includes the unique identifier of the claimable balance to be retrieved. + /// + /// Adheres to the Retrieve a Claimable Balance + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The single claimable balance request + /// * `request` - A reference to a [`SingleClaimableBalanceRequest`] instance, containing the + /// unique ID of the claimable balance to be fetched. + /// /// # Returns - /// The single claimable balance response - /// # Errors - /// Returns an error if the request fails - /// [GET /claimable_balances/{claimable_balance_id}](https://www.stellar.org/developers/horizon/reference/endpoints/claimable_balances-single.html) + /// + /// On successful execution, returns a `Result` containing a [`SingleClaimableBalanceResponse`], + /// which includes detailed information about the requested claimable balance. If the request + /// fails, it returns an error within `Result`. + /// pub async fn get_single_claimable_balance( &self, request: &SingleClaimableBalanceRequest, @@ -112,15 +173,25 @@ impl HorizonClient { self.get::(request).await } - /// Gets the all ledger response from the server + /// Retrieves a list of all ledgers from the Horizon server. + /// + /// This asynchronous method is designed to fetch list of ledgers + /// from the Horizon server. It requires a [`LedgersRequest`] to specify the parameters + /// for the ledger retrieval. + /// + /// Adheres to the List All Ledgers + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The ledgers request + /// * `request` - A reference to a [`LedgersRequest`] instance, specifying the query + /// parameters for retrieving the ledgers. + /// /// # Returns - /// The ledgers response - /// # Errors - /// Returns an error if the request fails - /// [GET /ledgers](https://www.stellar.org/developers/horizon/reference/endpoints/ledgers-all.html) + /// + /// On successful execution, returns a `Result` containing a [`LedgersResponse`], + /// which includes the list of all ledgers obtained from the Horizon server. If the request + /// fails, it returns an error within `Result`. + /// pub async fn get_all_ledgers( &self, request: &LedgersRequest, @@ -128,65 +199,129 @@ impl HorizonClient { self.get::(request).await } - /// Gets a single ledger from the server + /// Retrieves information for a specific ledger. + /// + /// This asynchronous method fetches details of a single ledger from the Horizon server. + /// It requires a [`SingleLedgerRequest`] that includes the ID of the ledger to be + /// retrieved. + /// + /// Adheres to the Retrieve a Ledger + /// endpoint. + /// /// # Arguments - /// * `self` - The Horizon client - /// * request - The single ledger request + /// * `request` - A reference to a [`SingleLedgerRequest`] instance, containing the + /// ledger ID for which information is to be fetched. + /// /// # Returns - /// The single ledger response - /// # Errors - /// Returns an error if the request fails - /// [GET /ledgers/{ledger_id}](https://www.stellar.org/developers/horizon/reference/endpoints/ledgers-single.html) + /// + /// Returns a `Result` containing a [`SingleLedgerResponse`], which includes detailed + /// information about the requested ledger. If the request fails, it returns an error + /// encapsulated within `Result`. + /// pub async fn get_single_ledger( &self, request: &SingleLedgerRequest, ) -> Result { self.get::(request).await } - - /// Sends a GET request to the server + + /// Sends a GET request to the Horizon server and retrieves a specified response type. + /// + /// This internal asynchronous method is designed to handle various GET requests to the + /// Horizon server. It is generic over the response type, allowing for flexibility in + /// handling different types of responses as dictated by the caller. This method performs + /// key tasks such as request validation, URL construction, sending the request, and + /// processing the received response. + /// + /// # Type Parameters + /// + /// * `Response` - Defines the expected response type. This type must implement the + /// [`models::Response`] trait. + /// /// # Arguments - /// * `TResponse` - The type of the response + /// + /// * `request` - A reference to an object implementing the [`Request`] trait. It contains + /// specific details about the GET request to be sent. + /// /// # Returns - /// The response from the server - /// # Errors - /// Returns an error if the request fails - async fn get( + /// + /// Returns a `Result` containing the response of type [`Response`] if the request is + /// successful. In case of failure (e.g., network issues, server errors), it returns an + /// error encapsulated as a `String`. + /// + /// # Example Usage + /// + /// This function is typically not called directly but through other specific methods of + /// the `HorizonClient` that define the type of request and response. + /// + /// # Remarks + /// + /// As a core utility function within `HorizonClient`, it centralizes the logic of sending + /// GET requests and handling responses. Modifications or enhancements to the request or + /// response handling logic should be implemented here to maintain consistency across the + /// client's interface. + /// + async fn get( &self, request: &impl Request, - ) -> Result { + ) -> Result { // Validate the request. request.validate()?; - //match request by SingleAccountRequest or AccountsRequest - // Determine the url - // TODO: construct with query parameters - + // Construct the URL with potential query parameters. let url = request.build_url(&self.base_url); + + // Send the request and await the response. let response = reqwest::get(&url).await.map_err(|e| e.to_string())?; - // println!("\n\nREQWEST RESPONSE: {:?}", response); - let result: TResponse = handle_response(response).await?; - // print!("\n\nResult: {:?}", result); + // Process the response and return the result. + let result: Response = handle_response(response).await?; Ok(result) } } -/// Handles the response from the server +/// Handles the response received from an HTTP request made to the Horizon server. +/// +/// This asynchronous internal function processes the `reqwest::Response` obtained from a +/// GET request. It is generic over the type `Response` which must implement the +/// [`models::Response`] trait. The function primarily checks the HTTP status code of the +/// response. If the status is `OK`, it attempts to deserialize the response body into +/// the specified `Response` type. For other status codes, it treats the response as an +/// error message. +/// +/// # Type Parameters +/// +/// * `Response` - The type into which the response body is to be deserialized. This type +/// must implement the [`models::Response`] trait. +/// /// # Arguments -/// * `response` - The response from the server +/// +/// * `response` - The `reqwest::Response` object obtained from the HTTP request. +/// /// # Returns -/// The deserialized response from the server response payload +/// +/// On success (HTTP status `OK`), returns a `Result` containing the deserialized +/// `Response`. If deserialization fails, or if the HTTP status is not `OK`, it returns +/// an error encapsulated as a `String`. +/// +/// # Example Usage +/// This function is not intended to be called directly. It is designed to be called +/// exclusively by the [`HorizonClient::get`](crate::horizon_client::HorizonClient::get) function. +/// /// # Errors -/// Returns an error if the response is not successful -async fn handle_response( +/// +/// Errors can arise from various situations, such as: +/// - Non-`OK` HTTP status codes. +/// - Failure in reading the response body. +/// - Deserialization errors when converting the response body into the `Response` type. +/// +async fn handle_response( response: reqwest::Response, -) -> Result { - // println!("\n Response: {:?}", response); +) -> Result { match response.status() { reqwest::StatusCode::OK => { let _response = response.text().await.map_err(|e| e.to_string())?; - TResponse::from_json(_response) + Response::from_json(_response) } _ => { let response = response.text().await.map_err(|e| e.to_string())?; @@ -194,13 +329,39 @@ async fn handle_response( } } } -/// url_validate validates a URL + + +/// Validates the format of a given URL. +/// +/// This function is an internal utility for validating the format of a URL. +/// It is typically invoked by [`HorizonClient::new`](crate::horizon_client::HorizonClient::new) to ensure that the URL +/// provided for initializing the client is correctly formatted. The function checks if +/// the URL begins with "http://" or "https://", and attempts to parse it using the `Url` +/// type from the `url` crate. +/// +/// # Arguments +/// +/// * `url` - A string slice representing the URL to be validated. +/// +/// # Returns +/// +/// Returns `Ok(())` if the URL is valid, indicating that the URL has the correct format +/// and scheme. If the URL is invalid, it returns an `Err` with a message describing +/// the issue. +/// +/// # Example Usage +/// +/// While this function is primarily used internally by [`HorizonClient::new`](crate::horizon_client::HorizonClient::new), +/// it can also be utilized in scenarios where URL validation is necessary before further +/// processing or usage. +/// fn url_validate(url: &str) -> Result<(), String> { - // println!("URL: {}", url); - // check if start with http:// or https:// + // Check if the URL starts with http:// or https:// if !url.starts_with("http://") && !url.starts_with("https://") { return Err(format!("URL must start with http:// or https://: {}", url)); } + + // Attempt to parse the URL to validate its format. Url::parse(url).map_err(|e| e.to_string())?; Ok(()) diff --git a/src/lib.rs b/src/lib.rs index e21be5b..4a34fa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,12 +68,16 @@ pub mod assets; pub mod claimable_balances; /// Client for calling the Stellar Horizon API +/// /// # Constructing a `HorizonClient` /// A string containing the base URL for the Horizon API is required to contruct a client. /// For example, to construct a client for the Horizon API testnet: /// ```rust +/// use stellar_rust_sdk::horizon_client::HorizonClient; +/// /// let base_url = "https://horizon-testnet.stellar.org".to_string(); -/// let horizon_client = HorizonClient::new(base_url).unwrap(); +/// let horizon_client = HorizonClient::new(base_url) +/// .expect("Failed to create Horizon Client");; /// ``` /// /// # Using the `HorizonClient` @@ -81,13 +85,20 @@ pub mod claimable_balances; /// by the Horizon API. For example, it has a [`HorizonClient::get_account_list`](crate::horizon_client::HorizonClient::get_account_list) /// function, which returns an async future that contains a result, as illustrated below: /// ```rust -/// let mut accounts_request = AccountsRequest::new(); -/// accounts_request -/// .set_signer("GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7") -/// .set_limit(10); +/// # use stellar_rust_sdk::assets::prelude::{AllAssetsRequest, AllAssetsResponse}; +/// # use stellar_rust_sdk::models::Request; +/// # use stellar_rust_sdk::horizon_client::HorizonClient; /// -/// let accounts_response: Result = -/// horizon_client.get_account_list(&accounts_request).await; +/// # 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 all_assets_request = AllAssetsRequest::new(); +/// let accounts_response: Result = horizon_client +/// .get_all_assets(&all_assets_request) +/// .await; +/// # Ok({}) +/// # } /// ``` pub mod horizon_client;